Uplink Payload Formatter

Payload Formatter Script

Use the following JavaScript code as the custom payload formatter script.

Uplink Payload Formatter
// Evolion Research Uplink Payload Formatter Version 1.3.4
function decodeUplink(input) {
    let addFraction = (n, d) => n / (10 ** d);
    let decodeReadingId = (d) => d >> 2;
    let decodeStatisticsId = (d) => d & 0x03;

    let data = {};

    // Events
    const events = {
        2: "telemetry",
        3: "telemetry alert",
        4: "telemetry log"
    };

    // Reading structures (type, signed, fraction)
    const readingStructures = {
        0: ["null", false, 0],
        1: ["temperature", true, 4],
        2: ["relative_humidity", false, 4],
        3: ["pressure", false, 4],
        4: ["gas_estimation", false, 4],
        5: ["iaq_index", false, 4],
        6: ["voc", false, 4],
        7: ["nox_index", false, 2],
        8: ["co2_concentration", false, 2],
        9: ["particulate_matter", false, 2],
        10: ["dew_point", true, 4],
        11: ["vpd", false, 4],
        12: ["visible_light", false, 0],
        13: ["ir_light", false, 0],
        14: ["uv_index", false, 4],
        15: ["light_custom", false, 0],
        16: ["angle", true, 4],
        17: ["vibration", false, 4],
        18: ["rpm", false, 2],
        19: ["speed", false, 4],
        20: ["latitude", true, 4],
        21: ["longitude", true, 4],
        22: ["altitude", true, 2],
        23: ["heading", true, 4],
        24: ["weight", false, 0],
        25: ["distance", false, 0],
        26: ["contact", false, 0],
        27: ["movement_detection", false, 0],
        28: ["wind_speed", false, 4],
        29: ["wind_direction", true, 4],
        30: ["precipitation", false, 2],
        31: ["soil_moisture", false, 4],
        32: ["water_flow", false, 0],
        33: ["water_conductivity", false, 0],
        34: ["ph", false, 4],
        35: ["salinity", false, 4],
        36: ["tds", false, 4],
        37: ["water_custom_1", false, 0],
        38: ["water_custom_2", false, 0],
        39: ["water_custom_3", false, 0],
        40: ["voltage", false, 4],
        41: ["current", false, 2],
        42: ["frequency", false, 4],
        43: ["real_power", false, 2],
        44: ["apparent_power", false, 2],
        45: ["reactive_power", false, 2],
        46: ["power_factor", false, 4],
        47: ["phase_angle", true, 4],
        48: ["custom_1", false, 0],
        49: ["custom_2", false, 0],
        50: ["custom_3", false, 0],
        51: ["custom_4", false, 0],
        52: ["sound_pressure", true, 4],
        53: ["solar_radiation", false, 2],
        54: ["probe_temperature", true, 3],
        55: ["probe_pressure", false, 2],
        56: ["uid", false, 0],
        57: ["time", false, 0],
        58: ["battery_voltage", false, 4],
        59: ["solar_voltage", false, 4],
        60: ["counter", false, 0],
        61: ["duty_cycle", false, 4],
        62: ["analog_input", false, 4],
        63: ["digital_input", false, 0]
    };

    // Three-letter statistics type abbreviations
    const statistics = {
        0: "",
        1: "_avr",
        2: "_min",
        3: "_max"
    };

    // Decode frame port
    data.event = events[input.fPort];

    // Sensor data decoding for "telemetry" and "telemetry alert" frame ports
    if (input.fPort == 2 || input.fPort == 3) {
        for (let i = 0; i < input.bytes.length; i += 4) {
            const readingId = decodeReadingId(input.bytes[i]);

            // Stop decoding if data type is null
            if (readingId == 0)
                break;

            // Decode digital input channels
            else if (readingId == 63) {
                for (let j = 0; j < 8; j++) {
                    const prop = (input.bytes[i + 3] >> j) & 1;
                    data[`di${j + 1}`] = prop == 1 ? true : false;
                }
            }

            // Decode other data types
            else {
                // Decode data type
                let prop;
                // If multiple-read data type, e.g., vlt_1, vlt_2
                if ([4,9,16,17,18,40,41,42,43,44,45,46,47,54,55,60,61,62].includes(readingId))
                {
                    for (let j = 1; j <= 64; j++) {
                        let tempProp = `${readingStructures[readingId][0]}_${j}${statistics[decodeStatisticsId(input.bytes[i])]}`;
                        if (data[tempProp] == null) {
                            prop = tempProp;
                            break;
                        }
                    }
                }
                // If single-read data type, e.g., tmp, pre
                else {
                    prop = `${readingStructures[readingId][0]}${statistics[decodeStatisticsId(input.bytes[i])]}`;
                }

                // Decode value
                const value = (input.bytes[i + 1] << 16) + (input.bytes[i + 2] << 8) + (input.bytes[i + 3]);

                // Get the fraction
                const fraction = readingStructures[readingId][2];

                // Check if the data type is signed
                if (readingStructures[readingId][1]) {
                    // Decode signed data
                    const negative = ((value >> 23) & 1) == 1 ? true : false;
                    data[prop] = addFraction(negative ? -((~(value - 1)) & 0xFFFFFF) : value, fraction);
                }
                else {
                    // Decode unsigned data
                    data[prop] = addFraction(value, fraction);
                }
            }
        }
    }

    return {
        data: data
    };
}