Hi!
Me and AI made this decoder for my private Chirpstack together, tested with a few months of data in influxDB, seems fine. How do I get the device supported by the platform?
Device:
https://www.dnil.se/products/lhi110-han-interface
Decoder:
function decodeUplink(input) {
const bytes = input.bytes;
const fPort = input.fPort;
const obj = {};
// Helper functions
function toHexString(byteArray) {
return Array.prototype.map
.call(byteArray, (byte) => ("0" + (byte & 0xff).toString(16)).slice(-2))
.join("-");
}
function arrToUint16(byteArr, startIdx) {
return (byteArr[startIdx] << 8) | byteArr[startIdx + 1];
}
function arrToUint32(byteArr, startIdx) {
return (
(byteArr[startIdx] << 24) |
(byteArr[startIdx + 1] << 16) |
(byteArr[startIdx + 2] << 8) |
byteArr[startIdx + 3]
) >>> 0; // Ensure unsigned
}
function arrToUint40(byteArr, startIdx) {
return (
(BigInt(byteArr[startIdx]) << 32n) |
(BigInt(byteArr[startIdx + 1]) << 24n) |
(BigInt(byteArr[startIdx + 2]) << 16n) |
(BigInt(byteArr[startIdx + 3]) << 8n) |
BigInt(byteArr[startIdx + 4])
);
}
function decodePower(pwrData) {
return (pwrData & (1 << 15)) ? (pwrData & ~(1 << 15)) * 100 : pwrData;
}
function decodeVoltages(byteArr, startIdx) {
const lineVoltArr = arrToUint32(byteArr, startIdx);
const l1Volt_mV = ((lineVoltArr & 0x3ff00000) >> 20) * 250 + 20000;
const l2Volt_mV = ((lineVoltArr & 0x000ffc00) >> 10) * 250 + 20000;
const l3Volt_mV = (lineVoltArr & 0x000003ff) * 250 + 20000;
return [l1Volt_mV, l2Volt_mV, l3Volt_mV];
}
function decodeCurrents(byteArr, startIdx) {
const lineCurrArr = arrToUint32(byteArr, startIdx);
return [
((lineCurrArr & 0x3ff00000) >> 20) * 100,
((lineCurrArr & 0x000ffc00) >> 10) * 100,
((lineCurrArr & 0x000003ff) >> 0) * 100,
];
}
// Main decoding logic
if (fPort === 1) {
if (bytes[0] === 0x01) {
switch (bytes[1]) {
case 0x03:
obj.fwVersion = String.fromCharCode.apply(String, bytes.slice(2));
break;
case 0x06:
obj.vdd = arrToUint16(bytes, 2);
break;
case 0x0a:
obj.CPUTemperature = ((arrToUint16(bytes, 2)) - 5000) / 100;
break;
case 0x20:
obj.status = bytes[2].toString(2);
break;
case 0x22:
obj.reportInterval = arrToUint16(bytes, 2);
break;
case 0x23:
break; // msgType removed
default:
obj.error = "Unknown data index";
}
} else if (bytes[0] === 0x02) {
obj.nackIndex = bytes[1];
}
} else if (fPort === 2) {
// Decoding for energy data
obj.activeImportReadingRaw = toHexString(bytes.slice(5, 10));
obj.activeImportReading = Number(arrToUint40(bytes, 5)) / 1000;
// Decode power, voltages, and currents
obj.actL1ImportPowerPeak = decodePower(arrToUint16(bytes, 10));
obj.actL1ImportPowerAver = decodePower(arrToUint16(bytes, 12));
obj.actL2ImportPowerPeak = decodePower(arrToUint16(bytes, 14));
obj.actL2ImportPowerAver = decodePower(arrToUint16(bytes, 16));
obj.actL3ImportPowerPeak = decodePower(arrToUint16(bytes, 18));
obj.actL3ImportPowerAver = decodePower(arrToUint16(bytes, 20));
const voltages = decodeVoltages(bytes, 22);
obj.l1VoltageAver_mV = voltages[0];
obj.l2VoltageAver_mV = voltages[1];
obj.l3VoltageAver_mV = voltages[2];
const currents = decodeCurrents(bytes, 26);
obj.l1CurrentPeak_mA = currents[0];
obj.l2CurrentPeak_mA = currents[1];
obj.l3CurrentPeak_mA = currents[2];
} else if (fPort === 10) {
obj.equipmentId = toHexString(bytes);
} else {
obj.error = "Unsupported fPort";
}
return {
data: obj,
warnings: obj.error ? [obj.error] : [],
};
}