const maxEirpDecoded = [8,10,12,13,14,16,18,20,21,24,26,27,29,30,33,36]; const timingSetupReqDelay = [1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; const deviceModeClass = ["Class-A", "RFU", "Class-C"]; const macCommands = [ { cid: 0x02, name: "LinkCheckReq", size: 0, dir: 0 }, { cid: 0x02, name: "LinkCheckAns", size: 2, dir: 1 }, { cid: 0x03, name: "LinkADRReq", size: 4, dir: 1 }, { cid: 0x03, name: "LinkADRAns", size: 1, dir: 0 }, { cid: 0x04, name: "DutyCycleReq", size: 1, dir: 1 }, { cid: 0x04, name: "DutyCycleAns", size: 0, dir: 0 }, { cid: 0x05, name: "RXParamSetupReq", size: 4, dir: 1 }, { cid: 0x05, name: "RXParamSetupAns", size: 1, dir: 0 }, { cid: 0x06, name: "DevStatusReq", size: 0, dir: 1 }, { cid: 0x06, name: "DevStatusAns", size: 2, dir: 0 }, { cid: 0x07, name: "NewChannelReq", size: 5, dir: 1 }, { cid: 0x07, name: "NewChannelAns", size: 1, dir: 0 }, { cid: 0x08, name: "RXTimingSetupReq", size: 1, dir: 1 }, { cid: 0x08, name: "RXTimingSetupAns", size: 0, dir: 0 }, { cid: 0x09, name: "TxParamSetupReq", size: 1, dir: 1 }, { cid: 0x09, name: "TxParamSetupAns", size: 0, dir: 0 }, { cid: 0x0A, name: "DlChannelReq", size: 4, dir: 1 }, { cid: 0x0A, name: "DlChannelAns", size: 1, dir: 0 }, { cid: 0x0B, name: "RekeyConf", size: 1, dir: 1 }, { cid: 0x0B, name: "RekeyInd", size: 1, dir: 0 }, { cid: 0x0C, name: "ADRParamSetupReq", size: 1, dir: 1 }, { cid: 0x0C, name: "ADRParamSetupAns", size: 0, dir: 0 }, { cid: 0x0D, name: "DeviceTimeReq", size: 0, dir: 0 }, { cid: 0x0D, name: "DeviceTimeAns", size: 5, dir: 1 }, { cid: 0x0E, name: "ForceRejoinReq", size: 2, dir: 1 }, { cid: 0x0F, name: "RejoinParamSetupReq", size: 1, dir: 1 }, { cid: 0x0F, name: "RejoinParamSetupAns", size: 1, dir: 0 }, { cid: 0x10, name: "PingSlotInfoReq", size: 1, dir: 0 }, { cid: 0x10, name: "PingSlotInfoAns", size: 0, dir: 1 }, { cid: 0x11, name: "PingSlotChannelReq", size: 4, dir: 1 }, { cid: 0x11, name: "PingSlotChannelAns", size: 1, dir: 0 }, { cid: 0x12, name: "BeaconTimingReq", size: 0, dir: 0 }, { cid: 0x12, name: "BeaconTimingAns", size: 3, dir: 1 }, { cid: 0x13, name: "BeaconFreqReq", size: 3, dir: 1 }, { cid: 0x13, name: "BeaconFreqAns", size: 0, dir: 0 }, { cid: 0x20, name: "DeviceModeConf", size: 1, dir: 1 }, { cid: 0x20, name: "DeviceModeInd", size: 1, dir: 0 } ]; let ulCids = [], dlCids = []; function macDirection() { ulCids = macCommands.filter(c => c.dir === 0); dlCids = macCommands.filter(c => c.dir === 1); } function macGetCid(cid, dir = 0) { const list = dir ? dlCids : ulCids; return list.find(c => c.cid === cid) || null; } function hexToBytes(hex) { return hex.match(/.{1,2}/g).map(h => parseInt(h, 16)); } // === Decode helpers === const handlers = { NewChannelReq: c => ({ ChIndex: c[0], Frequency: 100 * (c[1] + (c[2]<<8) + (c[3]<<16)), "DrRange.MinDR": c[4] & 0x0F, "DrRange.MaxDR": (c[4] >> 4) & 0x0F }), NewChannelAns: c => ({ ChannelFrequencyOK: c[0] & 0x01, DataRateRangeOK: (c[0] >> 1) & 0x01 }), DevStatusAns: c => { let battery = c[0]; if (battery === 0) battery = "ExternalPower"; else if (battery === 255) battery = "ErrorMeasuring"; let rs = c[1] & 0x3F; if (rs > 0x1F) rs -= 64; return { Battery: battery, RadioStatus: rs }; }, LinkCheckAns: c => ({ Margin: c[0], GwCnt: c[1] }), LinkADRReq: c => ({ TxPower: c[0] & 0x0F, DataRate: (c[0] >> 4) & 0x0F, ChMask: ((c[1]) + (c[2]<<8)).toString(16).padStart(4, "0"), Redundancy: c[3] }), LinkADRAns: c => ({ ChannelMaskACK: c[0] & 0x01, DataRateACK: (c[0] >> 1) & 0x01, PowerACK: (c[0] >> 2) & 0x01 }), DutyCycleReq: c => ({ MaxDutyCycle: c[0] & 0x0F }), RXParamSetupAns: c => ({ ChannelACK: c[0] & 0x01, RX2DataRateACK: (c[0] >> 1) & 0x01, RX1DROffsetACK: (c[0] >> 2) & 0x01 }), RXParamSetupReq: c => ({ RX2DataRate: c[0] & 0x0F, RX1DRoffset: (c[0] >> 4) & 0x0F, Frequency: 100 * (c[1] + (c[2]<<8) + (c[3]<<16)) }), DlChannelReq: c => ({ ChIndex: c[0], Frequency: 100 * (c[1] + (c[2]<<8) + (c[3]<<16)) }), DlChannelAns: c => ({ ChannelFrequencyOK: c[0] & 0x01, UplinkFrequencyExists: (c[0] >> 1) & 0x01 }), TxParamSetupReq: c => ({ MaxEIRP: c[0] & 0x0F, MaxEIRPdBm: maxEirpDecoded[c[0] & 0x0F] + " dBm", UplinkDwellTime: (c[0] >> 4) & 0x01, DownlinkDwellTime: (c[0] >> 5) & 0x01 }), RXTimingSetupReq: c => ({ Delay: c[0] & 0x0F, DelayDec: timingSetupReqDelay[c[0] & 0x0F] }), DeviceTimeAns: c => ({ Epoch: c[0] + (c[1]<<8) + (c[2]<<16) + (c[3]<<24), FractionalSeconds: c[4] * 0.00390625 }), RekeyConf: c => ({ LoRaWanVersion: c[0] & 0x0F }), RekeyInd: c => ({ LoRaWanVersion: c[0] & 0x0F }), ForceRejoinReq: c => ({ DataRate: c[0] & 0x0F, RejoinType: (c[0] >> 4) & 0x07, MaxRetries: c[1] & 0x07, MaxRetriesDecoded: (c[1] & 0x07) + 1, Period: (c[1] >> 3) & 0x07 }), RejoinParamSetupReq: c => ({ MaxCountN: c[0] & 0x0F, MaxCountNDecoded: 1 << (c[0] & 0x0F), MaxTimeN: (c[0] >> 4) & 0x0F, MaxTimeNDecoded: 1 << ((c[0] >> 4) + 10) }), RejoinParamSetupAns: c => ({ TimeOK: c[0] & 0x01 }), PingSlotInfoReq: c => ({ Periodicity: c[0] & 0x07 }), PingSlotChannelReq: c => ({ DataRate: c[0] & 0x0F, Frequency: 100 * (c[1] + (c[2]<<8) + (c[3]<<16)) }), PingSlotChannelAns: c => ({ FrequencyOK: c[0] & 0x01, DataRateOK: (c[0] >> 1) & 0x01 }), BeaconTimingAns: c => ({ Channel: c[0], Delay: c[1] + (c[2]<<8), DelayDecoded: 30 * (c[1] + (c[2]<<8)) }), BeaconFreqReq: c => ({ Frequency: 100 * (c[0] + (c[1]<<8) + (c[2]<<16)) }), DeviceModeInd: c => ({ DeviceClass: c[0], DeviceClassDecoded: deviceModeClass[c[0]] }), DeviceModeConf: c => ({ DeviceClass: c[0], DeviceClassDecoded: deviceModeClass[c[0]] }) }; // Main decoder function macDecode(direction, hexStr) { const payload = hexToBytes(hexStr); const result = []; let i = 0; while (i < payload.length) { const cid = payload[i]; const cmd = macGetCid(cid, direction); if (!cmd) { result.push({ cid: `0x${cid.toString(16).padStart(2, "0")}`, name: "Unknown", raw: payload.slice(i + 1).map(b => b.toString(16).padStart(2, "0")).join('') }); break; } const data = payload.slice(i + 1, i + 1 + cmd.size); const decoded = { cid: `0x${cid.toString(16).padStart(2, "0")}`, name: cmd.name, raw: data.map(b => b.toString(16).padStart(2, "0")).join(''), length: cmd.size }; if (handlers[cmd.name]) decoded.data = handlers[cmd.name](data); else if (cmd.size > 0) decoded.data = { warning: "Not decoded currently" }; result.push(decoded); i += 1 + cmd.size; } return result; } // Init macDirection();