⬆️CO2 Sensor Uplink Decoder

Universal Decoder:

// 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 = {};
        
        const calculateTemperature = (rawData) => (rawData - 400) / 10;
        const calculateHumidity = (rawData) => (rawData * 100) / 256;
        const calculateVoltage = (rawData) => ((rawData * 8) + 1600) / 1000;
        
        function decbin(number) {
            return number.toString(2).padStart(8, '0');
        }

        function handleKeepalive(bytes) {
            let data = {};

            let temperatureRaw = (bytes[1] << 8) | bytes[2];
            data.sensorTemperature = Number(calculateTemperature(temperatureRaw).toFixed(2));

            data.relativeHumidity = Number(calculateHumidity(bytes[3]).toFixed(2));
            
            let batteryVoltageRaw = (bytes[4] >> 4) & 0x0F;
            data.batteryVoltage = Number((2 + batteryVoltageRaw * 0.1).toFixed(2));
            
            data.thermistorProperlyConnected = (bytes[5] & 0x20) === 0;

            let extT1 = bytes[5] & 0x0F;
            let extT2 = bytes[6];
            data.extThermistorTemperature = data.thermistorProperlyConnected 
                ? Number(((extT1 << 8 | extT2) * 0.1).toFixed(2))
                : 0;
            
            return data;
        }

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

            commands.forEach((command, i) => {
                switch (command) {
                    case '04':
                        command_len = 2;
                        data.deviceVersions = {
                            hardware: Number(commands[i + 1]),
                            software: Number(commands[i + 2])
                        };
                        break;
                    case '12':
                        command_len = 1;
                        data.keepAliveTime = parseInt(commands[i + 1], 16);
                        break;
                    case '19':
                        command_len = 1;
                        let commandResponse = parseInt(commands[i + 1], 16);
                        data.joinRetryPeriod = (commandResponse * 5) / 60;
                        break;
                    case '1b':
                        command_len = 1;
                        data.uplinkType = parseInt(commands[i + 1], 16);
                        break;
                    case '1d':
                        command_len = 2;
                        data.watchDogParams = {
                            wdpC: commands[i + 1] === '00' ? false : parseInt(commands[i + 1], 16),
                            wdpUc: commands[i + 2] === '00' ? false : parseInt(commands[i + 2], 16)
                        };
                        break;
                    case '1f':
                        command_len = 4;
                        data.boundaryLevels = {
                            good_medium: parseInt(commands[i + 1] + commands[i + 2], 16),
                            medium_bad: parseInt(commands[i + 3] + commands[i + 4], 16)
                        };
                        break;
                    case '21':
                        command_len = 2;
                        data.autoZeroValue = parseInt(commands[i + 1] + commands[i + 2], 16);
                        break;
                    case '25':
                        command_len = 3;
                        data.measurementPeriod = {
                            good_zone: parseInt(commands[i + 1], 16),
                            medium_zone: parseInt(commands[i + 2], 16),
                            bad_zone: parseInt(commands[i + 3], 16)
                        };
                        break;
                    case '27':
                        command_len = 9;
                        data.buzzerNotification = {
                            duration_good_beeping: parseInt(commands[i + 1], 16),
                            duration_good_loud: parseInt(commands[i + 2], 16) * 10,
                            duration_good_silent: parseInt(commands[i + 3], 16) * 10,
                            duration_medium_beeping: parseInt(commands[i + 4], 16),
                            duration_medium_loud: parseInt(commands[i + 5], 16) * 10,
                            duration_medium_silent: parseInt(commands[i + 6], 16) * 10,
                            duration_bad_beeping: parseInt(commands[i + 7], 16),
                            duration_bad_loud: parseInt(commands[i + 8], 16) * 10,
                            duration_bad_silent: parseInt(commands[i + 9], 16) * 10
                        };
                        break;
                    case '29':
                        command_len = 15;
                        data.ledNotification = {
                            red_good: parseInt(commands[i + 1], 16),
                            green_good: parseInt(commands[i + 2], 16),
                            blue_good: parseInt(commands[i + 3], 16),
                            duration_good: parseInt(commands[i + 4] + commands[i + 5], 16) * 10,
                            red_medium: parseInt(commands[i + 6], 16),
                            green_medium: parseInt(commands[i + 7], 16),
                            blue_medium: parseInt(commands[i + 8], 16),
                            duration_medium: parseInt(commands[i + 9] + commands[i + 10], 16) * 10,
                            red_bad: parseInt(commands[i + 11], 16),
                            green_bad: parseInt(commands[i + 12], 16),
                            blue_bad: parseInt(commands[i + 13], 16),
                            duration_bad: parseInt(commands[i + 14] + commands[i + 15], 16) * 10
                        };
                        break;
                    case '2b':
                        command_len = 1;
                        data.autoZeroPeriod = parseInt(commands[i + 1], 16);
                        break;
                    default:
                        throw new Error('Unhandled data');
                }
                commands.splice(i, command_len);
            });
            return data;
        }

        if (bytes[0] === 1) {
            data = handleKeepalive(bytes);
        } else {
            data = handleResponse(bytes, data);
            bytes = bytes.slice(-7);
            data = handleKeepalive(bytes);
        }

        return { data: data };
    } catch (e) {
        throw new Error(e);
    }
}

Last updated

Was this helpful?