## Add a device profile on the Chirpstack

We need to add a device profile on the [Chirpstack](https://chirpstack.io/).

To add a device profile, you can follow next steps:

- Login to Chirpstack server.
- Go to the **Device profiles** page and click on **Add device profile** button.
- Fill the fields.
- Click on "**Payload codec**" tab, select "**JavaScript**" in "**Payload codec**" select field and copy-paste the
  following script to the "**Codec function**" raw:

  ```js
    function decodeUplink(input) {
    var bytes = input.bytes;
    var errors = [];
    var warnings = [];
    var decoded = {};
    var historyData = {};

    // Helper: parse little-endian bytes to integer
    function parseBytesToInt(bytes, index, length, signed) {
        var result = 0;
        for (var i = 0; i < length; i++) {
            result |= bytes[index + i] << (8 * i);
        }
        if (signed && (result & (1 << (length * 8 - 1)))) {
            result -= (1 << (length * 8));
        }
        return result;
    }

    // Helper: bytes to HEX string
    function bytesToHex(bytes) {
        var hex = '';
        for (var i = 0; i < bytes.length; i++) {
            var b = bytes[i].toString(16);
            hex += (b.length < 2 ? '0' : '') + b;
        }
        return hex.toUpperCase();
    }

    decoded.hexString = bytesToHex(bytes);

    for (var i = 0; i < bytes.length; ) {
        var channel_id   = bytes[i++];
        var channel_type = bytes[i++];

        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = bytes[i];
            i += 1;
        }
        // TEMPERATURE (°C)
        else if (channel_id === 0x03 && channel_type === 0x67) {
            decoded.temperature = parseBytesToInt(bytes, i, 2, false) / 10;
            i += 2;
        }
        // HUMIDITY
        else if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = bytes[i] / 2;
            i += 1;
        }
        // PIR
        else if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = bytes[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT LEVEL
        else if (channel_id === 0x06 && channel_type === 0xCB) {
            decoded.light_level = bytes[i];
            i += 1;
        }
        // CO2
        else if (channel_id === 0x07 && channel_type === 0x7D) {
            decoded.co2 = parseBytesToInt(bytes, i, 2, false);
            i += 2;
        }
        // TVOC
        else if (channel_id === 0x08 && channel_type === 0x7D) {
            decoded.tvoc = parseBytesToInt(bytes, i, 2, false);
            i += 2;
        }
        // PRESSURE
        else if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(bytes, i, 2, false) / 10;
            i += 2;
        }
        // HCHO (Formaldehyde)
        else if (channel_id === 0x0A && channel_type === 0x7D) {
            decoded.hcho = parseBytesToInt(bytes, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        else if (channel_id === 0x0B && channel_type === 0x7D) {
            decoded.pm2_5 = parseBytesToInt(bytes, i, 2, false);
            i += 2;
        }
        // PM10
        else if (channel_id === 0x0C && channel_type === 0x7D) {
            decoded.pm10 = parseBytesToInt(bytes, i, 2, false);
            i += 2;
        }
        // O3
        else if (channel_id === 0x0D && channel_type === 0x7D) {
            decoded.o3 = parseBytesToInt(bytes, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        else if (channel_id === 0x0E && channel_type === 0x01) {
            decoded.beep = bytes[i] === 1 ? "yes" : "no";
            i += 1;
        }
        // HISTORY DATA (0x20 / 0xCE)
        else if (channel_id === 0x20 && channel_type === 0xCE) {
            historyData = {};
            historyData.timestamp   = parseBytesToInt(bytes, i,      4, false);
            historyData.temperature = parseBytesToInt(bytes, i + 4,  2, false) / 10;
            historyData.humidity    = parseBytesToInt(bytes, i + 6,  2, false) / 2;
            historyData.pir         = bytes[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = bytes[i + 9] === 1;
            historyData.co2         = parseBytesToInt(bytes, i + 10, 2, false);
            historyData.tvoc        = parseBytesToInt(bytes, i + 12, 2, false);
            historyData.pressure    = parseBytesToInt(bytes, i + 14, 2, false) / 10;

            // Determine variant by remaining payload length
            var remaining = bytes.length - (i + 16);

            if (remaining >= 6) {
                // AM319 CH2O or O3 variant
                historyData.pm2_5 = parseBytesToInt(bytes, i + 16, 2, false);
                historyData.pm10  = parseBytesToInt(bytes, i + 18, 2, false);
                var extra         = parseBytesToInt(bytes, i + 20, 2, false);
                // Distinguish O3 vs HCHO by next channel sequence (heuristic: O3 values are smaller)
                if (extra < 500) {
                    historyData.o3   = extra / 100;
                } else {
                    historyData.hcho = extra / 100;
                }
                i += 22;
            } else if (remaining >= 4) {
                // AM308 variant
                historyData.pm2_5 = parseBytesToInt(bytes, i + 16, 2, false);
                historyData.pm10  = parseBytesToInt(bytes, i + 18, 2, false);
                i += 20;
            } else {
                // AM307 variant
                i += 16;
            }

            if (!decoded.history) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // Unknown channel — break to avoid infinite loop
        else {
            warnings.push(
                "Unknown channel_id=0x" + channel_id.toString(16).toUpperCase() +
                " channel_type=0x" + channel_type.toString(16).toUpperCase() +
                " at byte index " + (i - 2)
            );
            break;
        }
    }

    return {
        data:     decoded,
        errors:   errors,
        warnings: warnings
    };

  }
  
  function encodeDownlink(input) {
    return {
        bytes:    [0xE1, 0xE6, 0xFF, 0x00],
        fPort:    10,
        errors:   [],
        warnings: []
    };
  }{:copy-code}
  ```
- Click on **Submit** button.

${images.gallery(
  {
    src: 'images/main-page.webp',
    caption: 'Login to Chirpstack server.'
  },
  {
    src: 'images/add-device-profile.webp',
    caption: 'Go to the <b>Device profiles</b> page and click on <b>Add device profile</b> button.'
  },
  {
    src: 'images/create-device-profile.webp',
    caption: 'Fill the fields.'
  },
  {
    src: 'images/copy-paste-codec.webp',
    caption: 'Click on "<b>Payload codec</b>" tab, select "<b>JavaScript</b>" in "<b>Payload codec</b>" select field and copy-paste the following script to the "<b>Codec function</b>" raw.'
  }
)}

## Add device on the Chirpstack

To add a device, you can follow next steps:

- Go to the Applications page, click on your application and press Add device button.
- Fill parameters with values from the device configuration (fill in "**Join EUI**" with **APP EUI**). Then choose previously created device profile.
- Click on "Variables" tab and then on "+ Add variable" button. Fill in "**Key**" field with **ThingsBoardAccessToken** and "**Value**" field with **${device.token}**.
- Enter APP Key in Application key input. Click on Submit button to save the device.

${images.gallery(
  {
    src: 'images/create-device-step-0.webp',
    caption: 'Go to the Applications page, click on your application and press Add device button.'
  },
  {
    src: 'images/create-device-step-1.webp',
    caption: 'Fill parameters with values from the device configuration (fill in "<b>Join EUI</b>" with <b>APP EUI</b>).'
  },
  {
    src: 'images/add-device-variable.webp',
    caption: 'Click on "Variables" tab and then on "+ Add variable" button. Fill in "<b>Key</b>" field with <b>ThingsBoardAccessToken</b> and "<b>Value</b>" field with <b>${device.token}</b>. Click on Submit button to save the device.'
  },
  {
    src: 'images/add-application-key.webp',
    caption: 'Enter APP Key in Application key input. Click on Submit button to save the device.'
  }
)}

## Verify data

Navigate to the dashboard to view the telemetry and visualized data.

${note(If the event of a data transmission failure, re-read the device state through the application interface to force a refresh.)}
