Initial working radio controller for OP25
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ node_modules/
|
||||
*.log
|
||||
*.txt
|
||||
*.env
|
||||
!requirements.txt
|
||||
!requirements.txt
|
||||
*testOP25Dir/
|
||||
1
Client/PDAB
Submodule
1
Client/PDAB
Submodule
Submodule Client/PDAB added at 51027d794d
@@ -4,6 +4,7 @@ const log = new DebugBuilder("client", "clientController");
|
||||
|
||||
const spawn = require('child_process').spawn;
|
||||
const { resolve } = require("path");
|
||||
const { closeProcessWrapper } = require("../utilities/utilities");
|
||||
|
||||
// Global vars
|
||||
let pythonProcess;
|
||||
@@ -75,11 +76,7 @@ exports.leaveServer = async (req, res) => {
|
||||
log.INFO("Leaving the server");
|
||||
if (!pythonProcess) return res.sendStatus(200)
|
||||
|
||||
await pythonProcess.kill(2);
|
||||
await setTimeout(async () => {
|
||||
if (pythonProcess) await pythonProcess.kill(9);
|
||||
}, 25000)
|
||||
pythonProcess = undefined;
|
||||
pythonProcess = await closeProcessWrapper(pythonProcess);
|
||||
|
||||
return res.sendStatus(202);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ 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");
|
||||
|
||||
@@ -16,45 +17,137 @@ let radioChildProcess, tempRes, radioConfigPath;
|
||||
/**
|
||||
* Closes the radio executable if it's in one
|
||||
*/
|
||||
exports.closeRadioSession = (req, res) => {
|
||||
if (!radioChildProcess) return res.sendStatus(200)
|
||||
tempRes = res;
|
||||
radioChildProcess.kill();
|
||||
radioChildProcess = undefined;
|
||||
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 = (req, res) => {
|
||||
// Check if the given config is saved
|
||||
log.DEBUG("[/radio/changeCurrentConfig] - Checking if provided preset is in the config");
|
||||
if (!checkIfPresetExists(req.body.presetName)) return res.status(500).JSON("No preset with given name found in config"); // No preset with the given name is in the config
|
||||
exports.changeCurrentConfig = async (req, res) => {
|
||||
const presetName = req.body.presetName;
|
||||
if (!presetName) return res.status(500).json("You must include the preset name")
|
||||
|
||||
// Check if the current config is the same as the preset given
|
||||
const currentConfig = readOP25Config();
|
||||
if (currentConfig.channels && currentConfig.channels.name === req.body.presetName) {
|
||||
log.DEBUG("[/radio/changeCurrentConfig] - Current config is the same as the preset given");
|
||||
return res.sendStatus(202);
|
||||
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);
|
||||
}
|
||||
|
||||
// Convert radioPreset to OP25 'cfg.json. file
|
||||
log.DEBUG("[/radio/changeCurrentConfig] - Converting radioPreset to OP25 config");
|
||||
const updatedConfigObject = convertRadioPresetsToOP25Config(req.body.presetName);
|
||||
|
||||
// Replace current JSON file with the updated file
|
||||
writeOP25Config(updatedConfigObject, () => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
|
||||
return res.sendStatus(202);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new OP25 process tuned to the specified system
|
||||
*/
|
||||
exports.openRadioSession = () => {
|
||||
if (radioChildProcess) closeRadioSession();
|
||||
radioChildProcess = spawn(getRadioBinPath());
|
||||
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;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,8 +181,10 @@ function writeOP25Config(config, callback = undefined) {
|
||||
*/
|
||||
function readOP25Config() {
|
||||
const configPath = getRadioConfigPath();
|
||||
log.DEBUG(`Reading from config path: '${configPath}'`);
|
||||
return JSON.parse(fs.readFileSync(configPath));
|
||||
log.DEBUG(`Reading from config path: '${configPath}'`);
|
||||
const readFile = fs.readFileSync(configPath);
|
||||
log.VERBOSE("File Contents: ", readFile.toString());
|
||||
return JSON.parse(readFile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +202,7 @@ function getRadioConfigPath(){
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ router.post('/start', radioController.openRadioSession);
|
||||
|
||||
/**
|
||||
* POST Close the current radio session
|
||||
* Response from the radio: 200: closed; 204: not connected
|
||||
*/
|
||||
router.post('/stop', radioController.closeRadioSession);
|
||||
|
||||
|
||||
@@ -8,6 +8,32 @@ const log = new DebugBuilder("client", "executeConsoleCommands");
|
||||
const execCommand = promisify(exec);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} process The process to close
|
||||
* @returns {undefined} Undefined to replace the existing process in the parent
|
||||
*/
|
||||
exports.closeProcessWrapper = async (process) => {
|
||||
log.INFO("Leaving the server");
|
||||
if (!process) return undefined;
|
||||
|
||||
// Try to close the process gracefully
|
||||
await process.kill(2);
|
||||
|
||||
// Wait 25 seconds and see if the process is still open, if it is force it close
|
||||
await setTimeout(async () => {
|
||||
if (process) await process.kill(9);
|
||||
}, 25000)
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} consoleCommand
|
||||
* @returns
|
||||
*/
|
||||
exports.executeAsyncConsoleCommand = async function executeAsyncConsoleCommand(consoleCommand) {
|
||||
// Check to see if the command is a real command
|
||||
// TODO needs to be improved
|
||||
|
||||
Reference in New Issue
Block a user