Base64 Payload Decoder

base64decoderprotocolutilitybasic

Decodes Base64 encoded payload data into readable sensor values using Buffer utilities

javascriptbase64-decoder.js
/**
 * This snippet decodes Base64 encoded payloads commonly used by various IoT protocols.
 * Some devices send sensor data encoded in Base64 format which needs to be decoded
 * before parsing the binary data.
 *
 * Testing:
 * You can test this with the Device Emulator using:
 * [{ "variable": "data", "value": "AQlhE5UA359" }]
 */

// Find Base64 encoded payload
const payload_raw = payload.find((x) => x.variable === "data" || x.variable === "payload");

if (payload_raw) {
  try {
    // Decode Base64 data using Buffer
    const buffer = Buffer.from(payload_raw.value, "base64");

    // Example parsing (adjust based on your device's data format)
    // Let's assume:
    // Byte 0: Device ID
    // Bytes 1-2: Temperature (signed, divide by 100)
    // Byte 3: Battery level
    const data = [
      { variable: "device_id", value: buffer.readUInt8(0) },
      { variable: "temperature", value: buffer.readInt16BE(1) / 100, unit: "°C" },
      { variable: "battery", value: buffer.readUInt8(3), unit: "%" },
    ];

    // Add to payload with group and time
    const group = payload_raw.group || String(Date.now());
    const time = payload_raw.time;

    const newData = data.map((item) => ({
      ...item,
      group,
      ...(time && { time }),
    }));

    payload = payload.concat(newData);
  } catch (e) {
    // Print the error to the Live Inspector
    console.error("Base64 decode error:", e.message);

    // Add error variable for debugging
    payload.push({
      variable: "parse_error",
      value: `Base64 decode failed: ${e.message}`,
      group: String(Date.now()),
    });
  }
}

Bitwise Operations Parser

bitwisebinarycompactsensorbasic

Parse binary data using bitwise operations for compact sensor protocols

javascriptbitwise-operations-parser.js
/**
 * This snippet demonstrates parsing compact binary protocols where multiple
 * sensor values are packed into bytes using bitwise operations.
 *
 * Example format (5 bytes):
 * - Byte 0: Device ID
 * - Byte 1: Status flags (8 bits)
 * - Bytes 2-3: Temperature (16 bits)
 * - Byte 4: Battery level
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "data", "value": "FF8A5C7F80" }]
 */

// Find hexadecimal payload
const payload_raw = payload.find((x) => x.variable === "data" || x.variable === "payload");

if (payload_raw) {
  try {
    // Convert hex to buffer
    const buffer = Buffer.from(payload_raw.value, "hex");

    if (buffer.length >= 5) {
      // Parse device ID (byte 0)
      const deviceId = buffer.readUInt8(0);

      // Parse status flags (byte 1) - extract individual bits
      const statusByte = buffer.readUInt8(1);
      const alarmActive = (statusByte & 0x80) !== 0; // bit 7
      const lowBattery = (statusByte & 0x40) !== 0; // bit 6
      const motionDetected = (statusByte & 0x20) !== 0; // bit 5

      // Parse temperature (bytes 2-3, signed 16-bit)
      const temperatureRaw = buffer.readInt16BE(2);
      const temperature = temperatureRaw / 100.0; // Scale factor

      // Parse battery level (byte 4)
      const batteryLevel = buffer.readUInt8(4);

      // Build the parsed data
      const data = [
        { variable: "device_id", value: deviceId },
        { variable: "temperature", value: temperature, unit: "°C" },
        { variable: "battery", value: batteryLevel, unit: "%" },
        { variable: "alarm", value: alarmActive ? 1 : 0 },
        { variable: "low_battery_flag", value: lowBattery ? 1 : 0 },
        { variable: "motion", value: motionDetected ? 1 : 0 },
      ];

      // Add to payload with group and time
      const group = payload_raw.group || String(Date.now());
      const time = payload_raw.time;

      const newData = data.map((item) => ({
        ...item,
        group,
        ...(time && { time }),
      }));

      payload = payload.concat(newData);
    } else {
      console.log(`Invalid payload length: expected 5 bytes, got ${buffer.length}`);
    }
  } catch (e) {
    console.error("Bitwise parsing error:", e.message);

    payload.push({
      variable: "parse_error",
      value: `Parsing failed: ${e.message}`,
      group: String(Date.now()),
    });
  }
}

