add cayenne lpp parser

This commit is contained in:
liamcottle 2025-09-13 20:49:12 +12:00
parent 1b5c11f7bc
commit 3fcd1ec89e
2 changed files with 231 additions and 0 deletions

View file

@ -60,24 +60,59 @@ class BufferReader {
return view.getUint16(0, true);
}
readUInt16BE() {
const bytes = this.readBytes(2);
const view = new DataView(bytes.buffer);
return view.getUint16(0, false);
}
readUInt32LE() {
const bytes = this.readBytes(4);
const view = new DataView(bytes.buffer);
return view.getUint32(0, true);
}
readUInt32BE() {
const bytes = this.readBytes(4);
const view = new DataView(bytes.buffer);
return view.getUint32(0, false);
}
readInt16LE() {
const bytes = this.readBytes(2);
const view = new DataView(bytes.buffer);
return view.getInt16(0, true);
}
readInt16BE() {
const bytes = this.readBytes(2);
const view = new DataView(bytes.buffer);
return view.getInt16(0, false);
}
readInt32LE() {
const bytes = this.readBytes(4);
const view = new DataView(bytes.buffer);
return view.getInt32(0, true);
}
readInt24BE() {
// read 24-bit (3 bytes) big endian integer
var value = (this.readByte() << 16) | (this.readByte() << 8) | this.readByte();
// convert 24-bit signed integer to 32-bit signed integer
// 0x800000 is the sign bit for a 24-bit value
// if it's set, value is negative in 24-bit two's complement
// so we subtract 0x1000000 (which is 2^24) to get the correct negative value as a Dart integer
if((value & 0x800000) !== 0){
value -= 0x1000000;
}
return value;
}
}
export default BufferReader;

196
src/cayenne_lpp.js Normal file
View file

