Page cover image

Topas LoRaWAN

Product Datasheet

Device Profile for Tesenso IoT Cloud

Decoder/Payload Converter for Tesenso IoT Cloud

https://github.com/Tesenso-GmbH/Device-Decoder/blob/main/Topas_Sonic_LoRaWAN.js
// var msg = {
//     "data2" : "29 44 B4 25 33 98 00 20 10 07   7A 0B 00 0020   04 13 35 00 00 00    84 10 13 03 00 00 00     02 5A F1 00    02 FD 17 00 00      02 FD 74 FE 15",
//     "data": "3e 44 b425 11111111 10 07    7af8 00 3025    04 13 b70b0000 8410 13 00000000 02 5a 0000 02 FD17 0108 02 fd74 d016",
//     "data3": "2944B4253398002010077A4620002004139FB400008410130B000000025AF20002FD17080002FD74E415",
//     "data4": "2944B4253398002010077A4620002004139FB400008410130B000000025AF200 02FD17 FF7F 02FD74E415",
//     "data5": "2944 B425 3498 0020 1007 7A45 0000 20 0413D67F000084101317000000025AA50002FD17000002FD74E415",
//     "data6": "780413600B0000841013CD000000025AB90002FD17000002FD740815"
// }

var decodedMsg = msg;
if (msg && msg.data) {
    decodedMsg = main(msg.data)
}


return {
    msg: decodedMsg,
    metadata: metadata,
    msgType: msgType
};

// console.log(decodedMsg)


function main(data) {

    var CI_FIELD = '78';
    var ADDRESS_INDEX_START = 4;
    var ADDRESS_INDEX_END = 7;
    var VERSION_INDEX = 8;
    var MEDIUM_FIELD = 'Water';
    var MANUFACTURER_FIELD = 'IMT';

    var VALUE_DIF_VIF = {
        waterMeter: '04 13',
        reverseWaterMeter: '84 10 13',
        flowTemperature: '02 5a',
        errorCodes: '02 fd 17',
        batteryLifetime: '02 fd 74'
    }
    var STATUS_AND_EVENT_LIST_FIELD = [
        // Don,t remove those following empty fields.
        // Those are required since this full list is required for a 16-bit incoming status and event handling data
        '', '',
        'configMaxWaterTemperature',
        'configLimitMinWaterTemperature',
        'alarmDry',
        'alarmOverFlow',
        'alarmReverseFlow',
        'alarmBatteryLow',
        'alarmNoConsumption',
        'alarmOverTemperature',
        'alarmHeat',
        'alarmFrost',
        'alarmLeak',
        'alarmBurst',
        'alarmAirBubble',
        ''
    ]

    var ERROR_LIST_FIELD = [
        'dry',
        'overFlow',
        'reverseFlow',
        'batteryLow',
        'noConsumption',
        'overTemperature',
        'heat',
        'frost',
        'leak',
        'burst',
        'airBubble'
    ]
    if (!String.prototype.includes) {
        String.prototype.includes = function(search, start) {
            'use strict';
            if (typeof start !== 'number') {
                start = 0;
            }

            if (start + search.length > this.length) {
                return false;
            } else {
                return this.indexOf(search, start) !== -1;
            }
        };
    }
    function format(value)  {
        var bytes = value.replaceAll(' ', '').toLowerCase();
        var byteArr = []
        for (var i = 0; i < bytes.length; i+=2) {
            byteArr.push(bytes[i] + "" + bytes[i + 1])
        }
        return byteArr;
    }
    function isUnparsed(value) {
        return value && !!value.length && value[0] === CI_FIELD;
    }

    function getBodyPayloadStr(value) {
        var payloadStartIndex = isUnparsed(value) ? 1 : 15;
        if (value.length > payloadStartIndex) {
            return value.slice(payloadStartIndex).join(' ');
        }
        return '';
    }
    function getRevHexValue(value, startIndex, endIndex, convertToDecimal, devider) {
        endIndex = endIndex || startIndex;
        convertToDecimal = convertToDecimal || false;
        devider = devider || 1;
        var result = Array.isArray(value) && !!value.length && value.slice(startIndex, endIndex + 1).reverse().join('') || null;
        if (!!result && convertToDecimal) {
            result = parseInt(result, 16) / devider
        }
        return result;
    }

    function getAddressAndHeaders(value) {
        var res = {};
        if (isUnparsed(value)) {
            res.ciField = CI_FIELD;
        } else {
            res = {
                secondaryAddress: getRevHexValue(value, ADDRESS_INDEX_START, ADDRESS_INDEX_END),
                medium: MEDIUM_FIELD,
                length: getRevHexValue(value, 0,0, true),
                manufacturer: MANUFACTURER_FIELD,
                version: getRevHexValue(value, VERSION_INDEX, VERSION_INDEX, true)
            }
        }
        return res
    }
    function getDeviceValue(key) {
        var result = 1;
        if (key === 'waterMeter' || key === 'reverseWaterMeter') {
            result = 1000;
        } else if (key === 'flowTemperature') {
            result = 10;
        }
        return result;
    }
    function getValuesByDifVif(valueStr, result) {
        result = result || {};
        if (!valueStr) {
            return result;
        }
        var newValueStr;
        var foundResKey;
        for (var key in VALUE_DIF_VIF) {
           if ( valueStr.startsWith(VALUE_DIF_VIF[key])) {
               foundResKey = key;
               break;
           }
        }
        if (!!foundResKey) {
            var difVifArr = VALUE_DIF_VIF[foundResKey].split(' ');
            if (foundResKey === 'reverseWaterMeter') {
                difVifArr[0] = '04'
            }
            var dataIndexLength = parseInt(difVifArr[0]);
            var payloadLength = difVifArr.length + dataIndexLength;
            result[foundResKey] = getRevHexValue(
                valueStr.split(' '),
                payloadLength-dataIndexLength, payloadLength-1, foundResKey !== 'errorCodes', getDeviceValue(foundResKey));
            newValueStr = valueStr.split(' ').slice(payloadLength).join(' ');
            return getValuesByDifVif(newValueStr, result);
        }
        newValueStr = valueStr.split(' ').slice(1).join(' ');
        return getValuesByDifVif(newValueStr, result);
    }

    function getDeviceValues(value) {
        var bodyPayloadStr = getBodyPayloadStr(value);
        return getValuesByDifVif(bodyPayloadStr);
    }

    function getStatusAndEvents(errorCode) {
        var errorCodeInt = parseInt(errorCode, 16);
        var errorCodeBin = errorCodeInt.toString(2)
        if (errorCodeBin.length < 16 ) {

        }
        var shortLength  = 16 - errorCodeBin.length;
        for( var i = 0; i < shortLength; i++) {
            errorCodeBin = '0' + errorCodeBin;
        }
        var result = {
            error: !!errorCodeInt,
            errors: ''
        }

        for (var index = 0; index <= STATUS_AND_EVENT_LIST_FIELD.length; index ++) {
            var field = STATUS_AND_EVENT_LIST_FIELD[index];
            if (!!field) {
                result[field] = index<=3 ? parseInt(errorCodeBin[index]) : !!parseInt(errorCodeBin[index]);
                var errorVal;
                if (!!result[field]) {
                    for (var j = 0; j < ERROR_LIST_FIELD.length; j++ ) {
                        if (field.toLowerCase().includes(ERROR_LIST_FIELD[j].toLowerCase())) {
                            errorVal = ERROR_LIST_FIELD[j];
                            result.errors += !!errorVal ? !!result.errors ? ', ' + errorVal : errorVal : '';
                            break;
                        }
                    }
                }
            }
        }
        return result;
    }
    function decode(rowValue) {
        var formattedValue = format(rowValue);

        var addressAndHeaders = getAddressAndHeaders(formattedValue);
        var deviceValues = getDeviceValues(formattedValue);
        var statusAndEvents = getStatusAndEvents(deviceValues.errorCodes)
        return Object.assign({}, addressAndHeaders, deviceValues, statusAndEvents);
    }
    return decode(data);
}