Data Validation and Error Handling

validationerror-handlingsensorutilitybasic

Validates sensor data ranges and handles invalid values

javascriptdata-validation.js
/**
 * This snippet validates sensor data against expected ranges.
 * Invalid data is flagged with error messages.
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "temperature", "value": -50 }, { "variable": "humidity", "value": 150 }]
 */

// Define valid ranges for common sensors
const SENSOR_RANGES = {
  temperature: { min: -40, max: 85, unit: "°C" },
  humidity: { min: 0, max: 100, unit: "%" },
  battery: { min: 0, max: 100, unit: "%" },
  pressure: { min: 300, max: 1100, unit: "hPa" },
};

// Validate each item in the payload
for (const item of payload) {
  if (item.variable && typeof item.value === "number") {
    const range = SENSOR_RANGES[item.variable];

    if (range) {
      // Check if value is within valid range
      if (item.value < range.min || item.value > range.max) {
        // Add error for out-of-range values
        payload.push({
          variable: `${item.variable}_error`,
          value: `Value ${item.value} outside range ${range.min}-${range.max}`,
          group: item.group || String(Date.now()),
        });

        console.log(`Validation error: ${item.variable} = ${item.value} (expected: ${range.min}-${range.max})`);
      } else {
        // Add unit if not present
        if (!item.unit) {
          item.unit = range.unit;
        }
      }
    }
  }
}

JSON to TagoIO Format Converter

jsonconverterformatutilitybasic

Converts raw JSON data to TagoIO format with support for nested objects and metadata

javascriptjson-to-tago-format.js
/* What does this snippet do?
 ** It simply converts raw JSON to formatted TagoIO JSON.
 ** So if you send { "temperature": 10 }
 ** This script will convert it to { "variable": "temperature", "value": 10 }
 **
 ** The ignore_vars variable in this code should be used to ignore variables
 ** from the device that you don't want.
 */
// Add ignorable variables in this array.
const ignore_vars = [];

/**
 * Convert an object to TagoIO object format.
 * Can be used in two ways:
 * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... })
 * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... })
 *
 * @param {Object} object_item Object containing key and value.
 * @param {String} group Group for the variables
 * @param {String} prefix Add a prefix to the variable names
 */
function toTagoFormat(object_item, group, prefix = "") {
  const result = [];
  for (const key in object_item) {
    if (ignore_vars.includes(key)) continue;

    if (typeof object_item[key] === "object") {
      result.push({
        variable: object_item[key].variable || `${prefix}${key}`,
        value: object_item[key].value,
        group: object_item[key].group || group,
        metadata: object_item[key].metadata,
        location: object_item[key].location,
        unit: object_item[key].unit,
      });
    } else {
      result.push({
        variable: `${prefix}${key}`,
        value: object_item[key],
        group,
      });
    }
  }

  return result;
}

// Check if what is being stored is the ttn_payload.
// Payload is an environment variable. Is where what is being inserted to your device comes in.
if (!payload[0].variable) {
  // Get a unique group for the incoming data.
  const group = payload[0].group || String(Date.now());

  payload = toTagoFormat(payload[0], group);
}

LoRaWAN Hexadecimal Payload Parser

lorawanhexadecimalbufferprotocolbasic

Generic payload parser for LoRaWAN devices compatible with any network server

javascriptlorawan-hexadecimal-parser.js
/* This is a generic payload parser for LoRaWAN. It will work for any network server.
 ** The code finds the "payload" variable sent by your sensor and parses it if it exists.
 ** The content of the payload variable is always a hexadecimal value.
 **
 ** Note: Additional variables can be created by the Network Server and sent directly to the bucket. Normally they aren't handled here.
 **
 ** Testing:
 ** You can do manual tests to the parser by using the Device Emulator. Copy and paste the following JSON:
 ** [{ "variable": "data", "value": "0109611395" }]
 */