@ -0,0 +1,196 @@
import BufferReader from "./buffer_reader.js";
class CayenneLpp {
static LPP_DIGITAL_INPUT = 0; // 1 byte
static LPP_DIGITAL_OUTPUT = 1; // 1 byte
static LPP_ANALOG_INPUT = 2; // 2 bytes, 0.01 signed
static LPP_ANALOG_OUTPUT = 3; // 2 bytes, 0.01 signed
static LPP_GENERIC_SENSOR = 100; // 4 bytes, unsigned
static LPP_LUMINOSITY = 101; // 2 bytes, 1 lux unsigned
static LPP_PRESENCE = 102; // 1 byte, bool
static LPP_TEMPERATURE = 103; // 2 bytes, 0.1°C signed
static LPP_RELATIVE_HUMIDITY = 104; // 1 byte, 0.5% unsigned
static LPP_ACCELEROMETER = 113; // 2 bytes per axis, 0.001G
static LPP_BAROMETRIC_PRESSURE = 115; // 2 bytes 0.1hPa unsigned
static LPP_VOLTAGE = 116; // 2 bytes 0.01V unsigned
static LPP_CURRENT = 117; // 2 bytes 0.001A unsigned
static LPP_FREQUENCY = 118; // 4 bytes 1Hz unsigned
static LPP_PERCENTAGE = 120; // 1 byte 1-100% unsigned
static LPP_ALTITUDE = 121; // 2 byte 1m signed
static LPP_CONCENTRATION = 125; // 2 bytes, 1 ppm unsigned
static LPP_POWER = 128; // 2 byte, 1W, unsigned
static LPP_DISTANCE = 130; // 4 byte, 0.001m, unsigned
static LPP_ENERGY = 131; // 4 byte, 0.001kWh, unsigned
static LPP_DIRECTION = 132; // 2 bytes, 1deg, unsigned
static LPP_UNIXTIME = 133; // 4 bytes, unsigned
static LPP_GYROMETER = 134; // 2 bytes per axis, 0.01 °/s
static LPP_COLOUR = 135; // 1 byte per RGB Color
static LPP_GPS = 136; // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter
static LPP_SWITCH = 142; // 1 byte, 0/1
static LPP_POLYLINE = 240; // 1 byte size, 1 byte delta factor, 3 byte lon/lat 0.0001° * factor, n (size-8) bytes deltas
static parse(bytes) {
const buffer = new BufferReader(bytes);
const telemetry = [];
while(buffer.getRemainingBytesCount() >= 2){ // need at least 2 more bytes to get channel and type
const channel = buffer.readUInt8();
const type = buffer.readUInt8();
// stop parsing if channel and type are zero, as there seems to be garbage bytes???
if(channel === 0 && type === 0){
break;
}
switch(type){
case this.LPP_GENERIC_SENSOR: {
const value = buffer.readUInt32BE();
// console.log(`[CayenneLpp] parsed LPP_GENERIC_SENSOR=${value}`);
telemetry.push({
"channel": channel,
"type": type,
"value": value,
});
break;
}
case this.LPP_LUMINOSITY: {
const lux = buffer.readInt16BE();
// console.log(`[CayenneLpp] parsed LPP_LUMINOSITY=${lux}`);
telemetry.push({
"channel": channel,
"type": type,
"value": lux,
});
break;
}
case this.LPP_PRESENCE: {
const presence = buffer.readUInt8();
// console.log(`[CayenneLpp] parsed LPP_PRESENCE=${presence}`);
telemetry.push({
"channel": channel,
"type": type,
"value": presence,
});
break;
}
case this.LPP_TEMPERATURE: {
const temperature = buffer.readInt16BE() / 10;
// console.log(`[CayenneLpp] parsed LPP_TEMPERATURE=${temperature}`);
telemetry.push({
"channel": channel,
"type": type,
"value": temperature,
});
break;
}
case this.LPP_RELATIVE_HUMIDITY: {
const relativeHumidity = buffer.readUInt8() / 2;
// console.log(`[CayenneLpp] parsed LPP_RELATIVE_HUMIDITY=${relativeHumidity}`);
telemetry.push({
"channel": channel,
"type": type,
"value": relativeHumidity,
});
break;
}
case this.LPP_BAROMETRIC_PRESSURE: {
const barometricPressure = buffer.readUInt16BE() / 10;
// console.log(`[CayenneLpp] parsed LPP_BAROMETRIC_PRESSURE=${barometricPressure}`);
telemetry.push({
"channel": channel,
"type": type,
"value": barometricPressure,
});
break;
}
case this.LPP_VOLTAGE: {
// uint16: 0v to 655.35v
// int16: -327.67v to +327.67v
// should be readUInt16BE, but I'm using readInt16BE to allow for negative voltage
const voltage = buffer.readInt16BE() / 100;
// console.log(`[CayenneLpp] parsed LPP_VOLTAGE=${voltage}`);
telemetry.push({
"channel": channel,
"type": type,
"value": voltage,
});
break;
}
case this.LPP_CURRENT: {
// uint16: 0A to 655.35A
// int16: -327.67A to +327.67A
// should be readUInt16BE, but I'm using readInt16BE to allow for negative current
const current = buffer.readInt16BE() / 1000;
// console.log(`[CayenneLpp] parsed LPP_CURRENT=${current}`);
telemetry.push({
"channel": channel,
"type": type,
"value": current,
});
break;
}
case this.LPP_PERCENTAGE: {
const percentage = buffer.readUInt8();
// console.log(`[CayenneLpp] parsed LPP_PERCENTAGE=${percentage}`);
telemetry.push({
"channel": channel,
"type": type,
"value": percentage,
});
break;
}
case this.LPP_CONCENTRATION: {
const concentration = buffer.readUInt16BE();
// console.log(`[CayenneLpp] parsed LPP_CONCENTRATION=${concentration}`);
telemetry.push({
"channel": channel,
"type": type,
"value": concentration,
});
break;
}
case this.LPP_POWER: {
const power = buffer.readUInt16BE();
// console.log(`[CayenneLpp] parsed LPP_POWER=${power}`);
telemetry.push({
"channel": channel,
"type": type,
"value": power,
});
break;
}
case this.LPP_GPS: {
const latitude = buffer.readInt24BE() / 10000;
const longitude = buffer.readInt24BE() / 10000;
const altitude = buffer.readInt24BE() / 100;
// console.log(`[CayenneLpp] parsed LPP_GPS=${latitude},${longitude},${altitude}`);
telemetry.push({
"channel": channel,
"type": type,
"value": {
latitude: latitude,
longitude: longitude,
altitude: altitude,
},
});
break;
}
// todo support all telemetry types, otherwise if an unknown is given, we can't read other telemetry after it
default: {
// console.log(`[CayenneLpp] unsupported type: ${type}`);
return telemetry;
}
}
}
return telemetry;
}
}
export default CayenneLpp;