UNPKG

icom-wlan-node

Version:

Icom WLAN (CI‑V, audio) protocol implementation for Node.js/TypeScript.

137 lines (136 loc) 5.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Session = void 0; const UdpClient_1 = require("../transport/UdpClient"); const debug_1 = require("../utils/debug"); const IcomPackets_1 = require("./IcomPackets"); class Session { constructor(address, handlers) { this.udp = new UdpClient_1.UdpClient(); this.localId = Date.now() >>> 0; this.remoteId = 0; this.trackedSeq = 1; // 0x03 uses seq=0, 0x06 uses seq=1 this.pingSeq = 0; this.innerSeq = 0x30; this.rigToken = 0; this.localToken = (Date.now() & 0xffff) >>> 0; // short semantics this.lastSentTime = Date.now(); this.lastReceivedTime = Date.now(); this.txHistory = new Map(); this.destroyed = false; this.address = address; this.handlers = handlers; this.udp.on('data', (rinfo, data) => { if (this.destroyed) return; this.lastReceivedTime = Date.now(); try { const type = data.length >= 6 ? data.readUInt16LE(4) : -1; (0, debug_1.dbgV)(`RX port=${this.localPort} from ${rinfo.address}:${rinfo.port} len=${data.length} type=${type >= 0 ? '0x' + type.toString(16) : 'n/a'}`); } catch { } handlers.onData(data); }); this.udp.on('error', handlers.onSendError); } open() { this.destroyed = false; // Reset destroyed flag to allow sending data after reconnection this.udp.open(); } close() { this.stopTimers(); this.destroyed = true; this.udp.close(); } get localPort() { return this.udp.localPort; } sendRaw(buf) { if (this.destroyed) return; try { this.udp.send(buf, this.address.ip, this.address.port); this.lastSentTime = Date.now(); } catch (e) { /* bubble via event */ } } sendUntracked(buf) { this.sendRaw(buf); } sendTracked(buf) { const pkt = Buffer.from(buf); IcomPackets_1.ControlPacket.setSeq(pkt, this.trackedSeq); this.sendRaw(pkt); this.txHistory.set(this.trackedSeq, pkt); this.trackedSeq = (this.trackedSeq + 1) & 0xffff; } retransmit(seq) { const pkt = this.txHistory.get(seq); if (pkt) this.sendRaw(pkt); else this.sendUntracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.NULL, seq, this.localId, this.remoteId)); } startIdle() { this.stopIdle(); this.idleTimer = setInterval(() => { if (Date.now() - this.lastSentTime > 200) { this.sendTracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.NULL, 0, this.localId, this.remoteId)); } }, 100); } stopIdle() { if (this.idleTimer) { clearInterval(this.idleTimer); this.idleTimer = undefined; } } startAreYouThere() { this.stopAreYouThere(); (0, debug_1.dbg)(`Starting AreYouThere timer for ${this.address.ip}:${this.address.port}`); this.areYouThereTimer = setInterval(() => { (0, debug_1.dbg)(`AYT -> ${this.address.ip}:${this.address.port} localId=${this.localId}`); this.sendUntracked(IcomPackets_1.ControlPacket.toBytes(IcomPackets_1.Cmd.ARE_YOU_THERE, 0, this.localId, 0)); }, 500); } stopAreYouThere() { if (this.areYouThereTimer) { clearInterval(this.areYouThereTimer); this.areYouThereTimer = undefined; } } startPing() { this.stopPing(); this.pingTimer = setInterval(() => { this.sendUntracked(IcomPackets_1.PingPacket.buildPing(this.localId, this.remoteId, this.pingSeq)); }, 500); } stopPing() { if (this.pingTimer) { clearInterval(this.pingTimer); this.pingTimer = undefined; } } stopTimers() { this.stopAreYouThere(); this.stopPing(); this.stopIdle(); } setRemote(ip, port) { this.address = { ip, port }; } /** * Reset session state to initial values * Call this before reconnecting to ensure clean state * (especially important after radio restart) */ resetState() { // Stop all timers to prevent leaks and interference with new connection this.stopTimers(); // Reset destroyed flag to ensure session is usable after state reset this.destroyed = false; // Generate new IDs this.localId = Date.now() >>> 0; this.remoteId = 0; // Reset sequence numbers this.trackedSeq = 1; this.pingSeq = 0; this.innerSeq = 0x30; // Reset tokens this.rigToken = 0; this.localToken = (Date.now() & 0xffff) >>> 0; // Reset timestamps this.lastSentTime = Date.now(); this.lastReceivedTime = Date.now(); // Clear history this.txHistory.clear(); (0, debug_1.dbgV)(`Session state reset: localId=${this.localId}, localToken=${this.localToken}`); } } exports.Session = Session;