// Search for the payload variable in the payload global variable. Its contents are always [{ variable, value...}, {variable, value...} ...]
const payload_raw = payload.find(
  (x) => x.variable === "payload_raw" || x.variable === "payload" || x.variable === "data"
);
if (payload_raw) {
  try {
    // Convert the data from hexadecimal to JavaScript Buffer
    const buffer = Buffer.from(payload_raw.value, "hex");

    // Let's say you have a payload of 5 bytes:
    // 0 - Protocol Version
    // 1,2 - Temperature
    // 3,4 - Humidity
    // More information about buffers can be found here: https://nodejs.org/api/buffer.html
    const data = [
      { variable: "protocol_version", value: buffer.readInt8(0) },
      {
        variable: "temperature",
        value: buffer.readInt16BE(1) / 100,
        unit: "°C",
      },
      { variable: "humidity", value: buffer.readUInt16BE(3) / 100, unit: "%" },
    ];

    // This will concatenate the content sent by your device with the content generated in this payload parser.
    // It also adds the "group" and "time" fields to it, copying from your sensor data.
    payload = payload.concat(
      data.map((x) => ({
        ...x,
        group: String(payload_raw.serie || payload_raw.group),
        time: String(payload_raw.time),
      }))
    );
  } catch (e) {
    // Print the error to the Live Inspector.
    console.error(e);

    // Return the variable parse_error for debugging.
    payload = [{ variable: "parse_error", value: e.message }];
  }
}

MQTT Comma-Separated Values Parser

mqttcsvcomma-separatedparserbasic

Enhanced parser for MQTT devices sending comma-separated data

javascriptmqtt-comma-separated-parser.js
/**
 * This parser handles MQTT devices sending comma-separated data.
 * It supports both key-value pairs and positional data formats.
 *
 * Supported formats:
 * - "temp,12,hum,50" (alternating key-value)
 * - "25.5,60,85" (positional values)
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "payload", "value": "temp,12,hum,50", "metadata": { "mqtt_topic": "sensors/data" } }]
 */

// Find MQTT payload data
const mqttPayload = payload.find((data) => data.variable === "payload" || data.metadata?.mqtt_topic);

if (mqttPayload?.value) {
  try {
    const dataString = String(mqttPayload.value);
    const parts = dataString.split(",");
    const parsedData = [];

    // Check if it's alternating key-value format (even number of parts)
    if (parts.length % 2 === 0) {
      // Parse "key,value,key,value" format
      for (let i = 0; i < parts.length - 1; i += 2) {
        const variable = parts[i].trim();
        const value = Number(parts[i + 1].trim());

        // Add unit based on variable name
        let unit = null;
        if (variable.toLowerCase().includes("temp")) unit = "°C";
        else if (variable.toLowerCase().includes("hum")) unit = "%";
        else if (variable.toLowerCase().includes("batt")) unit = "%";

        parsedData.push({
          variable,
          value: Number.isNaN(value) ? parts[i + 1].trim() : value,
          ...(unit && { unit }),
        });
      }
    } else {
      // Positional format - assume common sensor order
      const mapping = ["temperature", "humidity", "battery"];

      for (let i = 0; i < parts.length; i++) {
        const value = Number(parts[i].trim());
        const variable = mapping[i] || `sensor_${i + 1}`;

        parsedData.push({
          variable,
          value: Number.isNaN(value) ? parts[i].trim() : value,
          unit: i === 0 ? "°C" : i === 1 ? "%" : null,
        });
      }
    }

    // Add group and time information
    const group = mqttPayload.group || String(Date.now());
    const time = mqttPayload.time;

    const newData = parsedData.map((item) => ({
      ...item,
      group,
      ...(time && { time }),
    }));

    // Add to payload
    payload = payload.concat(newData);

    console.log(`Parsed ${parsedData.length} values from MQTT data`);
  } catch (error) {
    console.error("MQTT parsing error:", error.message);

    payload.push({
      variable: "parse_error",
      value: `MQTT parsing failed: ${error.message}`,
      group: mqttPayload.group || String(Date.now()),
    });
  }
}

MQTT Hexadecimal Payload Parser

mqtthexadecimalbufferprotocolbasic

Generic payload parser for MQTT devices sending hexadecimal data

javascriptmqtt-hexadecimal-parser.js
/* This is a generic payload parser that can be used as a starting point for MQTT devices
 ** The code expects to receive hexadecimal string data, not JSON formatted data.
 **
 ** Testing:
 ** You can do manual tests to the parser by using the Device Emulator. Copy and paste the following JSON:
 ** [{ "variable": "payload", "value": "0109611395", "metadata": { "mqtt_topic": "data" } } ]
 */

