icom-wlan-node
Version:
Icom WLAN (CI‑V, audio) protocol implementation for Node.js/TypeScript.
137 lines (136 loc) • 5.11 kB
JavaScript
"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;