Page cover image

TRP-12 Water

Device Profile for Tesenso IoT Cloud

Decoder/Payload Converter for Tesenso IoT Cloud

https://github.com/Tesenso-GmbH/Device-Decoder/blob/main/Tetraedre_TRP-11_V1.js
// V1.0, 09.08.2021, DS
// V1.1, 11.03.2022, DS


if (msg.data) {
    var decoded = decodeFromHex(parseHexString(msg.data));
    if(decoded.register0801 != null && decoded.register0802 != null){
        decoded.electricityMeterEnergy = decoded.register0801 + decoded.register0802;
    }
    decoded.ts = msg.ts;
    decoded.rssi = msg.rssi;
    decoded.snr = msg.snr;
    decoded.toa = msg.toa;
    decoded.frequency = msg.frequency;
    decoded.dr = msg.dr;
    decoded.bat = decodeBattery(msg.bat);
    decoded.hex = msg.data;

    return {
        msg: decoded,
        metadata: metadata,
        msgType: msgType
    };
} else {
    return {
        msg: msg,
        metadata: metadata,
        msgType: msgType
    };
}


function decodeFromHex(bytes) {
    // Decode an uplink message from a buffer
    // (array) of bytes to an object of fields.

    var telemetry = {};

    // Get data length, main_header and initiate the counter
    var sensorDataLength = bytes.length;
    var counter = 1;
    var main_header = bytes[0];

    // Loop through the data and decode the chuncks. Add them to the telemetry
    // immediatly
    while (counter < sensorDataLength) {
        var identifier = bytes[counter];
        var chunkLength = 0;
        var obj = {};
        var subbytes = [];

        if (identifier >= 01 && identifier <= 95) {
            //identifierChunch = "ChunkTypeA";
            chunkLength =
                3; // Type A Chunk. Fixed size. 3 bytes
            subbytes = bytes.slice(counter, counter +
                chunkLength);
            obj = parseChunkTypeA(subbytes);
        } else if (identifier >= 96 && identifier <= 127) {
            //identifierChunch = "ChunkTypeD";
            chunkLength =
                2; // Type D Chunk. Fixed size. 2 bytes
            subbytes = bytes.slice(counter, counter +
                chunkLength);
            obj = parseChunkTypeD(subbytes);
        } else if (identifier >= 128 && identifier <= 191) {
            //identifierChunch = "ChunkTypeB";
            chunkLength =
                5; // Type B Chunk. Fixed size. 5 bytes
            subbytes = bytes.slice(counter, counter +
                chunkLength);
            obj = parseChunkTypeB(subbytes);
        } else if (identifier >= 192 && identifier <= 254) {
            //identifierChunch = "ChunkTypeC";
            chunkLength = bytes[counter + 1] +
                2; // Type C Chunk. Variable size. Size  + 2 (+ leading 2 bytes)
            if (counter + chunkLength > sensorDataLength) {
                chunkLength = sensorDataLength - counter;
            }
            // check if there already is a timesatmp present
            var timestamp = msg.ts;
            // if ('ts' in telemetry) {
            //     timestamp = telemetry[0].ts;
            // }
            subbytes = bytes.slice(counter, counter +
                chunkLength);
            // check if the payload already contained a timestamp
            obj = parseChunkTypeC(subbytes, timestamp);
        } else {
            continue;
        }
        // Push the parsed data to the telemetry array and increment counter
        // Check if it is an array, push the data if yes, otherwise concatenate
        if (Array.isArray(obj)) {
            telemetry.push(obj);
        } else {
            telemetry = Object.assign(telemetry, obj);
        }

        counter += chunkLength;
    }

    // check if the timestamp has been filled by the payload, otherwise use
    // the timestamp of the uplink to loriot
    if (!('ts' in telemetry)) {
        telemetry.ts = msg.ts;
    }

    return telemetry;
}

function decodeBattery(byte) {
    if (byte == 0) {
        return 'External power source';
    } else if (byte > 0 && byte < 255) {
        return byte / 254 * 100;
    } else {
        return 'Unknown battery state'
    }
}

function parseHexString(hex) {
    for (var bytes = [], c = 0; c < hex.length; c += 2)
        bytes.push(parseInt(hex.substr(c, 2), 16));
    return bytes;
}


function decodeToString(payload) {
    return String.fromCharCode.apply(String, payload);
}