// Prevent the code from running for other types of data insertions.
// We search for a variable named "payload" or a variable with metadata.mqtt_topic
const mqtt_payload = payload.find((data) => data.variable === "payload" || data.metadata?.mqtt_topic);
if (mqtt_payload) {
  // Cast the hexadecimal string to a buffer
  const buffer = Buffer.from(mqtt_payload.value, "hex");

  // Normalize the data to TagoIO format
  // We use the Number function to cast number values, so we can use them in chart widgets, etc.
  const data = [
    { variable: "protocol_version", value: buffer.readInt8(0) },
    { variable: "temperature", value: buffer.readInt16BE(1) / 100, unit: "°C" },
    { variable: "humidity", value: buffer.readUInt16BE(3) / 100, unit: "%" },
  ];

  // This will concatenate the content sent by your device with the content generated in this payload parser
  // It also adds the field "group" to be able to group data in tables and other widgets
  const group = String(Date.now());
  payload = payload.concat(data).map((x) => ({ ...x, group }));
}

Multi-Sensor Data Aggregator

aggregationmulti-sensorcalculationsbasic

Aggregates data from multiple sensors and calculates simple derived metrics

javascriptmulti-sensor-aggregator.js
/**
 * This snippet demonstrates basic sensor data aggregation and calculations.
 * It calculates a simple heat index and comfort score from temperature and humidity.
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [
 *   { "variable": "temperature", "value": 25.5 },
 *   { "variable": "humidity", "value": 60 }
 * ]
 */

// Find sensor values from payload
let temperature = null;
let humidity = null;

for (const item of payload) {
  if (item.variable === "temperature" && typeof item.value === "number") {
    temperature = item.value;
  } else if (item.variable === "humidity" && typeof item.value === "number") {
    humidity = item.value;
  }
}

// Calculate derived metrics if we have both temperature and humidity
if (temperature !== null && humidity !== null) {
  const group = String(Date.now());

  // Simple heat index calculation (for temperatures above 20°C)
  let heatIndex = temperature;
  if (temperature > 20) {
    heatIndex = temperature + (humidity / 100) * 2; // Simplified formula
  }

  // Comfort score (0-100, higher is better)
  let comfortScore = 50; // Base score

  // Temperature comfort (20-25°C is optimal)
  if (temperature >= 20 && temperature <= 25) {
    comfortScore += 30;
  } else if (temperature >= 18 && temperature <= 28) {
    comfortScore += 15;
  }

  // Humidity comfort (40-60% is optimal)
  if (humidity >= 40 && humidity <= 60) {
    comfortScore += 20;
  } else if (humidity >= 30 && humidity <= 70) {
    comfortScore += 10;
  }

  // Add calculated values to payload
  payload.push(
    {
      variable: "heat_index",
      value: Math.round(heatIndex * 10) / 10,
      unit: "°C",
      group,
    },
    {
      variable: "comfort_score",
      value: Math.min(100, comfortScore),
      unit: "points",
      group,
    }
  );

  console.log(`Calculated heat index: ${heatIndex}°C, comfort score: ${comfortScore}`);
}

SenML (Sensor Markup Language) Parser

senmlsensorprotocolparserdayjsadvanced

Parses SenML formatted data according to RFC 8428 specification using dayjs

javascriptsenml-parser.js
/**
 * Parses the value of the reading
 *
 * Value  Value of the entry.  Optional if a Sum value is present,
 * otherwise required.  Values are represented using three basic data
 * types, Floating point numbers ("v" field for "Value"), Booleans
 * ("vb" for "Boolean Value") and Strings ("vs" for "String Value").
 * Exactly one of these three fields MUST appear.
 * @param {Object} item
 * @returns {number | boolean | string}
 */
function parseValue(item) {
  if ("vb" in item) {
    return !!item.vb;
  }

  if ("v" in item) {
    return Number(item.v);
  }

  if ("vs" in item) {
    return item.vs;
  }
}

/**
 * Parses the measurement time
 * If either the Base Time or Time value is missing, the missing
 * attribute is considered to have a value of zero.  The Base Time and
 * Time values are added together to get the time of measurement.  A
 * time of zero indicates that the sensor does not know the absolute
 * time and the measurement was made roughly "now".  A negative value is
 * used to indicate seconds in the past from roughly "now".  A positive
 * value is used to indicate the number of seconds, excluding leap
 * seconds, since the start of the year 1970 in UTC.
 * @param {dayjs.Dayjs | Date} curr_time current time when this code is running
 * @param {Object} item measurement object
 * @returns {dayjs.Dayjs}
 */
