Files
DRB-CnC/Client/controllers/radioController.js
2023-05-20 14:31:43 -04:00

245 lines
8.4 KiB
JavaScript

// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("client", "radioController");
// Modules
const { resolve, dirname } = require('path');
require('dotenv').config();
const fs = require('fs');
const radioConfigHelper = require("../utilities/radioConfigHelper");
const presetWrappers = require("../utilities/updatePresets");
const { closeProcessWrapper } = require("../utilities/utilities");
const spawn = require('child_process').spawn;
const converter = require("convert-units");
const radioBinPath = process.env.OP25_BIN_PATH;
let radioChildProcess, tempRes, radioConfigPath;
/**
* Closes the radio executable if it's in one
*/
exports.closeRadioSession = async (req, res) => {
if (!radioChildProcess) return res.sendStatus(204);
radioChildProcess = await closeProcessWrapper(radioChildProcess);
if (!radioChildProcess) return res.sendStatus(200);
}
/**
* Change the current 'cfg.json' file to the preset specified
* @param {string} presetName
*/
exports.changeCurrentConfig = async (req, res) => {
const presetName = req.body.presetName;
if (!presetName) return res.status(500).json("You must include the preset name")
const updatedConfigObject = await this.changeCurrentConfigWrapper(presetName);
// No change was made to the config
if (!updatedConfigObject) return res.sendStatus(200);
// Error was encountered
if (typeof updatedConfigObject === "string") return res.status(500).json(updatedConfigObject);
// There was a change made to the config, reopening the radio session if it was open
if (radioChildProcess) {
log.DEBUG("Radio session open, restarting to accept the new config");
const radioSessionResult = await this.openRadioSessionWrapper(radioChildProcess, presetName);
// throw an error to the client if the wrapper ran into an error
if (typeof radioSessionResult === "string") return res.status(500).json(updatedConfigObject);
}
return res.sendStatus(202);
}
/**
* Open a new OP25 process tuned to the specified system
*/
exports.openRadioSession = async (req, res) => {
const presetName = req.body.presetName;
if(!presetName) return res.status(500).json({"message": "You must include the preset name to start the radio session with"})
radioChildProcess = await this.openRadioSessionWrapper(radioChildProcess, presetName);
// throw an error to the client if the wrapper ran into an error
if (typeof radioSessionResult === "string") return res.status(500).json(updatedConfigObject);
return res.sendStatus(200);
}
/**
* This wrapper closes any open radio sessions and the opens a new one
*
* @returns {radioChildProcess} The process of the radio session for use
*/
exports.openRadioSessionWrapper = async (radioChildProcess, presetName) => {
if (radioChildProcess) radioChildProcess = await closeProcessWrapper(radioChildProcess);
const configChangeResult = await this.changeCurrentConfigWrapper(presetName);
// Throw an error to the client if the config change ran into an error
if (typeof configChangeResult === "string") return configChangeResult;
if (process.platform === "win32") {
log.DEBUG("Starting Windows OP25");
radioChildProcess = await spawn("C:\\Python310\\python.exe", [getRadioBinPath(), "-c", getRadioConfigPath()], { cwd: dirname(getRadioBinPath()) });
}
else {
log.DEBUG("Starting Linux OP25");
radioChildProcess = await spawn(getRadioBinPath(), ["-c", getRadioConfigPath()], { cwd: dirname(getRadioBinPath()) });
}
log.VERBOSE("Radio Process: ", radioChildProcess);
let fullOutput;
radioChildProcess.stdout.setEncoding('utf8');
radioChildProcess.stdout.on("data", (data) => {
log.VERBOSE("From Process: ", data);
fullOutput += data.toString();
});
radioChildProcess.stderr.on('data', (data) => {
log.VERBOSE(`stderr: ${data}`);
fullOutput += data.toString();
});
radioChildProcess.on('close', (code) => {
log.DEBUG(`child process exited with code ${code}`);
log.VERBOSE("Full output from radio: ", fullOutput);
});
radioChildProcess.on("error", (code, signal) => {
log.ERROR("Error from the radio process: ", code, signal);
});
// Starting the radio application
return radioChildProcess
}
/**
*
* @param {*} presetName
* @returns
*/
exports.changeCurrentConfigWrapper = async (presetName) => {
// Check if the given config is saved
log.DEBUG("Checking if provided preset is in the config");
const presetIsPresent = await checkIfPresetExists(presetName);
if (!presetIsPresent) return "No preset with given name found in config"; // No preset with the given name is in the config
// Check if the current config is the same as the preset given
try {
const currentConfig = readOP25Config();
if (currentConfig.channels && currentConfig.channels.name === presetName) {
log.DEBUG("Current config is the same as the preset given");
return undefined;
}
}
catch (err) {
log.WARN("Problem reading the config file, overwriting with the new config", err);
}
// Convert radioPreset to OP25 'cfg.json. file
log.DEBUG("Converting radioPreset to OP25 config");
const updatedConfigObject = convertRadioPresetsToOP25Config(presetName);
// Replace current JSON file with the updated file
writeOP25Config(updatedConfigObject, () => {
return updatedConfigObject;
})
}
/**
* Get the location of the 'multi_rx.py' binary from the config
*/
function getRadioBinPath(){
return resolve(radioBinPath);
}
/**
* Write the given config to the JSON file in OP25 the bin dir
* @param config The full config to be written to the file
* @param {function} callback The function to be called when this wrapper completes
*/
function writeOP25Config(config, callback = undefined) {
log.DEBUG("Updating OP25 config with: ", config);
fs.writeFile(getRadioConfigPath(), JSON.stringify(config), (err) => {
// Error checking
if (err) {
log.ERROR(err);
throw err;
}
log.DEBUG("Write Complete");
if (callback) callback()
});
}
/**
* Get the current config file in use by OP25
* @returns {object|*} The parsed config object currently set in OP25
*/
function readOP25Config() {
const configPath = getRadioConfigPath();
log.DEBUG(`Reading from config path: '${configPath}'`);
const readFile = fs.readFileSync(configPath);
log.VERBOSE("File Contents: ", readFile.toString());
return JSON.parse(readFile);
}
/**
* Get the path of the config for the radio app (OP25) and set the global variable
*/
function getRadioConfigPath(){
let radioConfigDirPath = dirname(getRadioBinPath());
return resolve(`${radioConfigDirPath}/cfg.json`);
}
/**
* Check to see if the preset name exists in the config
* @param {string} presetName The system name as saved in the preset
* @returns {true||false}
*/
function checkIfPresetExists(presetName) {
const savedPresets = presetWrappers.getPresets();
log.DEBUG("Found presets: ", savedPresets, presetName, Object.keys(savedPresets).includes(presetName));
if (!Object.keys(savedPresets).includes(presetName)) return false;
else return true;
}
/**
* Convert a radioPreset to OP25's cfg.json file
*/
function convertRadioPresetsToOP25Config(presetName){
const savedPresets = presetWrappers.getPresets();
let frequencyString = "";
for (const frequency of savedPresets[presetName].frequencies){
frequencyString += `${converter(frequency).from("Hz").to("MHz")},`
}
frequencyString = frequencyString.slice(0, -1);
let updatedOP25Config;
switch (savedPresets[presetName].mode){
case "p25":
updatedOP25Config = new radioConfigHelper.P25({
"systemName": presetName,
"controlChannelsString": frequencyString,
"tagsFile": savedPresets[presetName].trunkFile
});
break;
case "nbfm":
//code for nbfm here
updatedOP25Config = new radioConfigHelper.NBFM({
"frequency": frequencyString,
"systemName": presetName
});
break;
default:
throw new Error("Radio mode of selected preset not recognized");
}
log.DEBUG(updatedOP25Config);
return updatedOP25Config;
}