
function messageFormatter(byteArray, port) {
return milesightDeviceDecode(byteArray);
}
/**
* Payload Decoder
*
* Copyright 2025 Milesight IoT
*
* @product AM103
*/
var RAW_VALUE = 0x00;
/* eslint no-redeclare: "off" */
/* eslint-disable */
// Chirpstack v4
function decodeUplink(input) {
var decoded = milesightDeviceDecode(input.bytes);
return { data: decoded };
}
// Chirpstack v3
function Decode(fPort, bytes) {
return milesightDeviceDecode(bytes);
}
// The Things Network
function Decoder(bytes, port) {
return milesightDeviceDecode(bytes);
}
/* eslint-enable */
function milesightDeviceDecode(bytes) {
var decoded = {};
for (var i = 0; i < bytes.length; ) {
var channel_id = bytes[i++];
var channel_type = bytes[i++];
// IPSO VERSION
if (channel_id === 0xff && channel_type === 0x01) {
decoded.ipso_version = readProtocolVersion(bytes[i]);
i += 1;
}
// HARDWARE VERSION
else if (channel_id === 0xff && channel_type === 0x09) {
decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2));
i += 2;
}
// FIRMWARE VERSION
else if (channel_id === 0xff && channel_type === 0x0a) {
decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2));
i += 2;
}
// TSL VERSION
else if (channel_id === 0xff && channel_type === 0xff) {
decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2));
i += 2;
}
// SERIAL NUMBER
else if (channel_id === 0xff && channel_type === 0x16) {
decoded.sn = readSerialNumber(bytes.slice(i, i + 8));
i += 8;
}
// LORAWAN CLASS TYPE
else if (channel_id === 0xff && channel_type === 0x0f) {
decoded.lorawan_class = readLoRaWANClass(bytes[i]);
i += 1;
}
// RESET EVENT
else if (channel_id === 0xff && channel_type === 0xfe) {
decoded.reset_event = readResetEvent(1);
i += 1;
}
// DEVICE STATUS
else if (channel_id === 0xff && channel_type === 0x0b) {
decoded.device_status = readDeviceStatus(1);
i += 1;
}
// BATTERY
else if (channel_id === 0x01 && channel_type === 0x75) {
decoded.battery = readUInt8(bytes[i]);
i += 1;
}
// TEMPERATURE
else if (channel_id === 0x03 && channel_type === 0x67) {
// °C
decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10;
i += 2;
}
// HUMIDITY
else if (channel_id === 0x04 && channel_type === 0x68) {
decoded.humidity = readUInt8(bytes[i]) / 2;
i += 1;
}
// CO2
else if (channel_id === 0x07 && channel_type === 0x7d) {
decoded.co2 = readUInt16LE(bytes.slice(i, i + 2));
i += 2;
}
// HISTORY DATA
else if (channel_id === 0x20 && channel_type === 0xce) {
var data = {};
data.timestamp = readUInt32LE(bytes.slice(i, i + 4));
data.temperature = readInt16LE(bytes.slice(i + 4, i + 6)) / 10;
data.humidity = readUInt8(bytes[i + 6]) / 2;
data.co2 = readUInt16LE(bytes.slice(i + 7, i + 9));
i += 9;
decoded.history = decoded.history || [];
decoded.history.push(data);
}
// SENSOR ENABLE
else if (channel_id === 0xff && channel_type === 0x18) {
// skip 1 byte
var data = readUInt8(bytes[i + 1]);
var sensor_bit_offset = { temperature: 0, humidity: 1, co2: 4 };
decoded.sensor_enable = {};
for (var key in sensor_bit_offset) {
decoded.sensor_enable[key] = readEnableStatus((data >> sensor_bit_offset[key]) & 0x01);
}
i += 2;
}
// DOWNLINK RESPONSE
else if (channel_id === 0xfe || channel_id === 0xff) {
var result = handle_downlink_response(channel_type, bytes, i);
decoded = Object.assign(decoded, result.data);
i = result.offset;
} else {
break;
}
}
return decoded;
}
function handle_downlink_response(channel_type, bytes, offset) {
var decoded = {};
switch (channel_type) {
case 0x03:
decoded.report_interval = readUInt16LE(bytes.slice(offset, offset + 2));
offset += 2;
break;
case 0x06:
decoded.temperature_alarm_config = {};
var condition = readUInt8(bytes[offset]);
decoded.temperature_alarm_config.condition = readMathCondition(condition & 0x07);
decoded.temperature_alarm_config.threshold_min = readInt16LE(bytes.slice(offset + 1, offset + 3)) / 10;
decoded.temperature_alarm_config.threshold_max = readInt16LE(bytes.slice(offset + 3, offset + 5)) / 10;
// skip 4 bytes
offset += 9;
break;
case 0x10:
decoded.reboot = readYesNoStatus(1);
offset += 1;
break;
case 0x11:
decoded.timestamp = readUInt32LE(bytes.slice(offset, offset + 4));
offset += 4;
break;
case 0x17:
decoded.time_zone = readTimeZone(readInt16LE(bytes.slice(offset, offset + 2)));
offset += 2;
break;
case 0x1a:
var mode_value = readUInt8(bytes[offset]);
decoded.co2_calibration_settings = {};
decoded.co2_calibration_settings.mode = readCalibrationMode(mode_value);
if (mode_value === 2) {
decoded.co2_calibration_settings.calibration_value = readInt16LE(bytes.slice(offset + 1, offset + 3));
offset += 3;
} else {
offset += 1;
}
break;
case 0x27:
decoded.clear_history = readYesNoStatus(1);
offset += 1;
break;
case 0x2d:
decoded.screen_display_enable = readEnableStatus(bytes[offset]);
offset += 1;
break;
case 0x2f:
decoded.led_indicator_mode = readLedIndicatorStatus(bytes[offset]);
offset += 1;
break;
case 0x39:
decoded.co2_abc_calibration_settings = {};
decoded.co2_abc_calibration_settings.enable = readEnableStatus(bytes[offset]);
decoded.co2_abc_calibration_settings.period = readUInt16LE(bytes.slice(offset + 1, offset + 3));
decoded.co2_abc_calibration_settings.calibration_value = readUInt16LE(bytes.slice(offset + 3, offset + 5));
offset += 5;
break;
case 0x3a:
var num = readUInt8(bytes[offset]);
offset += 1;
for (var i = 0; i < num; i++) {
var report_schedule_config = {};
report_schedule_config.start_time = readUInt8(bytes[offset]) / 10;
report_schedule_config.end_time = readUInt8(bytes[offset + 1]) / 10;
report_schedule_config.report_interval = readUInt16LE(bytes.slice(offset + 2, offset + 4));
report_schedule_config.co2_collection_interval = readUInt8(bytes[offset + 4]);
report_schedule_config.collection_interval = readUInt8(bytes[offset + 5]);
offset += 6;
decoded.report_schedule_config = decoded.report_schedule_config || [];
decoded.report_schedule_config.push(report_schedule_config);
}
break;
case 0x3b:
decoded.time_sync_enable = readEnableStatus(bytes[offset]);
offset += 1;
break;
case 0x54:
decoded.co2_alarm_config = {};
decoded.co2_alarm_config.enable = readEnableStatus(bytes[offset]);
decoded.co2_alarm_config.threshold_1 = readUInt16LE(bytes.slice(offset + 1, offset + 3));
decoded.co2_alarm_config.threshold_2 = readUInt16LE(bytes.slice(offset + 3, offset + 5));
offset += 5;
break;
case 0x56:
decoded.screen_intelligent_enable = readEnableStatus(bytes[offset]);
offset += 1;
break;
case 0x57:
decoded.clear_report_schedule = readYesNoStatus(1);
offset += 1;
break;
case 0x59:
decoded.reset_battery = readYesNoStatus(1);
offset += 1;
break;
case 0x5a:
decoded.screen_refresh_interval = readUInt16LE(bytes.slice(offset, offset + 2));
offset += 2;
break;
case 0x68:
decoded.history_enable = readEnableStatus(bytes[offset]);
offset += 1;
break;
case 0x69:
decoded.retransmit_enable = readEnableStatus(bytes[offset]);
offset += 1;
break;
case 0x6a:
var interval_type = readUInt8(bytes[offset]);
if (interval_type === 0) {
decoded.retransmit_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3));
} else if (interval_type === 1) {
decoded.resend_interval = readUInt16LE(bytes.slice(offset + 1, offset + 3));
}
offset += 3;
break;
case 0x75:
decoded.hibernate_config = {};
decoded.hibernate_config.enable = readEnableStatus(bytes[offset]);
decoded.hibernate_config.lora_uplink_enable = readEnableStatus(bytes[offset + 1]);
decoded.hibernate_config.start_time = readUInt16LE(bytes.slice(offset + 2, offset + 4));
decoded.hibernate_config.end_time = readUInt16LE(bytes.slice(offset + 4, offset + 6));
decoded.hibernate_config.weekdays = {};
var data = readUInt8(bytes[offset + 6]);
var weekday_bit_offset = { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 7 };
for (var key in weekday_bit_offset) {
decoded.hibernate_config.weekdays[key] = readEnableStatus((data >> weekday_bit_offset[key]) & 0x01);
}
offset += 7;
break;
case 0x85:
decoded.screen_display_time_enable = readEnableStatus(bytes[offset]);
offset += 1;
break;
case 0x86:
decoded.screen_last_refresh_interval = readUInt8(bytes[offset]);
offset += 1;
break;
case 0x87:
decoded.altitude_calibration_settings = {};
decoded.altitude_calibration_settings.enable = readEnableStatus(bytes[offset]);
decoded.altitude_calibration_settings.calibration_value = readUInt16LE(bytes.slice(offset + 1, offset + 3));
offset += 3;
break;
case 0xf0:
var mask = readUInt16LE(bytes.slice(offset, offset + 2));
var data = readUInt16LE(bytes.slice(offset + 2, offset + 4));
decoded.screen_display_element_settings = {};
var sensor_bit_offset = { temperature: 0, humidity: 1, co2: 2, smile: 3 };
for (var key in sensor_bit_offset) {
if ((mask >> sensor_bit_offset[key]) & 0x01) {
decoded.screen_display_element_settings[key] = readEnableStatus( (data >>> sensor_bit_offset[key]) & 0x01);
}
}
offset += 4;
break;
default:
throw new Error("unknown downlink response");
}
return { data: decoded, offset: offset };
}
function readProtocolVersion(bytes) {
var major = (bytes & 0xf0) >> 4;
var minor = bytes & 0x0f;
return "v" + major + "." + minor;
}
function readHardwareVersion(bytes) {
var major = (bytes[0] & 0xff).toString(16);
var minor = (bytes[1] & 0xff) >> 4;
return "v" + major + "." + minor;
}
function readFirmwareVersion(bytes) {
var major = (bytes[0] & 0xff).toString(16);
var minor = (bytes[1] & 0xff).toString(16);
return "v" + major + "." + minor;
}
function readTslVersion(bytes) {
var major = bytes[0] & 0xff;
var minor = bytes[1] & 0xff;
return "v" + major + "." + minor;
}
function readSerialNumber(bytes) {
var temp = [];
for (var idx = 0; idx < bytes.length; idx++) {
temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2));
}
return temp.join("");
}
function readLoRaWANClass(type) {
var class_map = {
0: "Class A",
1: "Class B",
2: "Class C",
3: "Class CtoB",
};
return getValue(class_map, type);
}
function readResetEvent(status) {
var status_map = { 0: "normal", 1: "reset" };
return getValue(status_map, status);
}
function readDeviceStatus(status) {
var status_map = { 0: "off", 1: "on" };
return getValue(status_map, status);
}
function readYesNoStatus(status) {
var status_map = { 0: "no", 1: "yes" };
return getValue(status_map, status);
}
function readEnableStatus(status) {
var status_map = { 0: "disable", 1: "enable" };
return getValue(status_map, status);
}
function readTimeZone(time_zone) {
var timezone_map = { "-120": "UTC-12", "-110": "UTC-11", "-100": "UTC-10", "-95": "UTC-9:30", "-90": "UTC-9", "-80": "UTC-8", "-70": "UTC-7", "-60": "UTC-6", "-50": "UTC-5", "-40": "UTC-4", "-35": "UTC-3:30", "-30": "UTC-3", "-20": "UTC-2", "-10": "UTC-1", 0: "UTC", 10: "UTC+1", 20: "UTC+2", 30: "UTC+3", 35: "UTC+3:30", 40: "UTC+4", 45: "UTC+4:30", 50: "UTC+5", 55: "UTC+5:30", 57: "UTC+5:45", 60: "UTC+6", 65: "UTC+6:30", 70: "UTC+7", 80: "UTC+8", 90: "UTC+9", 95: "UTC+9:30", 100: "UTC+10", 105: "UTC+10:30", 110: "UTC+11", 120: "UTC+12", 127: "UTC+12:45", 130: "UTC+13", 140: "UTC+14" };
return getValue(timezone_map, time_zone);
}
function readLedIndicatorStatus(status) {
var status_map = { 0: "off", 2: "blink" };
return getValue(status_map, status);
}
function readMathCondition(type) {
var condition_map = { 0: "disable", 1: "below", 2: "above", 3: "between", 4: "outside" };
return getValue(condition_map, type);
}
function readCalibrationMode(type) {
var mode_map = { 0: "factory", 1: "abc", 2: "manual", 3: "background", 4: "zero" };
return getValue(mode_map, type);
}
/* eslint-disable */
function readUInt8(bytes) {
return bytes & 0xff;
}
function readInt8(bytes) {
var ref = readUInt8(bytes);
return ref > 0x7f ? ref - 0x100 : ref;
}
function readUInt16LE(bytes) {
var value = (bytes[1] << 8) + bytes[0];
return value & 0xffff;
}
function readInt16LE(bytes) {
var ref = readUInt16LE(bytes);
return ref > 0x7fff ? ref - 0x10000 : ref;
}
function readUInt32LE(bytes) {
var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
return (value & 0xffffffff) >>> 0;
}
function readInt32LE(bytes) {
var ref = readUInt32LE(bytes);
return ref > 0x7fffffff ? ref - 0x100000000 : ref;
}
function getValue(map, key) {
if (RAW_VALUE) return key;
var value = map[key];
if (!value) value = "unknown";
return value;
}
if (!Object.assign) {
Object.defineProperty(Object, "assign", {
enumerable: false,
configurable: true,
writable: true,
value: function (target) {
"use strict";
if (target == null) {
throw new TypeError("Cannot convert first argument to object");
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource == null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(Object(nextSource));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
// concat array
if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) {
to[nextKey] = to[nextKey].concat(nextSource[nextKey]);
} else {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
}); }

In the Datapoint Details step,
Click Manage Message Formatter.
Click Choose Custom Function.
Select the required custom function and click Associate.
Click Save.
Click Update in the confirmation pop-up.
Once the device product is available in the application, you can select and assign the required uplink formatter to it.
In the developer application:
Learn how to use the best tools for sales force automation and better customer engagement from Zoho's implementation specialists.
If you'd like a personalized walk-through of our data preparation tool, please request a demo and we'll be happy to show you how to get the best out of Zoho DataPrep.
You are currently viewing the help pages of Qntrl’s earlier version. Click here to view our latest version—Qntrl 3.0's help articles.