function parseTime(curr_time = dayjs(), item) {
  if (!item.t) {
    return dayjs(curr_time).toISOString();
  }

  if (Number(item.t) < 0) {
    return dayjs(curr_time)
      .subtract(item.t * -1, "seconds")
      .toISOString();
  }

  return dayjs(curr_time).add(item.t, "seconds").toISOString();
}

/**
 * Removes unaccepted parameters from the variable name
 * @param {string} variable
 */
function parseVariable(variable) {
  const variableParsed = variable.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>{}[\]\\/]/gi, "");
  if (!variableParsed) {
    return "measurement";
  }
  return variableParsed;
}

/**
 *
 * @param {Object[]} senml_obj
 */
function decoder(senml_obj) {
  const toTagoJSON = [];
  const serie = String(Date.now());

  let curr_time = dayjs();
  let base_unit;
  let base_name;
  for (const item of senml_obj) {
    if (item.bt) {
      curr_time = dayjs(item.bt, "X");
    }
    if (item.bn) {
      base_name = item.bn;
    }
    if (item.bu) {
      base_unit = item.bu;
    }

    const itemTago = {
      variable: parseVariable(item.n || base_name),
      unit: item.u || base_unit,
      value: parseValue(item),
      time: parseTime(curr_time, item),
      serie,
      group: serie,
    };

    toTagoJSON.push(itemTago);
  }

  return toTagoJSON;
}

try {
  if (Array.isArray(payload) && payload[0].bn) {
    payload = decoder(payload);
  }
} catch (e) {
  console.log(e.message);
}

Sigfox Hexadecimal Payload Parser

sigfoxhexadecimalbuffergpscoordinatesadvanced

Generic payload parser for Sigfox devices with GPS coordinates and sensor data

javascriptsigfox-hexadecimal-parser.js
/* This is a generic payload parser that can be used as a starting point for Sigfox devices.
 ** The code finds the "data" variable sent by your sensor and parses it if it exists.
 ** The content of the value from the "data" variable is always a hexadecimal value.
 **
 ** Testing:
 ** You can do manual tests of the parser by using the Device Emulator. Copy and paste the following JSON:
 ** [{ "variable": "data", "value": "0109611395000DF9011EB9" }]
 */

// Search for the payload variable in the global payload variable. Its contents are always [{ variable, value...}, {variable, value...} ...]
const payload_raw = payload.find((x) => x.variable === "data");

// Check if payload_raw exists
if (payload_raw) {
  try {
    // Convert the data from hexadecimal to JavaScript Buffer
    const buffer = Buffer.from(payload_raw.value, "hex");

    // Let's say you have a payload of 11 bytes:
    // 0 - Counter (1 byte, 0 - 255)
    // 1,2 - Temperature (multiplied by 100, unit = Celsius)
    // 3,4 - Humidity (multiplied by 100, unit = Percent)
    // 5 - Latitude indicator: 00 = positive | 01 = negative
    // 6,7 - (Latitude value * 10000) / 1000000
    // 8 - Longitude indicator: 00 = positive | 01 = negative
    // 9,10 - (Longitude value * 10000) / 1000000
    // More information about buffers can be found here: https://nodejs.org/api/buffer.html

    // Latitude indicator
    const lat_indicator = buffer.readInt8(5);
    // Longitude indicator
    const lng_indicator = buffer.readInt8(8);

    // Latitude value
    let lat = (buffer.readUInt16BE(6) * 10022) / 1000000;

    // Apply indicator rule: if 0, it's positive; if 1, it's negative
    lat = lat_indicator === 0 ? lat : -lat;

    // Longitude value
    let lng = (buffer.readUInt16BE(9) * 10022) / 1000000;

    // Apply indicator rule: if 0, it's positive; if 1, it's negative
    lng = lng_indicator === 0 ? lng : -lng;

    const data = [
      { variable: "counter", value: buffer.readInt8(0) },
      { variable: "temperature", value: buffer.readInt16BE(1) / 100, unit: "°C" },
      { variable: "humidity", value: buffer.readUInt16BE(3) / 100, unit: "%" },
      { variable: "location", value: `${lat}, ${lng}`, location: { lat, lng } },
    ];

    // This will concatenate the content sent by your device with the content generated in this payload parser.
    // It also adds the "group" and "time" fields to it, copying from your sensor data.
    payload = payload.concat(
      data.map((x) => ({
        ...x,
        group: payload_raw.serie || payload_raw.group,
        time: payload_raw.time,
      }))
    );
  } catch (e) {
    // Print the error to the Live Inspector.
    console.error(e);

    // Return the variable parse_error for debugging.
    payload = [{ variable: "parse_error", value: e.message }];
  }
}