function decodeToJson(payload) {
    // covert payload to string.
    var str = decodeToString(payload);
    // parse string to JSON
    var data = JSON.parse(str);
    return data;
}

function twos_complement(input_value, num_bits) {
    var mask = Math.pow(2, num_bits - 1);
    return -(input_value & mask) + (input_value & ~mask);
}

function get_int(bytes) {
    var length = bytes.length;
    var int = 0;
    var i;

    for (i = 0; i < length; i++) {
        int += bytes[length - i - 1] << (i * 8);
    }

    return int;
}

function get_float(bytes) {
    var length = bytes.length;
    var float = 0;
    var int = get_int(bytes);

    if (length == 4) {
        var sign = (int & 0x80000000) ? -1 : 1;
        var exponent = ((int >> 23) & 0xFF) - 127;
        var significand = (int & ~(-1 << 23));

        if (exponent == 128) {
            float = sign * ((significand) ? Number.NaN :
                Number.POSITIVE_INFINITY);
        } else if (exponent == -127) {
            if (significand == 0) {
                float = sign * 0.0;
            } else {
                exponent = -126;
                significand /= (1 << 22);
                float = sign * significand * Math.pow(2,
                    exponent);
            }
        } else {
            significand = (significand | (1 << 23)) / (1 <<
                23);
            float = sign * significand * Math.pow(2,
                exponent);
        }
    } else if (length == 2) {
        var left = (int >> 14) & 0x03;
        var value0 = int & 0x3FFF;
        float = value0;
        if (left == 0) {
            float = float * 0.001;
        } else if (left == 1) {
            float = float * 0.02;
            float = float + 16.38;
        } else if (left == 3) {
            float = float * 5;
            float = float + 16725;
        } else if (left == 2) {
            float = float + 344;
        }
    } else {
        throw "Undefined floating point size";
    }
    return float;
}

function parseChunkTypeA(bytes) {
    var telemetry = {};
    var dataHeader = bytes[0];
    var dataValue = get_int(bytes.slice(1));
    if (dataHeader ==
        1
    ) { // 1 = 0x01 Temperature. signed integer 16bits. 1 LSB = 0.01°C.   0: 0°C; 1: +0.01°C; 0xFFFF : -0.01°C
        telemetry.temperature = twos_complement(dataValue,
            16) / 100;
    } else if (dataHeader ==
        2
    ) { // 2 = 0x02 Relative Humidity. unsigned integer 16bits. 1 LSB = 0.01°RH
        telemetry.humidity = twos_complement(dataValue,
            16) / 100;
    } else if (dataHeader ==
        3
    ) { // 3 = 0x03 Oxygen concentration. 1 LSB = 0.001%
        telemetry.oxygen = dataValue / 1000;
    } else if (dataHeader ==
        4) { // 4 = 0x04 CO2 concentration. 1 LSB = 0.001%
        telemetry.co2 = dataValue / 1000;
    } else if (dataHeader ==
        5
    ) { // 5 = 0x05 Second temperature. signed integer 16bits. 1 LSB = 0.01°C.   0: 0°C; 1: +0.01°C; 0xFFFF : -0.01°C
        telemetry.temperature2 = twos_complement(dataValue,
            16) / 100;
    } else if (dataHeader ==
        6
    ) { // 6 = 0x06 Pressure. unsigned integer 16bits. 1 LSB = 0.5mbar
        telemetry.pressure = dataValue / 2;
    } else if (dataHeader ==
        7) { // 7 = 0x07 analog channel #0. 1 LSB = 1 uA
        telemetry.analogCurrent0 = dataValue;
    } else if (dataHeader ==
        8) { // 8 = 0x08 analog channel #1. 1 LSB = 1 uA
        telemetry.analogCurrent1 = dataValue;
    } else if (dataHeader ==
        9) { // 9 = 0x09 analog channel #2. 1 LSB = 1 uA
        telemetry.analogCurrent2 = dataValue;
    } else if (dataHeader ==
        10) { // 10 = 0x0A analog channel #3. 1 LSB = 1 uA
        telemetry.analogCurrent3 = dataValue;
    } else if (dataHeader ==
        11) { // 11 = 0x0B digital inputs state
        telemetry.digitalInputsState = dataValue;
    } else if (dataHeader ==
        12) { // 12 = 0x0C relative pulse counter 0
        telemetry.relativePulseCounter0 = dataValue;
    } else if (dataHeader ==
        13) { // 13 = 0x0D relative pulse counter 1
        telemetry.relativePulseCounter1 = dataValue;
    } else if (dataHeader ==
        14) { // 14 = 0x0E relative pulse counter 2
        telemetry.relativePulseCounter2 = dataValue;
    } else if (dataHeader ==
        16) { // 16 = 0x10 analog channel #0. 1 LSB = 1 mV
        telemetry.analogVoltage0 = dataValue;
    } else if (dataHeader ==
        17) { // 17 = 0x11 analog channel #1. 1 LSB = 1 mV
        telemetry.analogVoltage1 = dataValue;
    } else if (dataHeader ==
        18) { // 18 = 0x12 analog channel #2. 1 LSB = 1 mV
        telemetry.analogVoltage2 = dataValue;
    } else if (dataHeader ==
        19) { // 19 = 0x12 analog channel #3. 1 LSB = 1 mV
        telemetry.analogVoltage3 = dataValue;
    }
    return telemetry;
}

