Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: test nix-start nix-test

serve:
docker pull chirpstack/chirpstack-device-profiles:latest
docker pull --platform=linux/amd64 chirpstack/chirpstack-device-profiles:latest
docker run --rm -u $(id -u):$(id -g) -p 8090:8090 -v '$(shell pwd):/chirpstack-device-profiles' chirpstack/chirpstack-device-profiles:latest -p /chirpstack-device-profiles

build:
Expand Down
366 changes: 366 additions & 0 deletions vendors/milesight/codecs/am102-l.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
/**
* Payload Decoder
*
* Copyright 2025 Milesight IoT
*
* @product AM102L
*/
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;
}
// 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;
i += 7;
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 };
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 0x27:
decoded.clear_history = readYesNoStatus(1);
offset += 1;
break;
case 0x2f:
decoded.led_indicator_mode = readLedIndicatorStatus(bytes[offset]);
offset += 1;
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));
// skip 1 byte
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 0x57:
decoded.clear_report_schedule = readYesNoStatus(1);
offset += 1;
break;
case 0x59:
decoded.reset_battery = readYesNoStatus(1);
offset += 1;
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;
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);
}

/* 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;
},
});
//}
Loading