String Payload Parser

stringparserdelimiterkey-valuebasic

Parse string-based payload with delimiters and key-value pairs

javascriptstring-payload-parser.js
/**
 * This snippet parses string-based payloads with various delimiters.
 * Common formats include:
 * - "temp:25.5;humidity:60;battery:80"
 * - "temp=25.5&humidity=60&battery=80"
 * - "25.5|60|80" (positional values)
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "payload", "value": "temp:25.5;humidity:60;battery:80" }]
 */

// Configuration: adjust these settings based on your device's format
const PAIR_DELIMITER = ";"; // Separator between key-value pairs
const KEY_VALUE_DELIMITER = ":"; // Separator between key and value
const POSITIONAL_DELIMITER = "|"; // Delimiter for positional data

// Mapping for positional data (when no keys are provided)
const POSITIONAL_MAPPING = [
  { variable: "temperature", unit: "°C" },
  { variable: "humidity", unit: "%" },
  { variable: "battery", unit: "%" },
  { variable: "signal", unit: "dBm" },
];

// Helper function to trim whitespace
function trim(str) {
  return str.replace(/^\s+|\s+$/g, "");
}

// Helper function to convert a value to number if possible
function parseValue(value) {
  const num = Number(value);
  return Number.isNaN(num) ? value : num;
}

// Find string payload in the data
const payload_raw = payload.find((x) => x.variable === "payload" || x.variable === "data" || x.variable === "message");

if (payload_raw && typeof payload_raw.value === "string") {
  const data = [];
  const payloadValue = payload_raw.value;

  // Check if it's key-value pairs format
  if (payloadValue.includes(KEY_VALUE_DELIMITER)) {
    // Parse key-value pairs
    const pairs = payloadValue.split(PAIR_DELIMITER);

    for (const pair of pairs) {
      const parts = pair.split(KEY_VALUE_DELIMITER);
      if (parts.length >= 2) {
        const key = trim(parts[0]);
        const value = trim(parts.slice(1).join(KEY_VALUE_DELIMITER)); // Handle values with delimiters

        // Determine unit based on variable name
        let unit = null;
        const lowerKey = key.toLowerCase();
        if (lowerKey.includes("temp")) {
          unit = "°C";
        } else if (lowerKey.includes("humid")) {
          unit = "%";
        } else if (lowerKey.includes("batt")) {
          unit = "%";
        } else if (lowerKey.includes("signal") || lowerKey.includes("rssi")) {
          unit = "dBm";
        }

        data.push({
          variable: key,
          value: parseValue(value),
          ...(unit && { unit }),
        });
      }
    }

    // Check if it's positional format
  } else if (payloadValue.includes(POSITIONAL_DELIMITER)) {
    const values = payloadValue.split(POSITIONAL_DELIMITER);

    values.forEach((value, index) => {
      const trimmedValue = trim(value);
      const mapping = POSITIONAL_MAPPING[index];

      if (mapping) {
        data.push({
          variable: mapping.variable,
          value: parseValue(trimmedValue),
          unit: mapping.unit,
        });
      } else {
        // Fallback for unmapped positions
        data.push({
          variable: `sensor_${index + 1}`,
          value: parseValue(trimmedValue),
        });
      }
    });

    // Single value format
  } else {
    // Assume it's a single temperature value
    data.push({
      variable: "temperature",
      value: parseValue(payloadValue),
      unit: "°C",
    });
  }

  // Add to payload with group and time
  const group = payload_raw.group || String(Date.now());
  const time = payload_raw.time;

  const newData = data.map((item) => ({
    ...item,
    group,
    ...(time && { time }),
  }));

  payload = payload.concat(newData);
}

Temperature Fahrenheit to Celsius Converter

temperatureconversionfahrenheitcelsiusbasic

Converts temperature values from Fahrenheit to Celsius with validation