Device dataKeys

in manual
dataKey
dataType
dataFormat

address

secondaryAdress

serverAttribute

string

manufacturer

manufacturer

serverAttribute

string

device_type

medium

serverAttribute

string

0_0_0_volume_m³

waterMeter

telemetry

integer

0_0_1_volume_m³

reverseWaterMeter

telemetry

integer

0_0_0_supply_temperature_°C

flowTemperature

telemetry

integer

remaining battery lifetime

batteryLifetime

telemetry

integer

config_limit_max_water_temp

configMaxWaterTemperature

serverAttribute

integer

config_limit_min_water_temp

configLimitMinWaterTemperature

serverAttribute

integer

error

error

telemetry → use later for alarming (boolean)

boolean

errors

string of errors if any –> if possible, please list them separate as below dry,overflow,reverse_flow,battery_low,over_temperature,heat,frost,leak,burst,air_bubble", "status_no_consumption

→ list them separate as datapoint as boolean value

string

dry

alarmDry

telemetry → use later for alarming

boolean

overflow

alarmOverFlow

telemetry → use later for alarming

boolean

reverse_flow

alarmReverseFlow

telemetry → use later for alarming

boolean

battery_low

alarmBatteryLow

telemetry → use later for alarming

boolean

over_temperature

alarmOverTemperature

telemetry → use later for alarming

boolean

heat

alarmHeat

telemetry → use later for alarming

boolean

frost

alarmFrost

telemetry → use later for alarming

boolean

burst

alarmBurst

telemetry → use later for alarming

boolean

leak

alarmLeak

telemetry → use later for alarming

boolean

air_bubble

alarmAirBubble

telemetry → use later for alarming

boolean

status_no_consumption

alarmNoConsumption

telemetry → use later for alarming

boolean

Last updated