241 lines
7.7 KiB
JavaScript
241 lines
7.7 KiB
JavaScript
require("dotenv").config({ quiet: true })
|
|
|
|
const ws = require("ws");
|
|
// const conn = new ws("wss://saubeo.solutions/demo/rdio-scanner/");
|
|
const conn = new ws("wss://rosebud-mt.ko4wal.radio")
|
|
const fs = require("fs");
|
|
const Discord = require("discord.js");
|
|
const hook = new Discord.WebhookClient({ url: process.env.DISCORD_WEBHOOK_URL });
|
|
|
|
const knownTones = {
|
|
"634.5:706.8": "<@&1531872533119504464>",
|
|
"601.1:706.8": "<@&1531872550852755596>",
|
|
}
|
|
|
|
// create or empty the temp folder
|
|
if (!fs.existsSync("./temp")) {
|
|
fs.mkdirSync("./temp");
|
|
} else {
|
|
fs.readdirSync("./temp").forEach((file) => {
|
|
fs.unlinkSync(`./temp/${file}`);
|
|
});
|
|
}
|
|
|
|
// Build an LFM filter map that enables every talkgroup, in every group,
|
|
// on every system found in the CFG payload.
|
|
var globalSystems = {};
|
|
|
|
function buildFullLfmFilter(config) {
|
|
const filter = {};
|
|
const systems = (config && config.systems) || {};
|
|
|
|
for (const sysId of Object.keys(systems)) {
|
|
const system = systems[sysId];
|
|
const talkgroups = (system && system.talkgroups) || [];
|
|
|
|
filter[system.id] = filter[system.id] || {};
|
|
globalSystems[system.id] = {};
|
|
globalSystems[system.id].name = system.label;
|
|
for (const tg of talkgroups) {
|
|
const groupId = tg.groupId ?? tg.group ?? tg.tagId ?? 0;
|
|
filter[system.id][tg.id] = true;
|
|
globalSystems[system.id][tg.id] = tg;
|
|
// console.log(tg)
|
|
}
|
|
// Print out number of talkgroups for system, and sys name
|
|
console.log(`System ${system.label} (${system.id}) has ${talkgroups.length} talkgroups.`);
|
|
}
|
|
// console.log(JSON.stringify(filter, null, 2));
|
|
// console.log("Global systems:", JSON.stringify(globalSystems, null, 2));
|
|
return filter;
|
|
}
|
|
|
|
// Wrapper function around twotonedec.py to call it from Node.js and return the result.
|
|
const { spawn } = require("child_process");
|
|
|
|
const decodeTwoTone = (filePath) => {
|
|
return new Promise((resolve, reject) => {
|
|
// FFMPEG Convert file to wav
|
|
const wavFilePath = filePath.replace(/\.\w+$/, ".wav");
|
|
console.log(`Converting ${filePath} to ${wavFilePath} using FFMPEG...`);
|
|
// unlink the wav file if it already exists
|
|
const path = require("path");
|
|
|
|
console.log("Original:", filePath);
|
|
console.log("WAV:", wavFilePath);
|
|
console.log("Absolute:", path.resolve(wavFilePath));
|
|
console.log("Exists:", fs.existsSync(wavFilePath));
|
|
if (fs.existsSync(wavFilePath)) {
|
|
fs.unlinkSync(wavFilePath);
|
|
}
|
|
const ffmpegProcess = spawn("ffmpeg", [
|
|
"-y",
|
|
"-i", filePath,
|
|
"-acodec", "pcm_s16le",
|
|
"-ar", "8000", // or whatever your decoder expects
|
|
"-ac", "1", // mono if required
|
|
wavFilePath
|
|
]);
|
|
|
|
ffmpegProcess.on("close", (code) => {
|
|
if (code !== 0) {
|
|
reject(new Error(`FFMPEG process exited with code ${code}`));
|
|
return;
|
|
}
|
|
// Proceed with Python process only after FFMPEG conversion is successful
|
|
const pythonProcess = spawn("python3.12", ["twotonedec.py", wavFilePath]);
|
|
let result = "";
|
|
|
|
pythonProcess.stdout.on("data", (data) => {
|
|
result += data.toString();
|
|
});
|
|
pythonProcess.stderr.on("data", (data) => {
|
|
console.error(`stderr: ${data}`);
|
|
});
|
|
pythonProcess.on("close", (code) => {
|
|
if (code === 0) {
|
|
try {
|
|
result = JSON.parse(result.replace(/'/g, '"'));
|
|
// Delete the wav file after processing
|
|
fs.unlink(wavFilePath, (err) => {
|
|
if (err) {
|
|
console.error(`Error deleting wav file: ${err}`);
|
|
}
|
|
});
|
|
resolve(result);
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
} else {
|
|
reject(new Error(`Python process exited with code ${code}`));
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
|
|
// decodeTwoTone("test2.m4a").then((result) => {
|
|
// console.log("Two-tone detection result:", result);
|
|
// }) // Test code
|
|
|
|
conn.on("message", (data) => {
|
|
let msg = data.toString()
|
|
let parsed = JSON.parse(msg)
|
|
// console.log("Received message:", parsed);
|
|
switch (parsed[0]) {
|
|
case "CFG": // Config RX
|
|
const config = parsed[1];
|
|
const lfmFilter = buildFullLfmFilter(config);
|
|
console.log("Got config from server, sending LFM filter");
|
|
conn.send(JSON.stringify(["LFM", lfmFilter]));
|
|
break;
|
|
case "LFM": // LFM Response
|
|
if (parsed[1] == true) {
|
|
console.log("LFM filter accepted by server.");
|
|
|
|
// TEST
|
|
// conn.send(JSON.stringify(["CAL","653"]))
|
|
// conn.send(JSON.stringify(["CAL","655"]))
|
|
// conn.send(JSON.stringify(["CAL","634"]))
|
|
} else {
|
|
console.error("LFM filter rejected by server:", parsed[1]);
|
|
process.exit(1);
|
|
}
|
|
break;
|
|
case "LSC": // Listener Count
|
|
console.log("Listener count:", parsed[1]);
|
|
break;
|
|
case "VER": // Ver info
|
|
console.log(`${parsed[1].branding} RDIO v${parsed[1].version}`)
|
|
conn.send(JSON.stringify(["CFG"]));
|
|
break;
|
|
case "CAL": // New call
|
|
console.log(`New Call ${globalSystems[parsed[1].system][parsed[1].talkgroup].name} call #${parsed[1].id}`);
|
|
const tgName = globalSystems[parsed[1].system][parsed[1].talkgroup].name;
|
|
fs.writeFileSync(`./temp/${parsed[1].audioName}`, Buffer.from(parsed[1].audio.data));
|
|
decodeTwoTone(`./temp/${parsed[1].audioName}`).then((result) => {
|
|
console.log(`Two-tone detection result for ${tgName}:`, result);
|
|
// Two tone result will be array of objects for each detected twotone activation. .detected is array of two frequencies, one for each tone. Create a counter for each pair of frequencies detected. Make a message to send to discord with the tone names, so if 2 copies of RC EMS are sent, send "RC EMS (2x)" instead. For multiple, send "RC EMS (2x), RC Fire (1x)" etc.
|
|
const toneCounts = {};
|
|
for (const activation of result) {
|
|
const detected = activation.detected;
|
|
const key = `${detected[0]}:${detected[1]}`;
|
|
if (toneCounts[key]) {
|
|
toneCounts[key]++;
|
|
} else {
|
|
toneCounts[key] = 1;
|
|
}
|
|
}
|
|
const toneMessages = [];
|
|
for (const key in toneCounts) {
|
|
const name = knownTones[key] || key;
|
|
const count = toneCounts[key];
|
|
toneMessages.push(count > 1 ? `${name} (${count}x)` : name);
|
|
}
|
|
const message = toneMessages.join(", ");
|
|
console.log(`Two-tone summary for ${tgName}:`, message);
|
|
hook.send({
|
|
// Username is tg name
|
|
username: globalSystems[parsed[1].system][parsed[1].talkgroup].name,
|
|
content: `${message}`,
|
|
// Upload audio file
|
|
files: [{
|
|
attachment: `./temp/${parsed[1].audioName}`,
|
|
name: parsed[1].audioName
|
|
}]
|
|
}).then(() => {
|
|
// Delete the audio file after sending to Discord
|
|
fs.unlink(`./temp/${parsed[1].audioName}`, (err) => {
|
|
if (err) {
|
|
console.error(`Error deleting audio file: ${err}`);
|
|
}
|
|
});
|
|
})
|
|
}); // TwoTone breaks on my server. Will fix later (I think i need py 3.12 not 3.13)
|
|
// hook.send({
|
|
// // Username is tg name
|
|
// username: globalSystems[parsed[1].system][parsed[1].talkgroup].name,
|
|
// // content: `${message}`,
|
|
// // Upload audio file
|
|
// files: [{
|
|
// attachment: `./temp/${parsed[1].audioName}`,
|
|
// name: parsed[1].audioName
|
|
// }]
|
|
// }).then(() => {
|
|
// // Delete the audio file after sending to Discord
|
|
// fs.unlink(`./temp/${parsed[1].audioName}`, (err) => {
|
|
// if (err) {
|
|
// console.error(`Error deleting audio file: ${err}`);
|
|
// }
|
|
// });
|
|
// })
|
|
break;
|
|
default:
|
|
console.log("Unknown message:", JSON.stringify(parsed));
|
|
break;
|
|
}
|
|
});
|
|
|
|
conn.on("open", () => {
|
|
console.log("Connected to the server.");
|
|
conn.send(JSON.stringify(["VER"]))
|
|
});
|
|
|
|
conn.on("close", () => {
|
|
// Set 3 second timeout to reconnect.
|
|
setTimeout(() => {
|
|
console.log("Connection closed. Reconnecting...");
|
|
conn = new ws("wss://rosebud-mt.ko4wal.radio");
|
|
}, 3000);
|
|
});
|
|
|
|
conn.on("error", (err) => {
|
|
console.error("Connection error:", err);
|
|
// Close conn, and reconnect after 3 seconds
|
|
conn.close();
|
|
setTimeout(() => {
|
|
console.log("Reconnecting...");
|
|
conn = new ws("wss://rosebud-mt.ko4wal.radio");
|
|
}, 3000);
|
|
}); |