javascripttemperature-fahrenheit-to-celsius.js
/**
 * This snippet converts temperature values from Fahrenheit to Celsius.
 * It includes basic validation and supports multiple temperature variables.
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "temperature", "value": 68 }]
 */

// Find temperature variables in the payload
const temperatureItems = payload.filter(
  (item) => item.variable?.toLowerCase().includes("temp") && typeof item.value === "number"
);

for (const item of temperatureItems) {
  // Validate temperature range (reasonable Fahrenheit values)
  if (item.value >= -40 && item.value <= 140) {
    // Convert from Fahrenheit to Celsius
    const celsius = ((item.value - 32) * 5) / 9;

    // Update the item
    item.value = Math.round(celsius * 100) / 100; // Round to 2 decimal places
    item.unit = "°C";

    console.log(`Converted ${item.variable}: ${item.value}°C`);
  } else {
    // Add error for invalid temperatures
    payload.push({
      variable: `${item.variable}_error`,
      value: "invalid_temperature_range",
      metadata: { original_value: item.value },
      group: item.group || String(Date.now()),
    });
  }
}

Time-Based Data Filter

filtertimedayjsutilitybasic

Filter data based on time of day using dayjs

javascripttime-based-filter.js
/**
 * This snippet filters data based on time of day.
 * Useful for ignoring data during maintenance hours or specific time periods.
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "humidity", "value": 65, "time": "2023-06-15T08:00:00Z" }]
 */

// Configuration
const MAINTENANCE_START_HOUR = 7; // 7 AM
const MAINTENANCE_END_HOUR = 9; // 9 AM

// Find the variable we want to filter
const sensorItem = payload.find((item) => item.variable === "humidity");

if (sensorItem?.time) {
  const itemTime = dayjs(sensorItem.time);
  const hour = itemTime.hour();

  // Filter out data during maintenance hours
  if (hour >= MAINTENANCE_START_HOUR && hour <= MAINTENANCE_END_HOUR) {
    console.log(`Filtering data at hour ${hour} - maintenance time`);

    // Add a filtered notification
    payload.push({
      variable: "filtered_data",
      value: `${sensorItem.variable} filtered during maintenance`,
      group: String(Date.now()),
    });

    // Remove the original item
    const index = payload.indexOf(sensorItem);
    payload.splice(index, 1);
  }
}

Timezone Data Processor

timezonedayjstimestampconversionbasic

Convert timestamps between timezones using dayjs and timeUtils

javascripttimezone-data-processor.js
/**
 * This snippet demonstrates timezone conversion using TagoIO's timeUtils and dayjs.
 * It converts timestamps to different timezones and formats them.
 *
 * Testing:
 * You can test with the Device Emulator using:
 * [{ "variable": "timestamp", "value": "2023-06-15T14:30:00.000Z" }]
 */

// Find timestamp data in payload
const timestampItem = payload.find(
  (item) => item.variable?.toLowerCase().includes("timestamp") || item.variable?.toLowerCase().includes("time")
);

if (timestampItem?.value) {
  try {
    const originalTime = timestampItem.value;
    const group = timestampItem.group || String(Date.now());

    // Use dayjs for basic formatting
    const dayjsTime = dayjs(originalTime);

    // Add formatted timestamp using dayjs
    payload.push({
      variable: "formatted_time",
      value: dayjsTime.format("YYYY-MM-DD HH:mm:ss"),
      group,
    });

    // Use timeUtils for timezone conversion (if available)
    if (typeof timeUtils !== "undefined") {
      try {
        // Convert to New York timezone
        const nyTime = timeUtils.formatInTimezone(originalTime, "America/New_York", "%Y-%m-%d %H:%M:%S %z");
        payload.push({
          variable: "time_ny",
          value: nyTime,
          group,
        });

        // Convert to Tokyo timezone
        const tokyoTime = timeUtils.formatInTimezone(originalTime, "Asia/Tokyo", "%Y-%m-%d %H:%M:%S %z");
        payload.push({
          variable: "time_tokyo",
          value: tokyoTime,
          group,
        });
      } catch (conversionError) {
        console.log("Timezone conversion error:", conversionError.message);
      }
    }

    console.log(`Processed timestamp: ${originalTime}`);
  } catch (error) {
    console.error("Timestamp processing error:", error.message);

    payload.push({
      variable: "timestamp_error",
      value: `Processing failed: ${error.message}`,
      group: String(Date.now()),
    });
  }
}