⬆️MClimate CO2 Display Uplink decoder

Universal Decoder:

Supports: The Thinks Network, Milesight, DataCake, Chirpstack

// 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 toBool(value){
            return value == '1';
        }
        function calculateTemperature(rawData){
            return (rawData - 400) / 10;
        }
        function calculateHumidity(rawData){
            return (rawData * 100) / 256;
        }
        function decbin(number) {
            if (number < 0) {
                number = 0xFFFFFFFF + number + 1
            }
            number = number.toString(2);
            return "00000000".substr(number.length) + number;
        }
        function handleKeepalive(bytes, data) {
            var tempDec = (bytes[1] << 8) | bytes[2];
            var temperatureValue = calculateTemperature(tempDec);
            var humidityValue = calculateHumidity(bytes[3]);
            var batteryVoltageCalculated = ((bytes[4] << 8) | bytes[5]) / 1000;
            var temperature = temperatureValue;
            var humidity = humidityValue;
            var batteryVoltage = batteryVoltageCalculated;
            var co2Low = bytes[6]; // CO2 value in ppm, bits 7:0
            var co2High = (bytes[7] & 0xF8) >> 3; // CO2 value in ppm, bits 12:8 (bits 7:3 of byte 7)
            var ppm = (co2High << 8) | co2Low;
            var powerSourceStatus = bytes[7] & 0x07; // Last 3 bits of byte 7
            var lux = (bytes[8] << 8) | bytes[9];
            var pir = toBool(bytes[10]);
            data.sensorTemperature = Number(temperature.toFixed(2));
            data.relativeHumidity = Number(humidity.toFixed(2));
            data.batteryVoltage = Number(batteryVoltage.toFixed(2));
            data.ppm = ppm;
            data.powerSourceStatus = powerSourceStatus;
            data.lux = lux;
            data.pir = pir;
            return data;
        }

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

            commands.map(function (command, i) {
                switch (command) {
                    case '04':
                        {
                            command_len = 2;
                            var hardwareVersion = commands[i + 1];
                            var softwareVersion = commands[i + 2];
                            data.deviceVersions = { hardware: Number(hardwareVersion), software: Number(softwareVersion) };
                        }
                        break;
                    case '12':
                        {
                            command_len = 1;
                            data.keepAliveTime = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '14':
                        {
                            command_len = 1;
                            data.childLock = toBool(parseInt(commands[i + 1], 16));
                        }
                        break;
                    case '19':
                        {
                            command_len = 1;
                            var commandResponse = parseInt(commands[i + 1], 16);
                            var periodInMinutes = commandResponse * 5 / 60;
                            data.joinRetryPeriod = periodInMinutes;
                        }
                        break;
                    case '1b':
                        {
                            command_len = 1;
                            data.uplinkType = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '1d':
                        {
                            command_len = 2;
                            var wdpC = commands[i + 1] == '00' ? false : parseInt(commands[i + 1], 16);
                            var wdpUc = commands[i + 2] == '00' ? false : parseInt(commands[i + 2], 16);
                            data.watchDogParams = { wdpC: wdpC, wdpUc: wdpUc };
                        }
                        break;
                    case '1f':
                        {
                            command_len = 4;
                            var good_medium =  (parseInt(commands[i + 1], 16) << 8) | 
                            parseInt(commands[i + 2], 16);
                            var medium_bad =  (parseInt(commands[i + 3], 16) << 8) | 
                            parseInt(commands[i + 4], 16);
                            
                            data.boundaryLevels = { good_medium: Number(good_medium), medium_bad: Number(medium_bad) } ;
                        }
                        break;
                    case '21':
                        {
                            command_len = 2;
                            data.autoZeroValue = (parseInt(commands[i + 1], 16) << 8) | 
                            parseInt(commands[i + 2], 16);
                        }
                        break;
                    case '25':
                        {

                            command_len = 3;
                            var good_zone = parseInt(commands[i + 1], 16);
                            var medium_zone = parseInt(commands[i + 2], 16);
                            var bad_zone = parseInt(commands[i + 3], 16);

                            data.measurementPeriod = { good_zone: Number(good_zone), medium_zone: Number(medium_zone), bad_zone: Number(bad_zone) };
                        }
                        break;
                    case '2b':
                        {
                            command_len = 1;
                            data.autoZeroPeriod = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '34':
                        {
                            command_len = 1;
                            data.displayRefreshPeriod = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '3d':
                        {
                            command_len = 1;
                            data.pirSensorStatus = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '3f':
                        {
                            command_len = 1;
                            data.pirSensorSensitivity = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '41':
                        {
                            command_len = 1;
                            data.currentTemperatureVisibility = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '43':
                        {
                            command_len = 1;
                            data.humidityVisibility = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '45':
                        {
                            command_len = 1;
                            data.lightIntensityVisibility = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '47':
                        {
                            command_len = 1;
                            data.pirInitPeriod = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '49':
                        {
                            command_len = 1;
                            data.pirMeasurementPeriod = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '4b':
                        {
                            command_len = 1;
                            data.pirCheckPeriod = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '4d':
                        {
                            command_len = 1;
                            data.pirBlindPeriod = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '80':
                        {
                            command_len = 1;
                            data.measurementBlindTime = parseInt(commands[i + 1], 16);
                        }
                        break;
                    case '83':
                        {
                            command_len = 1;
                            var bin = decbin(parseInt(commands[i + 1], 16));
                            var chart = Number(bin[5]);
                            var digital_value = Number(bin[6]);
                            var emoji = Number(bin[7]);
                            data.imagesVisibility ={ chart: chart, digital_value: digital_value, emoji: emoji };
                        }
                        break;
                    case 'a0': {
                        command_len = 4;
                        var fuota_address = (parseInt(commands[i + 1], 16) << 24) | 
                                            (parseInt(commands[i + 2], 16) << 16) | 
                                            (parseInt(commands[i + 3], 16) << 8) | 
                                            parseInt(commands[i + 4], 16);
                        var fuota_address_raw = commands[i + 1] + commands[i + 2] + 
                                                commands[i + 3] + commands[i + 4];
                        
                        data.fuota = { fuota_address: fuota_address, fuota_address_raw: fuota_address_raw };
                        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) {
        console.log(e)
        throw new Error('Unhandled data');
    }
}

Last updated

Was this helpful?