function parseChunkTypeB(bytes) {
    var telemetry = {};
    var dataHeader = bytes[0];
    var dataValue = bytes.slice(1);
    // console.log(dataHeader)
    if (dataHeader ==
        128
    ) { // 128 = 0x80 timestamp of the measurement. unsigned integer 32 bits. UNIX timestamp
        // console.log(dataValue)
        // console.log(get_int(dataValue))
        //telemetry.ts = twos_complement(get_int(dataValue), 32) *1000; // calculate it in miliseconds
    } else if (dataHeader ==
        129
    ) { // 129 = 0x81 Main energy index. IEEE754 floating point. Unit : kWh for electricity meters (register 1.8.0)
        telemetry.electricityMeterEnergy = get_float(
            dataValue);
    } else if (dataHeader ==
        131
    ) { // 129 = 0x81 Main energy index. IEEE754 floating point. Unit : kWh for electricity meters (register 1.8.0)
        telemetry.register0801 = get_float(
            dataValue);
    } else if (dataHeader ==
        132
    ) { // 129 = 0x81 Main energy index. IEEE754 floating point. Unit : kWh for electricity meters (register 1.8.0)
        telemetry.register0802 = get_float(
            dataValue);
    } else if (dataHeader ==
        133
    ) { // 133 = 0x85 Main energy index. IEEE754 floating point. Unit : m3 for water meter
        telemetry.waterMeter = get_float(dataValue);
    } else if (dataHeader ==
        134
    ) { // 134 = 0x86 Main energy index. IEEE754 floating point. Unit : m3 for uncorrected gas meter
        telemetry.gasMeterVolume = get_float(dataValue);
    } else if (dataHeader ==
        135
    ) { // 135 = 0x87 Flow temperature. IEEE754 floating point. Unit : °C
        telemetry.flowTemperature = get_float(dataValue);
    } else if (dataHeader ==
        138
    ) { // 138 = 0x8a Power register. IEEE754 floating point. Unit W.
        telemetry.power = get_float(dataValue);
    } else if (dataHeader ==
        139
    ) { // 139 = 0x8b Volume index. IEEE754 floating point. Unit: m3 for heat meter
        telemetry.heatMeterVolume = get_float(dataValue);
    } else if (dataHeader ==
        140
    ) { // 140 = 0x8c Return flow temperature. IEEE754 floating point. Unit: °C
        telemetry.returnFlowTemperature = get_float(
            dataValue);
    } else if (dataHeader ==
        141
    ) { // 141 = 0x8d VolumeFlow = flowRate. IEEE754 floating point. Unit: m3/h
        telemetry.flowRate = get_float(dataValue);
    } else if (dataHeader ==
        154
    ) { // 154 = 0x9A IEEE754 floating point. Unit : kWh for heat meters (register 1.8.0)
        telemetry.heatMeterEnergy = get_float(dataValue);
    }
    return telemetry;
}

