-
-
Save iPAS/e24970a91463a4a8177f9806d1ef14b8 to your computer and use it in GitHub Desktop.
/** | |
* Cayenne Low-power Payload Library | |
* | |
* @author Pasakorn Tiwatthanont | |
* @email [email protected] | |
* | |
* @link https://htmlcheatsheet.com/js/ | |
*/ | |
// console.log('Node.js version: ' + process.version) | |
format = require('string-format') | |
/** | |
* Byte stream to fixed-point decimal word. | |
* | |
* @param stream: array of bytes. | |
* @return: word of the bytes in big-endian format. | |
*/ | |
function arrayToDecimal(stream, is_signed=false, decimal_point=0) { | |
var value = 0; | |
for (var i = 0; i < stream.length; i++) { | |
if (stream[i] > 0xFF) | |
throw 'Byte value overflow!'; | |
value = (value<<8) | stream[i]; | |
} | |
if (is_signed) { | |
edge = 1 << (stream.length )*8; // 0x1000.. | |
max = (edge-1) >> 1; // 0x0FFF.. >> 1 | |
value = (value > max) ? value - edge : value; | |
} | |
if (decimal_point) { | |
value /= Math.pow(10, decimal_point); | |
} | |
return value; | |
} | |
/** | |
* Cayenne Low-power Payload Decoder | |
* | |
* @param payload: array of bytes. | |
* @return: JSON | |
*/ | |
function decode(payload) { | |
if (payload == null) | |
payload = [ | |
0x01, 0x67, 0x00, 0xE1, // "temperature_1" : 22.5 (fixed point) | |
0x02, 0x73, 0x29, 0xEC, // "barometric_pressure_2": 1073.2 (fixed point) | |
0x03, 0x88, // "gps_3": | |
0x02, 0xDD, 0xFC, // { "latitude" : 18.79, | |
0x0F, 0x1A, 0x68, // "longitude": 98.98, | |
0x00, 0x79, 0x18, // "altitude" : 310 } | |
] | |
/** | |
* @reference https://github.com/myDevicesIoT/cayenne-docs/blob/master/docs/LORA.md | |
* | |
* Type IPSO LPP Hex Data Size Data Resolution per bit | |
* Digital Input 3200 0 0 1 1 | |
* Digital Output 3201 1 1 1 1 | |
* Analog Input 3202 2 2 2 0.01 Signed | |
* Analog Output 3203 3 3 2 0.01 Signed | |
* Illuminance Sensor 3301 101 65 2 1 Lux Unsigned MSB | |
* Presence Sensor 3302 102 66 1 1 | |
* Temperature Sensor 3303 103 67 2 0.1 °C Signed MSB | |
* Humidity Sensor 3304 104 68 1 0.5 % Unsigned | |
* Accelerometer 3313 113 71 6 0.001 G Signed MSB per axis | |
* Barometer 3315 115 73 2 0.1 hPa Unsigned MSB | |
* Gyrometer 3334 134 86 6 0.01 °/s Signed MSB per axis | |
* GPS Location 3336 136 88 9 Latitude : 0.0001 ° Signed MSB | |
* Longitude : 0.0001 ° Signed MSB | |
* Altitude : 0.01 meter Signed MSB | |
*/ | |
var sensor_types = { | |
0 : {'size': 1, 'name': 'Digital Input' , 'signed': false, 'decimal_point': 0,}, | |
1 : {'size': 1, 'name': 'Digital Output' , 'signed': false, 'decimal_point': 0,}, | |
2 : {'size': 2, 'name': 'Analog Input' , 'signed': true , 'decimal_point': 2,}, | |
3 : {'size': 2, 'name': 'Analog Output' , 'signed': true , 'decimal_point': 2,}, | |
101: {'size': 2, 'name': 'Illuminance Sensor' , 'signed': false, 'decimal_point': 0,}, | |
102: {'size': 1, 'name': 'Presence Sensor' , 'signed': false, 'decimal_point': 0,}, | |
103: {'size': 2, 'name': 'Temperature Sensor' , 'signed': true , 'decimal_point': 1,}, | |
104: {'size': 1, 'name': 'Humidity Sensor' , 'signed': false, 'decimal_point': 1,}, | |
113: {'size': 6, 'name': 'Accelerometer' , 'signed': true , 'decimal_point': 3,}, | |
115: {'size': 2, 'name': 'Barometer' , 'signed': false, 'decimal_point': 1,}, | |
134: {'size': 6, 'name': 'Gyrometer' , 'signed': true , 'decimal_point': 2,}, | |
136: {'size': 9, 'name': 'GPS Location' , 'signed': false, 'decimal_point': [4,4,2], },}; | |
var sensors = {}; | |
var i = 0; | |
while (i < payload.length) { | |
// console.log(i); | |
// console.log(typeof payload[i]) | |
// console.log(payload[i].toString()) | |
s_no = payload[i++]; | |
s_type = payload[i++]; | |
if (typeof sensor_types[s_type] == 'undefined') | |
throw format('Sensor type error!: {}', s_type); | |
s_size = sensor_types[s_type].size; | |
s_name = sensor_types[s_type].name; | |
switch (s_type) { | |
case 0 : // Digital Input | |
case 1 : // Digital Output | |
case 2 : // Analog Input | |
case 3 : // Analog Output | |
case 101: // Illuminance Sensor | |
case 102: // Presence Sensor | |
case 103: // Temperature Sensor | |
case 104: // Humidity Sensor | |
case 113: // Accelerometer | |
case 115: // Barometer | |
case 134: // Gyrometer | |
s_value = arrayToDecimal(payload.slice(i, i+s_size), | |
is_signed = sensor_types[s_type].signed, | |
decimal_point = sensor_types[s_type].decimal_point); | |
//console.log(format('no:{} size:{} type:{} value:{}', s_no, s_size, s_name, s_value)); | |
break; | |
case 136: // GPS Location | |
s_value = { | |
'latitude': arrayToDecimal(payload.slice(i+0, i+3), is_signed=sensor_types[s_type].signed, decimal_point=sensor_types[s_type].decimal_point[0]), | |
'longitude': arrayToDecimal(payload.slice(i+3, i+6), is_signed=sensor_types[s_type].signed, decimal_point=sensor_types[s_type].decimal_point[1]), | |
'altitude': arrayToDecimal(payload.slice(i+6, i+9), is_signed=sensor_types[s_type].signed, decimal_point=sensor_types[s_type].decimal_point[2]),}; | |
//console.log(format('no:{} size:{} type:{} lat:{} lon:{} alt:{}', s_no, s_size, s_name, s_value.lat, s_value.lon, s_value.alt)); | |
break; | |
} | |
sensors[s_no] = {'type': s_type, 'type_name': s_name, 'value': s_value }; | |
i += s_size; | |
} | |
return sensors; | |
} | |
/** | |
* Test | |
*/ | |
function test() { | |
// Test arrayToDecimal() | |
arrTest = [ 0xFF ]; | |
console.log(arrayToDecimal(arrTest) + ' -> ' + arrayToDecimal(arrTest, is_signed=true)); // -1 | |
arrTest = [ 0xFF, 0xFF ]; | |
console.log(arrayToDecimal(arrTest) + ' -> ' + arrayToDecimal(arrTest, is_signed=true)); // -1 | |
// Test decode() | |
console.log(decode()); | |
} | |
test() |
Alternatively, rather than adding a single exception, one could also replace the decimals handling. Like maybe make arrayToDecimal
use some resolution
parameter, rather than decimal_point
:
function arrayToDecimal(stream, is_signed = false, resolution = 1) {
...
value *= resolution;
// Ensure the JavaScript IEEE 754 floating point calculations don't
// return too many decimals, like for 111 * 0.1 = 11.100000000000001.
// Unary plus-operator to cast string result of toFixed to number.
return +value.toFixed(10);
}
...along with fixing all definitions to match the table from the comments:
var sensor_types = {
0: {size: 1, name: "Digital Input", signed: false, resolution: 1},
1: {size: 1, name: "Digital Output", signed: false, resolution: 1},
2: {size: 2, name: "Analog Input", signed: true, resolution: 0.01}
...
104: {size: 1, name: "humidity", signed: false, resolution: 0.5},
...
...and the invocations, such as:
while (i < payload.length) {
...
switch (s_type) {
...
case 104: // Humidity Sensor
case 113: // Accelerometer
case 115: // Barometer
case 134: // Gyrometer
s_value = arrayToDecimal(
payload.slice(i, i + s_size),
sensor_types[s_type].signed,
sensor_types[s_type].resolution
);
break;
...
As for the additional use of toFixed
, beware that JavaScript's 0.1 cannot be represented in an exact JavaScript IEEE 754 floating point, yielding funny results like 0.1 * 111= 11.100000000000001, while 111 / 10 = 11.1 and 0.01 * 111 = 1.11 are just fine.
Finally: lacking named parameters in JavaScript, using is_signed
in is_signed = sensor_types[s_type].signed
really just creates a temporary variable that is not used anywhere in the code, so can be omitted.
The script decode Humidity incorrectly, due resolution for humidity is 0,5% per bit and no 0,1% per bit.
So, the code need to be modified, replacing
case 134: // Gyrometer
s_value = arrayToDecimal(payload.slice(i, i+s_size),
is_signed = sensor_types[s_type].signed,
decimal_point = sensor_types[s_type].decimal_point);
//console.log(format('no:{} size:{} type:{} value:{}', s_no, s_size, s_name, s_value));
break;
with
case 134: // Gyrometer
s_value = arrayToDecimal(payload.slice(i, i+s_size),
is_signed = sensor_types[s_type].signed,
decimal_point = sensor_types[s_type].decimal_point);
//console.log(format('no:{} size:{} type:{} value:{}', s_no, s_size, s_name, s_value));
if (s_type == 104) { s_value = s_value * 5}; // Humidity Data Resolution is 0,5% per bit, not 0,1% per bit
break;