For the complete documentation index, see llms.txt. This page is also available as Markdown.

Milesight

Creation procedure

Network Server → Payload Codec → Custom Payload Codec

Copy and paste each of the following in their respective fields:

Payload Decoder Function

// DataCake
function Decoder(bytes, port) {
    var decoded = decodeUplink({ bytes: bytes, fPort: port }).data;
    return decoded;
}

// Milesight
function Decode(port, bytes) {
    var decoded = decodeUplink({ bytes: bytes, fPort: port }).data;
    return decoded;
}

// The Things Industries / Main
function decodeUplink(input) {
    try {
        var bytes = input.bytes;
        var data = {};

        function handleKeepalive(bytes, data) {
            var tempRaw = (bytes[1] << 8) | bytes[2];
            data.sensorTemperature = Number(((tempRaw - 400) / 10).toFixed(2));
            data.relativeHumidity  = Number(((bytes[3] * 100) / 256).toFixed(2));
            data.targetTemperature = ((bytes[4] << 8) | bytes[5]) / 10;
            data.operationalMode   = bytes[6];   // 0 Ventilation, 1 Heating, 2 Cooling
            data.displayedFanSpeed = bytes[7];   // 0 Auto .. 6 High
            data.actualFanSpeed    = bytes[8];   // 1..6 speeds, 7 Fan off
            data.valveStatus       = bytes[9];   // 0 closed, 1 open
            data.deviceStatus      = bytes[10];  // 0 off, 1 on
            return data;
        }

        function handleResponse(bytes, data) {
            var commands = bytes.map(function (byte) {
                return ("0" + byte.toString(16)).substr(-2);
            });
            commands = commands.slice(0, -11);
            var command_len = 0;

            commands.map(function (command, i) {
                switch (command) {
                    case '04': {
                        command_len = 2;
                        // Nibble-encoded: 0x25 => version 2.5 (FIX D5)
                        var hwByte = parseInt(commands[i + 1], 16);
                        var swByte = parseInt(commands[i + 2], 16);
                        data.devHwVer = ((hwByte >> 4) & 0x0F) + ((hwByte & 0x0F) / 10);
                        data.devSwVer = ((swByte >> 4) & 0x0F) + ((swByte & 0x0F) / 10);
                        break;
                    }
                    case '05': {
                        command_len = 1;
                        data.targetTemperatureStep = parseInt(commands[i + 1], 16) / 10;
                        break;
                    }
                    case '12': {
                        command_len = 1;
                        data.keepAliveTime = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '14': {
                        command_len = 1;
                        // raw enum 0-5 (FIX D6): 0 none, 1 all, 2 onoff+mode,
                        // 3 onoff, 4 all-except-onoff, 5 mode
                        data.keysLock = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '15': {
                        command_len = 2;
                        data.tempRangeMin = parseInt(commands[i + 1], 16);
                        data.tempRangeMax = parseInt(commands[i + 2], 16);
                        break;
                    }
                    case '17': {
                        command_len = 4;
                        data.heatingTempMin = parseInt(commands[i + 1], 16);
                        data.heatingTempMax = parseInt(commands[i + 2], 16);
                        data.coolingTempMin = parseInt(commands[i + 3], 16);
                        data.coolingTempMax = parseInt(commands[i + 4], 16);
                        break;
                    }
                    case '19': {
                        command_len = 1;
                        // raw*5 seconds -> minutes
                        data.joinRetryPeriod = parseInt(commands[i + 1], 16) * 5 / 60;
                        break;
                    }
                    case '1b': {
                        command_len = 1;
                        data.uplinkType = parseInt(commands[i + 1], 16); // 0 unconf, 1 conf
                        break;
                    }
                    case '1d': {
                        command_len = 2;
                        // plain numbers, 0 = disabled (FIX D9)
                        data.wdogConfirmed   = parseInt(commands[i + 1], 16);
                        data.wdogUnconfirmed = parseInt(commands[i + 2], 16);
                        break;
                    }
                    case '2f': {
                        command_len = 2; // FIX D1: 2 bytes, /10
                        data.targetTemperature =
                            (((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))) / 10;
                        break;
                    }
                    case '30': {
                        command_len = 2; // FIX D2: 2 bytes, /10
                        data.manualTargetTemperature =
                            (((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))) / 10;
                        break;
                    }
                    case '32': {
                        command_len = 1;
                        data.valveOpenCloseTime = parseInt(commands[i + 1], 16); // seconds
                        break;
                    }
                    case '34': {
                        command_len = 1;
                        data.displayRefreshPeriod = parseInt(commands[i + 1], 16); // hours
                        break;
                    }
                    case '36': {
                        command_len = 1;
                        data.extAutoTempControl = parseInt(commands[i + 1], 16); // 0/1
                        break;
                    }
                    case '3e': {
                        command_len = 2; // FIX D4: /10
                        data.extSensorTemperature =
                            (((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16))) / 10;
                        break;
                    }
                    case '41': {
                        command_len = 1;
                        data.currentTempVisibility = parseInt(commands[i + 1], 16); // 0 hidden, 1 shown
                        break;
                    }
                    case '43': {
                        command_len = 1;
                        data.humidityVisibility = parseInt(commands[i + 1], 16); // 0 hidden, 1 shown
                        break;
                    }
                    case '45': {
                        command_len = 1;
                        data.fanSpeed = parseInt(commands[i + 1], 16); // 0 Auto .. 6 High
                        break;
                    }
                    case '47': {
                        command_len = 1;
                        // 0 L/M/H, 1 L/M, 2 L, 3 deactivated
                        data.fanSpeedLimit = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '49': {
                        command_len = 2;
                        data.ecmVoltageMin = parseInt(commands[i + 1], 16) / 10; // V
                        data.ecmVoltageMax = parseInt(commands[i + 2], 16) / 10; // V
                        break;
                    }
                    case '4b': {
                        command_len = 1;
                        data.ecmStartUpTime = parseInt(commands[i + 1], 16); // seconds
                        break;
                    }
                    case '4d': {
                        command_len = 1;
                        data.ecmRelay = parseInt(commands[i + 1], 16); // 0/1
                        break;
                    }
                    case '4f': {
                        command_len = 1;
                        data.frostProtection = parseInt(commands[i + 1], 16); // 0/1
                        break;
                    }
                    case '51': {
                        command_len = 2;
                        data.frostProtThreshold = parseInt(commands[i + 1], 16); // degC
                        data.frostProtSetpoint  = parseInt(commands[i + 2], 16); // degC
                        break;
                    }
                    case '53': {
                        command_len = 1;
                        data.operationalMode = parseInt(commands[i + 1], 16); // 0/1/2
                        break;
                    }
                    case '55': {
                        command_len = 1;
                        // 0 V/H/C, 1 V/H, 2 V/C
                        data.allowedOperModes = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '57': {
                        command_len = 1;
                        data.coolingSetpointUnocc = parseInt(commands[i + 1], 16); // degC
                        break;
                    }
                    case '59': {
                        command_len = 1;
                        data.heatingSetpointUnocc = parseInt(commands[i + 1], 16); // degC
                        break;
                    }
                    case '5b': {
                        command_len = 2; // FIX D7: single signed value in degC
                        var sign = parseInt(commands[i + 1], 16) === 1 ? -1 : 1;
                        data.tempCompensation = sign * parseInt(commands[i + 2], 16) / 10;
                        break;
                    }
                    case '5d': {
                        command_len = 1;
                        // 0 Low, 1 Automatic, 2 Do not change
                        data.fanSpeedUnocc = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '5f': {
                        command_len = 1;
                        data.autoChangeover = parseInt(commands[i + 1], 16); // 0/1
                        break;
                    }
                    case '61': {
                        command_len = 1;
                        data.application = parseInt(commands[i + 1], 16); // wiring diagram 0-6
                        break;
                    }
                    case '63': {
                        command_len = 1;
                        data.occFunction = parseInt(commands[i + 1], 16); // 0-10
                        break;
                    }
                    case '65': {
                        command_len = 2;
                        data.changeoverCoolThr = parseInt(commands[i + 1], 16); // degC
                        data.changeoverHeatThr = parseInt(commands[i + 2], 16); // degC
                        break;
                    }
                    case '67': {
                        command_len = 1;
                        data.deviceStatus = parseInt(commands[i + 1], 16); // 0/1
                        break;
                    }
                    case '69': {
                        command_len = 1;
                        // 0 last status, 1 on, 2 off
                        data.powerRecovery = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '6b': {
                        command_len = 1;
                        data.deltaT1 = parseInt(commands[i + 1], 16) / 10; // degC
                        break;
                    }
                    case '6d': {
                        command_len = 2; // FIX D3: /10 (was *10)
                        data.deltaT2 = parseInt(commands[i + 1], 16) / 10; // degC
                        data.deltaT3 = parseInt(commands[i + 2], 16) / 10; // degC
                        break;
                    }
                    case '6e': {
                        command_len = 1;
                        data.frostProtStatus = parseInt(commands[i + 1], 16); // 0/1 running
                        break;
                    }
                    case '70': {
                        command_len = 1;
                        // 0 occupied, 1 unoccupied (set-point decrease)
                        data.occStatusSetpoint = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '71': {
                        command_len = 1;
                        // 0 occupied, 1 unoccupied (fan off, valve closed)
                        data.occStatusFanValve = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '72': {
                        command_len = 1;
                        data.dewPointStatus = parseInt(commands[i + 1], 16); // 0/1 reached
                        break;
                    }
                    case '73': {
                        command_len = 1;
                        data.filterAlarm = parseInt(commands[i + 1], 16); // 0/1
                        break;
                    }
                    case '74': {
                        command_len = 2;
                        // NTC temp 1degC res; FW>=2.1 sends 0xFF on NTC fault
                        data.changeoverNtcTemp  = parseInt(commands[i + 1], 16);
                        // 0 threshold not reached, 1 heating, 2 cooling
                        data.changeoverModeFlag = parseInt(commands[i + 2], 16);
                        break;
                    }
                    case '75': {
                        command_len = 1;
                        data.powerModuleStatus = parseInt(commands[i + 1], 16); // 0 OK, 1 error
                        break;
                    }
                    case '77': {
                        command_len = 4;
                        data.heatMinUnocc = parseInt(commands[i + 1], 16);
                        data.heatMaxUnocc = parseInt(commands[i + 2], 16);
                        data.coolMinUnocc = parseInt(commands[i + 3], 16);
                        data.coolMaxUnocc = parseInt(commands[i + 4], 16);
                        break;
                    }
                    case '79': {
                        command_len = 1;
                        data.fanOffDelayTime = parseInt(commands[i + 1], 16); // minutes
                        break;
                    }
                    case '7b': {
                        command_len = 1;
                        // 0 off at target, 1 keep running for a time, 2 continuous
                        data.additionalFanMode = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '7c': {
                        command_len = 1;
                        // 0 none, 1 detection, 2 init, 3 measurement
                        data.intTempSensorError = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '7d': {
                        command_len = 1;
                        // 0 none, 1 open, 2 short, 3 internal module, 4 measurement
                        data.extTempSensorError = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case '9b': {
                        command_len = 1;
                        // 0 EN, 1 FR, 2 DE (FW>=2.1), 3 ES (FW>=2.1)
                        data.uiLanguage = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case 'a4': {
                        command_len = 1; // FIX D8 (was missing)
                        // 0 EU868, 1 AS923, 2 AU915, 3 US915
                        data.lorawanRegion = parseInt(commands[i + 1], 16);
                        break;
                    }
                    case 'a0': {
                        command_len = 4;
                        data.fuotaAddress =
                            (parseInt(commands[i + 1], 16) << 24) |
                            (parseInt(commands[i + 2], 16) << 16) |
                            (parseInt(commands[i + 3], 16) << 8)  |
                             parseInt(commands[i + 4], 16);
                        break;
                    }
                    default:
                        break;
                }
                commands.splice(i, command_len);
            });
            return data;
        }

        if (bytes[0] === 1) {
            data = handleKeepalive(bytes, data);
        } else {
            data = handleResponse(bytes, data);
            bytes = bytes.slice(-11);
            data = handleKeepalive(bytes, data);
        }
        return { data: data };
    } catch (e) {
        throw new Error('Unhandled data');
    }
}

Payload Encoder Function

Object Mapping Function (JSON Function)

Last updated

Was this helpful?