// ChirpStack v4+ Codec (JavaScript) // LSN50 v2.x - Production codec for PA12 + Downlink control (TDC + 5VT) // // Verified mapping (uplink): PA12 = bytes[6] bit1 (0x02) on FPort 2 (MOD=1, 11 bytes) // Downlink commands (Dragino common): // - Set TDC (seconds): 0x01 + 3 bytes (big-endian) // - Set 5VT (ms): 0x07 + 2 bytes (big-endian) const CFG = { INVERT_LOGIC: false, EXPECTED_UP_FPORT: 2, EXPECTED_UP_LEN: 11, CMD_FPORT: 2, LIMITS: { TDC_SEC_MIN: 5, TDC_SEC_MAX: 86400, VT_MS_MIN: 0, VT_MS_MAX: 60000 } }; function decodeUplink(input) { const b = input.bytes || []; const fPort = input.fPort; const rawHex = b.map(x => x.toString(16).padStart(2, "0")).join(""); const byte6 = (b.length > 6) ? b[6] : 0x00; const pa12 = (byte6 & 0x02) ? 1 : 0; const drenaje = CFG.INVERT_LOGIC ? (pa12 ? 0 : 1) : pa12; const warnings = []; if (fPort !== CFG.EXPECTED_UP_FPORT) warnings.push(`Unexpected fPort=${fPort}, expected ${CFG.EXPECTED_UP_FPORT}`); if (b.length !== CFG.EXPECTED_UP_LEN) warnings.push(`Unexpected payload length=${b.length}, expected ${CFG.EXPECTED_UP_LEN}`); return { data: { drenaje, pa12, byte6, payload_len: b.length, payload_hex: rawHex, warnings: warnings.length ? warnings : undefined } }; } function u16be(n) { return [(n >> 8) & 0xff, n & 0xff]; } function u24be(n) { return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } function assertInt(name, v) { if (typeof v !== "number" || !isFinite(v) || Math.floor(v) !== v) { throw new Error(`${name} must be an integer`); } } function clampInt(name, v, min, max) { assertInt(name, v); if (v < min || v > max) throw new Error(`${name} out of range [${min}, ${max}]: ${v}`); return v; } function encodeDownlink(input) { const data = (input && input.data) ? input.data : {}; const cmd = data.cmd; if (!cmd) { return { bytes: [] }; } if (cmd === "set_tdc") { const tdc = clampInt("tdc_sec", data.tdc_sec, CFG.LIMITS.TDC_SEC_MIN, CFG.LIMITS.TDC_SEC_MAX); const bytes = [0x01].concat(u24be(tdc)); return { fPort: CFG.CMD_FPORT, bytes }; } if (cmd === "set_5vt") { const vt = clampInt("vt_ms", data.vt_ms, CFG.LIMITS.VT_MS_MIN, CFG.LIMITS.VT_MS_MAX); const bytes = [0x07].concat(u16be(vt)); return { fPort: CFG.CMD_FPORT, bytes }; } if (cmd === "profile_active") { const vt = clampInt("vt_ms", (data.vt_ms ?? 2000), CFG.LIMITS.VT_MS_MIN, CFG.LIMITS.VT_MS_MAX); const bytes = [0x07].concat(u16be(vt)); return { fPort: CFG.CMD_FPORT, bytes }; } if (cmd === "profile_sleep") { const vt = clampInt("vt_ms", (data.vt_ms ?? 0), CFG.LIMITS.VT_MS_MIN, CFG.LIMITS.VT_MS_MAX); const bytes = [0x07].concat(u16be(vt)); return { fPort: CFG.CMD_FPORT, bytes }; } throw new Error(`Unknown cmd: ${cmd}`); }