diff --git a/src/buffer_reader.js b/src/buffer_reader.js index 9682a01..9142fbc 100644 --- a/src/buffer_reader.js +++ b/src/buffer_reader.js @@ -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; diff --git a/src/cayenne_lpp.js b/src/cayenne_lpp.js new file mode 100644 index 0000000..ab8cab3 --- /dev/null +++ b/src/cayenne_lpp.js @@ -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;