106 lines
2.8 KiB
JavaScript

// 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}`);
}