function parseChunkTypeC(bytes, timestamp) {
    var telemetry = [];
    var size = bytes.length;
    var dataHeader = bytes[0];
    var snr = get_int(bytes.slice(1, 5));
    var status = bytes.slice(5, 6);
    var interval =
        900000; // accumulation interval in milliseconds
    var lastReadIndex = get_float(bytes.slice(6, 10));
    var idx = 10;
    var counter = 1;

    // Get the interval from the status
    if ((status & 0x1c) == 0) {
        interval = 3600000;
    } else if ((status & 0x1c) == 0x4) {
        interval = 900000;
    } else if ((status & 0x1c) == 0x8) {
        interval = 38400000;
    }
    // Loop through the deltas and change the timestamp accordingly
    if (dataHeader ==
        201
    ) { // 140 = 0x8c Return flow temperature. IEEE754 floating point. Unit: °C
        telemetry.push({
            //'ts': timestamp,
            'waterMeterSNR': snr,
            'waterMeterCounter': lastReadIndex,
            'batteryError': Boolean(status &
                0x2),
            'otherError': Boolean(status & 1)
        })
        while (idx <= size) {
            var slice = bytes.slice(idx, idx + 2);
            if (!(get_int(slice) == 0xffff)) {
                telemetry.push({
                    //'ts': timestamp - counter * interval,
                    'waterMeterCounter': lastReadIndex +
                        get_float(slice)
                })
            }
            idx += 2;
            counter++;
        }
    } else if (dataHeader ==
        202
    ) { // 141 = 0x8d Volume flow. IEEE754 floating point. Unit: m3/h
        telemetry.push({
            //'ts': timestamp,
            'gasMeterSNR': snr,
            'gasMeterCounter': lastReadIndex,
            'batteryError': Boolean(status &
                0x2),
            'otherError': Boolean(status & 1)
        })
        while (idx <= size) {
            var slice = bytes.slice(idx, idx + 2);
            if (!(get_int(slice) == 0xffff)) {
                telemetry.push({
                    //'ts': timestamp - counter * interval,
                    'gasMeterCounter': lastReadIndex +
                        get_float(slice)
                })
            }
            idx += 2;
            counter++;
        }
    }
    return telemetry;
}

function parseChunkTypeD(bytes) {
    var telemetry = {};
    var dataHeader = bytes[0];
    var dataValue = get_int(bytes.slice(1));

    if (dataHeader == 96) { // 96 = 0x60 Battery voltage
        var volt = 0;
        if (dataValue >= 81) {
            volt = 4.2 + (dataValue - 80) * 0.1
        } else {
            volt = 1.8 + dataValue * 0.03
        }
        telemetry.batteryVoltage = volt;
    } else if (dataHeader ==
        97) { // 97 = 0x61 M-BUS meter status byte
        telemetry.mBusMeterStatus = dataValue;
    }
    return telemetry;
}

function decodeBattery(byte) {
    if (byte == 0) {
        return 'External power source';
    } else if (byte > 0 && byte < 255) {
        return byte / 254 * 100;
    } else {
        return 'Unknown battery state';
    }
}
    "cmd": "gw",
    "seqno": 147729,
    "EUI": "BE7A000000000027",
    "ts": 1661434039877,
    "fcnt": 198,
    "port": 3,
    "freq": 868500000,
    "toa": 1810,
    "dr": "SF12 BW125 4/5",
    "ack": false,
    "gws": [{
        "rssi": -87,
        "snr": 7.2,
        "ts": 1661434039877,
        "time": "2022-08-25T13:27:19.763929Z",
        "gweui": "9C65F9FFFF386BD3",
        "ant": 0,
        "lat": 47.3855578,
        "lon": 8.5384989
    }, {
        "rssi": -112,
        "snr": -17.8,
        "ts": 1661434039898,
        "time": "2022-08-25T13:27:19.776447Z",
        "gweui": "9C65F9FFFF38674D",
        "ant": 0,
        "lat": 47.385558700000004,
        "lon": 8.5385182
    }],
    "bat": 255,
    "data": "01800002b14d6000820093be028545efe800",
    "_id": "630778b83184c84733e8971e"
}n
{
    "msg": {
        "batteryVoltage": 1.8,
        "waterMeter": 7677,
        "ts": 1661262701281,
        "rssi": -80,
        "snr": 9.8,
        "toa": 61,
        "frequency": 867700000,
        "dr": "SF7 BW125 4/5",
        "bat": "External power source",
        "hex": "01800002b14d6000820093be028545efe800"
    },
    "metadata": {
        "deviceType": "default",
        "deviceName": "Test Device",
        "ts": "1661434329768"
    },
    "msgType": "POST_TELEMETRY_REQUEST"
}

Last updated