Major update

- Working client server interactions
- Can create radio config
- Needs radio testing
This commit is contained in:
Logan Cusano
2023-02-18 20:41:43 -05:00
parent c8c75e8b37
commit ce072d9287
25 changed files with 1114 additions and 39 deletions

1
.gitignore vendored
View File

@@ -4,4 +4,5 @@ node_modules/
# Development files
*.log
*.txt

1
Client/.env Normal file
View File

@@ -0,0 +1 @@
DEBUG="client:*";

View File

@@ -7,6 +7,7 @@ var logger = require('morgan');
var indexRouter = require('./routes/index');
var botRouter = require('./routes/bot');
var clientRouter = require('./routes/client');
var radioRouter = require('./routes/radio');
var app = express();
@@ -28,6 +29,9 @@ app.use('/bot', botRouter);
// Local client control route
app.use("/client", clientRouter);
// Local radio controller route
app.use("/radio", radioRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));

View File

@@ -1,4 +1,5 @@
// Core config settings for the node, these are the settings that are checked with the server
const path = require("path");
exports.clientConfig = {
"id": 13,
"name": "boilin balls in the hall",
@@ -14,4 +15,9 @@ exports.serverConfig = {
"ip": "127.0.0.1",
"hostname": "localhost",
"port": 3000
}
// Configuration of the local OP25 application
exports.radioAppConfig = {
"bin": "H:/Logan/Projects/Discord-Radio-Bot-CnC/Client/.idea/testOP25Dir/multi_rx.py"
}

View File

@@ -1 +1 @@
{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"}}
{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"},"coppies":{"frequencies":[154875000],"mode":"nbfm","trunkFile":"none"}}

View File

@@ -6,7 +6,7 @@ const path = require('path');
const fork = require('child_process').fork;
const discordBotPath = path.resolve('discord-bot/app.js');
let botChildProcess, radioChildProcess, tempRes;
let botChildProcess, tempRes;
/**
* Bot Process Object Builder
@@ -67,6 +67,7 @@ exports.joinServer = (req, res) => {
tempRes.sendStatus(202);
tempRes = undefined;
botChildProcess.kill();
botChildProcess = undefined;
return;
case "ChgPreSet":
tempRes.sendStatus(200);

View File

@@ -0,0 +1,147 @@
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("client", "radioController");
// Modules
const { resolve, dirname } = require('path');
const fs = require('fs');
const radioConfig = require('../config/clientConfig').radioAppConfig;
const radioConfigHelper = require("../utilities/radioConfigHelper");
const presetWrappers = require("../utilities/updatePresets");
const spawn = require('child_process').spawn;
const converter = require("convert-units");
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;
}
/**
* 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
// 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);
}
// 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);
})
}
/**
* Open a new OP25 process tuned to the specified system
*/
exports.openRadioSession = () => {
if (radioChildProcess) closeRadioSession();
radioChildProcess = spawn(getRadioBinPath());
}
/**
* Get the location of the 'multi_rx.py' binary from the config
*/
function getRadioBinPath(){
return resolve(radioConfig.bin);
}
/**
* 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}'`);
return JSON.parse(fs.readFileSync(configPath));
}
/**
* 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();
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;
}

327
Client/package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "client",
"version": "0.0.0",
"dependencies": {
"convert-units": "^2.3.4",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "~2.6.1",
@@ -174,6 +175,15 @@
"node": ">= 0.6"
}
},
"node_modules/convert-units": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz",
"integrity": "sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==",
"dependencies": {
"lodash.foreach": "2.3.x",
"lodash.keys": "2.3.x"
}
},
"node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
@@ -443,6 +453,160 @@
"node": ">=8"
}
},
"node_modules/lodash._basebind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz",
"integrity": "sha512-SHqM7YCuJ+BeGTs7lqpWnmdHEeF4MWxS3dksJctHFNxR81FXPOzA4bS5Vs5CpcGTkBpM8FCl+YEbQEblRw8ABg==",
"dependencies": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"node_modules/lodash._basecreate": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz",
"integrity": "sha512-vwZaWldZwS2y9b99D8i9+WtgiZXbHKsBsMrpxJEqTsNW20NhJo5W8PBQkeQO9CmxuqEYn8UkMnfEM2MMT4cVrw==",
"dependencies": {
"lodash._renative": "~2.3.0",
"lodash.isobject": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"node_modules/lodash._basecreatecallback": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz",
"integrity": "sha512-Ev+pDzzfVfgbiucpXijconLGRBar7/+KNCf05kSnk4CmdDVhAy1RdbU9efCJ/o9GXI08JdUGwZ+5QJ3QX3kj0g==",
"dependencies": {
"lodash._setbinddata": "~2.3.0",
"lodash.bind": "~2.3.0",
"lodash.identity": "~2.3.0",
"lodash.support": "~2.3.0"
}
},
"node_modules/lodash._basecreatewrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz",
"integrity": "sha512-YLycQ7k8AB9Wc1EOvLNxuRWcqipDkMXq2GCgnLWQR6qtgTb3gY3LELzEpnFshrEO4LOLs+R2EpcY+uCOZaLQ8Q==",
"dependencies": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash._slice": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"node_modules/lodash._createwrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz",
"integrity": "sha512-XjaI/rzg9W+WO4WJDQ+PRlHD5sAMJ1RhJLuT65cBxLCb1kIYs4U20jqvTDGAWyVT3c34GYiLd9AreHYuB/8yJA==",
"dependencies": {
"lodash._basebind": "~2.3.0",
"lodash._basecreatewrapper": "~2.3.0",
"lodash.isfunction": "~2.3.0"
}
},
"node_modules/lodash._objecttypes": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz",
"integrity": "sha512-jbA6QyHt9cw3BzvbWzIcnU3Z12jSneT6xBgz3Y782CJsN1tV5aTBKrFo2B4AkeHBNaxSrbPYZZpi1Lwj3xjdtg=="
},
"node_modules/lodash._renative": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._renative/-/lodash._renative-2.3.0.tgz",
"integrity": "sha512-v44MRirqYqZGK/h5UKoVqXWF2L+LUiLTU+Ogu5rHRVWJUA1uWIlHaMpG8f/OA8j++BzPMQij9+erXHtgFcbuwg=="
},
"node_modules/lodash._setbinddata": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz",
"integrity": "sha512-xMFfbF7dL+sFtrdE49uHFmfpBAEwlFtfgMp86nQRlAF6aizYL+3MTbnYMKJSkP1W501PhsgiBED5kBbZd8kR2g==",
"dependencies": {
"lodash._renative": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"node_modules/lodash._shimkeys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz",
"integrity": "sha512-9Iuyi7TiWMGa/9+2rqEE+Zwye4b/U2w7Saw6UX1h6Xs88mEER+uz9FZcEBPKMVKsad9Pw5GNAcIBRnW2jNpneQ==",
"dependencies": {
"lodash._objecttypes": "~2.3.0"
}
},
"node_modules/lodash._slice": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.3.0.tgz",
"integrity": "sha512-7C61GhzRUv36gTafr+RIb+AomCAYsSATEoK4OP0VkNBcwvsM022Z22AVgqjjzikeNO1U29LzsJZDvLbiNPUYvA=="
},
"node_modules/lodash.bind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.3.0.tgz",
"integrity": "sha512-goakyOo+FMN8lttMPnZ0UNlr5RlzX4IrUXyTJPT2A0tGCMXySupond9wzvDqTvVmYTcQjIKGrj8naJDS2xWAlQ==",
"dependencies": {
"lodash._createwrapper": "~2.3.0",
"lodash._renative": "~2.3.0",
"lodash._slice": "~2.3.0"
}
},
"node_modules/lodash.foreach": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.3.0.tgz",
"integrity": "sha512-yLnyptVRJd0//AbGp480grgQG9iaDIV5uOgSbpurRy1dYybPbjNTLQ3FyLEQ84buVLPG7jyaiyvpzgfOutRB3Q==",
"dependencies": {
"lodash._basecreatecallback": "~2.3.0",
"lodash.forown": "~2.3.0"
}
},
"node_modules/lodash.forown": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.3.0.tgz",
"integrity": "sha512-dUnCsuQTtq3Y7bxPNoEEqjJjPL2ftLtcz2PTeRKvhbpdM514AvnqCjewHGsm/W+dwspIwa14KoWEZeizJ7smxA==",
"dependencies": {
"lodash._basecreatecallback": "~2.3.0",
"lodash._objecttypes": "~2.3.0",
"lodash.keys": "~2.3.0"
}
},
"node_modules/lodash.identity": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.3.0.tgz",
"integrity": "sha512-NYJ2r2cwy3tkx/saqbIZEX6oQUzjWTnGRu7d/zmBjMCZos3eHBxCpbvWFWSetv8jFVrptsp6EbWjzNgBKhUoOA=="
},
"node_modules/lodash.isfunction": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz",
"integrity": "sha512-X5lteBYlCrVO7Qc00fxP8W90fzRp6Ax9XcHANmU3OsZHdSyIVZ9ZlX5QTTpRq8aGY+9I5Rmd0UTzTIIyWPugEQ=="
},
"node_modules/lodash.isobject": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.3.0.tgz",
"integrity": "sha512-jo1pfV61C4TE8BfEzqaHj6EIKiSkFANJrB6yscwuCJMSRw5tbqjk4Gv7nJzk4Z6nFKobZjGZ8Qd41vmnwgeQqQ==",
"dependencies": {
"lodash._objecttypes": "~2.3.0"
}
},
"node_modules/lodash.keys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.3.0.tgz",
"integrity": "sha512-c0UW0ffqMxSCtoVbmVt2lERJLkEqgoOn2ejPsWXzr0ZrqRbl3uruGgwHzhtqXxi6K/ei3Ey7zimOqSwXgzazPg==",
"dependencies": {
"lodash._renative": "~2.3.0",
"lodash._shimkeys": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"node_modules/lodash.noop": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.3.0.tgz",
"integrity": "sha512-NpSm8HRm1WkBBWHUveDukLF4Kfb5P5E3fjHc9Qre9A11nNubozLWD2wH3UBTZbu+KSuX8aSUvy9b+PUyEceJ8g=="
},
"node_modules/lodash.support": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.3.0.tgz",
"integrity": "sha512-etc7VWbB0U3Iya8ixj2xy4sDBN3jvPX7ODi8iXtn4KkkjNpdngrdc7Vlt5jub/Vgqx6/dWtp7Ml9awhCQPYKGQ==",
"dependencies": {
"lodash._renative": "~2.3.0"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -949,6 +1113,15 @@
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"convert-units": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz",
"integrity": "sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==",
"requires": {
"lodash.foreach": "2.3.x",
"lodash.keys": "2.3.x"
}
},
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
@@ -1160,6 +1333,160 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"lodash._basebind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz",
"integrity": "sha512-SHqM7YCuJ+BeGTs7lqpWnmdHEeF4MWxS3dksJctHFNxR81FXPOzA4bS5Vs5CpcGTkBpM8FCl+YEbQEblRw8ABg==",
"requires": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"lodash._basecreate": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz",
"integrity": "sha512-vwZaWldZwS2y9b99D8i9+WtgiZXbHKsBsMrpxJEqTsNW20NhJo5W8PBQkeQO9CmxuqEYn8UkMnfEM2MMT4cVrw==",
"requires": {
"lodash._renative": "~2.3.0",
"lodash.isobject": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"lodash._basecreatecallback": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz",
"integrity": "sha512-Ev+pDzzfVfgbiucpXijconLGRBar7/+KNCf05kSnk4CmdDVhAy1RdbU9efCJ/o9GXI08JdUGwZ+5QJ3QX3kj0g==",
"requires": {
"lodash._setbinddata": "~2.3.0",
"lodash.bind": "~2.3.0",
"lodash.identity": "~2.3.0",
"lodash.support": "~2.3.0"
}
},
"lodash._basecreatewrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz",
"integrity": "sha512-YLycQ7k8AB9Wc1EOvLNxuRWcqipDkMXq2GCgnLWQR6qtgTb3gY3LELzEpnFshrEO4LOLs+R2EpcY+uCOZaLQ8Q==",
"requires": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash._slice": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"lodash._createwrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz",
"integrity": "sha512-XjaI/rzg9W+WO4WJDQ+PRlHD5sAMJ1RhJLuT65cBxLCb1kIYs4U20jqvTDGAWyVT3c34GYiLd9AreHYuB/8yJA==",
"requires": {
"lodash._basebind": "~2.3.0",
"lodash._basecreatewrapper": "~2.3.0",
"lodash.isfunction": "~2.3.0"
}
},
"lodash._objecttypes": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz",
"integrity": "sha512-jbA6QyHt9cw3BzvbWzIcnU3Z12jSneT6xBgz3Y782CJsN1tV5aTBKrFo2B4AkeHBNaxSrbPYZZpi1Lwj3xjdtg=="
},
"lodash._renative": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._renative/-/lodash._renative-2.3.0.tgz",
"integrity": "sha512-v44MRirqYqZGK/h5UKoVqXWF2L+LUiLTU+Ogu5rHRVWJUA1uWIlHaMpG8f/OA8j++BzPMQij9+erXHtgFcbuwg=="
},
"lodash._setbinddata": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz",
"integrity": "sha512-xMFfbF7dL+sFtrdE49uHFmfpBAEwlFtfgMp86nQRlAF6aizYL+3MTbnYMKJSkP1W501PhsgiBED5kBbZd8kR2g==",
"requires": {
"lodash._renative": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"lodash._shimkeys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz",
"integrity": "sha512-9Iuyi7TiWMGa/9+2rqEE+Zwye4b/U2w7Saw6UX1h6Xs88mEER+uz9FZcEBPKMVKsad9Pw5GNAcIBRnW2jNpneQ==",
"requires": {
"lodash._objecttypes": "~2.3.0"
}
},
"lodash._slice": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.3.0.tgz",
"integrity": "sha512-7C61GhzRUv36gTafr+RIb+AomCAYsSATEoK4OP0VkNBcwvsM022Z22AVgqjjzikeNO1U29LzsJZDvLbiNPUYvA=="
},
"lodash.bind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.3.0.tgz",
"integrity": "sha512-goakyOo+FMN8lttMPnZ0UNlr5RlzX4IrUXyTJPT2A0tGCMXySupond9wzvDqTvVmYTcQjIKGrj8naJDS2xWAlQ==",
"requires": {
"lodash._createwrapper": "~2.3.0",
"lodash._renative": "~2.3.0",
"lodash._slice": "~2.3.0"
}
},
"lodash.foreach": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.3.0.tgz",
"integrity": "sha512-yLnyptVRJd0//AbGp480grgQG9iaDIV5uOgSbpurRy1dYybPbjNTLQ3FyLEQ84buVLPG7jyaiyvpzgfOutRB3Q==",
"requires": {
"lodash._basecreatecallback": "~2.3.0",
"lodash.forown": "~2.3.0"
}
},
"lodash.forown": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.3.0.tgz",
"integrity": "sha512-dUnCsuQTtq3Y7bxPNoEEqjJjPL2ftLtcz2PTeRKvhbpdM514AvnqCjewHGsm/W+dwspIwa14KoWEZeizJ7smxA==",
"requires": {
"lodash._basecreatecallback": "~2.3.0",
"lodash._objecttypes": "~2.3.0",
"lodash.keys": "~2.3.0"
}
},
"lodash.identity": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.3.0.tgz",
"integrity": "sha512-NYJ2r2cwy3tkx/saqbIZEX6oQUzjWTnGRu7d/zmBjMCZos3eHBxCpbvWFWSetv8jFVrptsp6EbWjzNgBKhUoOA=="
},
"lodash.isfunction": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz",
"integrity": "sha512-X5lteBYlCrVO7Qc00fxP8W90fzRp6Ax9XcHANmU3OsZHdSyIVZ9ZlX5QTTpRq8aGY+9I5Rmd0UTzTIIyWPugEQ=="
},
"lodash.isobject": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.3.0.tgz",
"integrity": "sha512-jo1pfV61C4TE8BfEzqaHj6EIKiSkFANJrB6yscwuCJMSRw5tbqjk4Gv7nJzk4Z6nFKobZjGZ8Qd41vmnwgeQqQ==",
"requires": {
"lodash._objecttypes": "~2.3.0"
}
},
"lodash.keys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.3.0.tgz",
"integrity": "sha512-c0UW0ffqMxSCtoVbmVt2lERJLkEqgoOn2ejPsWXzr0ZrqRbl3uruGgwHzhtqXxi6K/ei3Ey7zimOqSwXgzazPg==",
"requires": {
"lodash._renative": "~2.3.0",
"lodash._shimkeys": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"lodash.noop": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.3.0.tgz",
"integrity": "sha512-NpSm8HRm1WkBBWHUveDukLF4Kfb5P5E3fjHc9Qre9A11nNubozLWD2wH3UBTZbu+KSuX8aSUvy9b+PUyEceJ8g=="
},
"lodash.support": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.3.0.tgz",
"integrity": "sha512-etc7VWbB0U3Iya8ixj2xy4sDBN3jvPX7ODi8iXtn4KkkjNpdngrdc7Vlt5jub/Vgqx6/dWtp7Ml9awhCQPYKGQ==",
"requires": {
"lodash._renative": "~2.3.0"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",

View File

@@ -6,6 +6,7 @@
"start": "node ./bin/www"
},
"dependencies": {
"convert-units": "^2.3.4",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "~2.6.1",

View File

@@ -1,6 +1,6 @@
var express = require('express');
const express = require('express');
const router = express.Router();
const botController = require("../controllers/botController");
var router = express.Router();
/** GET bot status
* Check to see if the bot is online and if so, if it is currently connected to anything
@@ -27,11 +27,4 @@ router.post('/join', botController.joinServer);
*/
router.post('/leave', botController.leaveServer);
/** POST change bot preset
* This will change the bot to the preset specified (if different from what is currently playing)
*
* The status of the bot: 200 = no change, 202 = changed successfully, 500 + JSON = encountered error
* @returns status
*/
module.exports = router;

30
Client/routes/radio.js Normal file
View File

@@ -0,0 +1,30 @@
// Controllers
const radioController = require("../controllers/radioController");
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("client", "clientController");
// Modules
const express = require('express');
const router = express.Router();
/**
* POST Open a new radio session
* This will open OP25 to the current specified radio settings
*/
router.post('/start', radioController.openRadioSession);
/**
* POST Close the current radio session
*/
router.post('/stop', radioController.closeRadioSession);
/** POST change current radio preset
* This will change the bot to the preset specified (if different from what is currently playing)
* @param req The request sent from the master
* @param {string} req.body.presetName The name of the system to be updated
* The status of the bot: 200 = no change, 202 = changed successfully, 500 + JSON = encountered error
* @returns status
*/
router.post('/changeConfig', radioController.changeCurrentConfig);
module.exports = router;

View File

@@ -0,0 +1,161 @@
exports.P25 = class P25ConfigGenerator {
constructor({systemName, controlChannelsString, tagsFile}) {
this.channels = new channelConfig({
"systemName": systemName,
"enableAnalog": "off",
"demodType": "cqpsk",
"cqpskTracking": true,
"filterType": "rc"
});
this.devices = new deviceConfig({
"gain": "LNA:36"
});
this.trunking = new trunkingConfig({
"module": "tk_p25.py",
"systemName": systemName,
"controlChannelsString": controlChannelsString,
"tagsFile": tagsFile
});
this.audio = new audioConfig({});
this.terminal = new terminalConfig({});
}
}
exports.NBFM = class NBFMConfigGenerator {
constructor({systemName, frequency, nbfmSquelch = -70}) {
this.channels = new channelConfig({
"channelName": systemName,
"enableAnalog": "on",
"nbfmSquelch": nbfmSquelch,
"frequency": frequency,
"demodType": "fsk4",
"filterType": "widepulse"
});
this.devices = new deviceConfig({
"gain": "LNA:32"
});
this.audio = new audioConfig({});
this.terminal = new terminalConfig({});
}
}
class channelConfig {
constructor({
channelName = "Voice_ch1",
device = "sdr0",
systemName,
metaStreamName,
demodType, // cqpsk: P25; fsk4: everything else
cqpskTracking,
trackingThreshold = 120,
trackingFeedback = 0.75,
destination = "udp://127.0.0.1:23456",
excess_bw = 0.2,
filterType = "rc", // rc: P25; widepulse: analog
ifRate = 24000,
plot = "",
symbolRate = 4800,
enableAnalog, //[on, off, auto]
nbfmDeviation = 4000, // only needed if analog is enabled
nbfmSquelch = -50, // only needed if analog is enabled
frequency, // only needed if analog is enabled
blacklist,
whitelist,
cryptKeys
}){
// Core Configs
this.name = channelName;
this.device = device;
this.demod_type = demodType;
this.destination = destination;
this.excess_bw = excess_bw;
this.filter_type = filterType;
this.if_rate = ifRate;
this.plot = plot;
this.symbol_rate = symbolRate;
this.enable_analog = enableAnalog;
// P25 config
if (!enableAnalog || enableAnalog === "off" || systemName) this.trunking_sysname = systemName;
if (!enableAnalog || enableAnalog === "off" || systemName && metaStreamName) this.meta_stream_name = metaStreamName ?? "";
if (!enableAnalog || enableAnalog === "off" || systemName) this.cqpsk_tracking = cqpskTracking;
if (!enableAnalog || enableAnalog === "off" || systemName) this.tracking_threshold = trackingThreshold;
if (!enableAnalog || enableAnalog === "off" || systemName) this.tracking_feedback = trackingFeedback;
if (!enableAnalog || enableAnalog === "off" || systemName && blacklist) this.blacklist = blacklist ?? "";
if (!enableAnalog || enableAnalog === "off" || systemName && whitelist) this.whitelist = whitelist ?? "";
if (!enableAnalog || enableAnalog === "off" || systemName && cryptKeys) this.crypt_keys = cryptKeys ?? "";
// Analog config
if (enableAnalog === "on" || enableAnalog === "auto") this.nbfm_deviation = nbfmDeviation;
if (enableAnalog === "on" || enableAnalog === "auto") this.nbfm_squelch = nbfmSquelch;
if (enableAnalog === "on" || enableAnalog === "auto") this.frequency = frequency;
}
}
class deviceConfig {
constructor({args = "rtl", gain = "LNA:32", gainMode = false, name = "sdr0", offset = 0, ppm = 0.0, sampleRate = 1920000, tunable=true}) {
this.args = args
this.gains = gain
this.gain_mode = gainMode
this.name = name
this.offset = offset
this.ppm = ppm
this.rate = sampleRate
this.usable_bw_pct = 0.85
this.tunable = tunable
}
}
class trunkingConfig {
/**
*
* @param {object} *
*/
constructor({module, systemName, controlChannelsString, tagsFile = "", nac = "0x0", wacn = "0x0", cryptBehavior = 2, whitelist = "", blacklist = ""}) {
this.module = module;
this.chans = [{
"nac": nac,
"wacn": wacn,
"sysname": systemName,
"control_channel_list": controlChannelsString,
"whitelist": whitelist,
"blacklist": blacklist,
"tgid_tags_file": tagsFile,
"tdma_cc": false,
"crypt_behavior": cryptBehavior
}];
}
}
class audioConfig {
constructor({module = "sockaudio.py", port = 23456, deviceName = "default"}) {
this.module = module;
this.instances = [{
"instance_name": "audio0",
"device_name": deviceName,
"udp_port": port,
"audio_gain": 1.0,
"number_channels": 1
}];
}
}
class metadataStreamConfig {
constructor({}) {
this.module = "";
this.streams = [];
}
}
class terminalConfig {
constructor({module = "terminal.py", terminalType = "http:0.0.0.0:8080"}) {
this.module = module;
this.terminal_type = terminalType;
this.curses_plot_interval= 0.1;
this.http_plot_interval= 1.0;
this.http_plot_directory = "../www/images";
this.tuning_step_large= 1200;
this.tuning_step_small = 100;
}
}

View File

@@ -3,6 +3,8 @@ const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("client", "updatePresets");
// Modules
const fs = require('fs');
const path = require("path");
const converter = require("convert-units");
/**
* Write the given presets to the JSON file
@@ -10,7 +12,8 @@ const fs = require('fs');
* @param {function} callback The function to be called when this wrapper completes
*/
function writePresets(presets, callback = undefined) {
fs.writeFile("../config/radioPresets.json", JSON.stringify(presets), (err) => {
log.DEBUG(`${__dirname}`);
fs.writeFile("./config/radioPresets.json", JSON.stringify(presets), (err) => {
// Error checking
if (err) throw err;
log.DEBUG("Write Complete");
@@ -37,7 +40,7 @@ function sanitizeFrequencies(frequenciesArray) {
/**
* Function to convert a string or a float into the integer type needed to be saved
* @param frequency Could be a string, number or float,
* @returns {number|number|*} Return the value to be saved in Hz format ("154875000" = 154.875MHz)
* @returns {number|number|*} Return the value to be saved in Hz format ("154.875"MHz format = "154875000")
*/
function convertFrequencyToHertz(frequency){
// check if the passed value is a number
@@ -50,18 +53,7 @@ function convertFrequencyToHertz(frequency){
else {
log.DEBUG(`${frequency} is a float value.`);
// Convert to a string to remove the decimal in place and then correct the length
frequency = frequency.toString();
// One extra digit checked for the '.' included in the string
if (frequency.length >= 8 && frequency.length <= 10) return parseInt(frequency.replace(".", ""));
else if (frequency.length <= 7) {
// Check to see if the frequency is 1s, 10s or 100s of MHz
let zerosToBeRemoved = 3 - frequency.split(".")[0].length;
// Need to add more 0s since it was in MHz format
let neededZeros = (9 - frequency.length) - zerosToBeRemoved;
frequency = frequency.replace(".", "") + '0'.repeat(neededZeros)
return parseInt(frequency);
}
return converter(frequency).from("MHz").to("Hz");
}
} else {
log.DEBUG(`${frequency} is not a number`);
@@ -76,7 +68,8 @@ function convertFrequencyToHertz(frequency){
* @returns {any} The object containing the different systems the bot is near
*/
exports.getPresets = function getPresets() {
return JSON.parse(fs.readFileSync("../config/radioPresets.json"));
log.DEBUG(`Getting presets from directory: '${__dirname}'`);
return JSON.parse(fs.readFileSync( "./config/radioPresets.json"));
}
/**

1
Server/.env Normal file
View File

@@ -0,0 +1 @@
DEBUG="server:*";

20
Server/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\bin\\www",
"env": {
"DEBUG":"*"
}
}
]
}

View File

@@ -6,6 +6,7 @@
var app = require('../app');
// Debug
const debug = require('debug')('server');
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("server", "www");
var http = require('http');
@@ -89,4 +90,5 @@ function onListening() {
? 'pipe ' + addr
: 'port ' + addr.port;
log.DEBUG('Listening on ' + bind);
debug("testing");
}

View File

@@ -0,0 +1,5 @@
const discordConfig = {
channelID: '367396189529833476'
}
module.exports = discordConfig;

View File

@@ -0,0 +1,129 @@
// Config
const discordConfig = require("../config/discordConfig");
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("server", "adminController");
// Utilities
const mysqlHandler = require("../utilities/mysqlHandler");
const utils = require("../utilities/utils");
const requests = require("../utilities/httpRequests");
/** Get the presets of all online nodes, can be used for functions
*
* @param callback Callback function
* @returns {*} A list of the systems online
*/
async function getPresetsOfOnlineNodes(callback) {
mysqlHandler.getOnlineNodes((onlineNodes) => {
let systems = {};
onlineNodes.forEach(onlineNode => {
systems[onlineNode.id] = utils.BufferToJson(onlineNode.nearbySystems);
});
callback(systems);
});
}
async function requestNodeListenToPreset(preset, nodeId, callback) {
mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{
reqOptions = new requests.requestOptions("/bot/join", "POST", nodeObject.ip, nodeObject.port);
requests.sendHttpRequest(reqOptions, JSON.stringify({
"channelID": discordConfig.channelID,
"presetName": preset
}), (responseObject) => {
callback(responseObject)
});
})
}
async function getNodeBotStatus(nodeId, callback) {
mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{
reqOptions = new requests.requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5);
requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => {
if (responseObject === false) {
// Bot is joined
}
else {
// Bot is free
}
callback(responseObject);
});
});
}
async function requestNodeLeaveServer(nodeId, callback) {
getNodeBotStatus(nodeId, (responseObject) => {
if (responseObject === false) {
// Bot is joined
mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{
reqOptions = new requests.requestOptions("/bot/leave", "POST", nodeObject.ip, nodeObject.port);
requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => {
callback(responseObject);
});
});
}
else {
// Bot is free
callback(false);
}
})
}
/** Return to requests for the presets of all online nodes, cannot be used in functions
*
* @param {*} req Express request parameter
* @param {*} res Express response parameter
*/
exports.getAvailablePresets = async (req, res) => {
await getPresetsOfOnlineNodes((systems) => {
res.status(200).json({
"systemsOnline": systems
});
})
}
/** Request a node to join the server listening to a specific preset
*
* @param {*} req Express request parameter
* @var {*} req.body.preset The preset to join (REQ)
* @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset)
* @param {*} res Express response parameter
*/
exports.joinPreset = async (req, res) => {
if (!req.body.preset) return res.status(400).json("No preset specified");
await getPresetsOfOnlineNodes((systems) => {
const systemsWithSelectedPreset = Object.values(systems).filter(nodePresets => nodePresets.includes(req.body.preset)).length
if (!systemsWithSelectedPreset) return res.status(400).json("No system online with that preset");
if (systemsWithSelectedPreset > 1) {
if (!req.body.nodeId) return res.status(175).json("Multiple locations with the selected channel, please specify a nodeID (nodeId)")
requestNodeListenToPreset(req.body.preset, req.body.nodeId, (responseObject) => {
if (responseObject === false) return res.status(400).json("Timeout reached");
return res.sendStatus(responseObject.statusCode);
});
}
else {
let nodeId;
if (!req.body.nodeId) nodeId = utils.getKeyByArrayValue(systems, req.body.preset);
else nodeId = req.body.nodeId;
requestNodeListenToPreset(req.body.preset, nodeId, (responseObject) => {
if (responseObject === false) return res.status(400).json("Timeout reached");
return res.sendStatus(responseObject.statusCode);
});
}
});
}
/** Request a node to join the server listening to a specific preset
*
* @param {*} req Express request parameter
* @param {*} res Express response parameter
*/
exports.leaveServer = async (req, res) => {
if (!req.body.nodeId) return res.status(400).json("No nodeID specified");
requestNodeLeaveServer(req.body.nodeId, (responseObject) => {
if (responseObject === false) return res.status(400).json("Bot not joined to server");
return res.sendStatus(responseObject.statusCode);
});
}

View File

@@ -0,0 +1,150 @@
//Config
import { getTOKEN, getGuildID, getApplicationID } from './utilities/configHandler.js.js';
// Commands
import ping from './commands/ping.js';
import join from './commands/join.js.js';
import leave from './commands/leave.js.js';
import status from './commands/status.js.js';
// Debug
import ModuleDebugBuilder from "./utilities/moduleDebugBuilder.js.js";
const log = new ModuleDebugBuilder("bot", "app");
// Modules
import { Client, GatewayIntentBits } from 'discord.js';
// Utilities
import registerCommands from './utilities/registerCommands.js.js';
/**
* Host Process Object Builder
*
* This constructor is used to easily construct responses to the host process
*/
class HPOB {
/**
* Build an object to be passed to the host process
* @param command The command to that was run ("Status", "Join", "Leave", "ChgPreSet")
* @param response The response from the command that was run
*/
constructor(command = "Status"||"Join"||"Leave"||"ChgPreSet", response) {
this.cmd = command;
this.msg = response;
}
}
// Create the Discord client
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates
]
});
/**
* When the parent process sends a message, this will interpret the message and act accordingly
*
* DRB IPC Message Structure:
* msg.cmd = The command keyword; Commands covered on the server side
* msg.params = An array containing the parameters for the command
*
*/
process.on('message', (msg) => {
log.DEBUG('IPC Message: ', msg);
const guildID = getGuilds()[0];
log.DEBUG("Guild Name: ", getGuildNameFromID(guildID));
switch (msg.cmd) {
// Check the status of the bot
case "Status":
log.INFO("Status command run from IPC");
status({guildID: guildID, callback: (statusObj) => {
log.DEBUG("Status Object string: ", statusObj);
if (!statusObj.voiceConnection) return process.send(new HPOB("Status", "VDISCONN"));
}});
break;
// Check the params for a server ID and if so join the server
case "Join":
log.INFO("Join command run from IPC");
join({guildID: guildID, guildObj: client.guilds.cache.get(guildID), channelID: msg.params.channelID, callback: () => {
process.send(new HPOB("Join", "AIDS"));
}})
break;
// Check to see if the bot is in a server and if so leave
case "Leave":
log.INFO("Leave command run from IPC");
leave({guildID: guildID, callback: (response) => {
process.send(new HPOB("Leave", response));
}});
break;
default:
// Command doesn't exist
log.INFO("Unknown command run from IPC");
break;
}
})
// When the client is connected and ready
client.on('ready', () =>{
log.INFO(`${client.user.tag} is ready`)
process.send({'msg': "INIT READY"});
});
/*
* Saved For later
client.on('messageCreate', (message) => {
log.DEBUG(`Message Sent by: ${message.author.tag}\n\t'${message.content}'`);
});
*/
// When a command is sent
client.on('interactionCreate', (interaction) => {
if (interaction.isChatInputCommand()){
switch (interaction.commandName) {
case "ping":
ping(interaction);
break;
case "join":
join({ interaction: interaction });
break;
case "leave":
leave({ interaction: interaction });
break;
case "status":
status({ interaction: interaction });
break;
default:
interaction.reply({ content: 'Command not found, try one that exists', fetchReply: true })
.then((message) => log.DEBUG(`Reply sent with content ${message.content}`))
.catch((err) => log.ERROR(err));
}
}
})
function loginBot(){
client.login(getTOKEN());
}
function getGuilds() {
return client.guilds.cache.map(guild => guild.id)
}
function getGuildNameFromID(guildID) {
return client.guilds.cache.map((guild) => {
if (guild.id === guildID) return guild.name;
})[0]
}
function main(){
registerCommands(() => {
loginBot();
});
}
main();
//module.exports = client;

View File

@@ -0,0 +1,6 @@
// Utilities
import { replyToInteraction } from '../utilities/messageHandler.js.js';
export default function ping(interaction) {
return replyToInteraction(interaction, "Pong! I have Aids and now you do too!");
}

View File

@@ -0,0 +1,7 @@
{
"TOKEN": "OTQzNzQyMDQwMjU1MTE1MzA0.Yg3eRA.ZxEbRr55xahjfaUmPY8pmS-RHTY",
"ApplicationID": "943742040255115304",
"GuildID": "367396189529833472",
"DeviceID": "5",
"DeviceName": "VoiceMeeter Aux Output (VB-Audi"
}

View File

@@ -4,17 +4,16 @@ const log = new DebugBuilder("server", "admin");
// Modules
var express = require('express');
var router = express.Router();
var adminController = require("../controllers/adminController");
/* GET */
router.get('/', (req, res) => {
res.send('GET request to the admin')
})
router.get('/presets', adminController.getAvailablePresets);
/* POST */
router.post('/', (req, res) => {
log.DEBUG(req.body);
res.send('POST request to the post')
})
router.post('/join', adminController.joinPreset);
/* POST */
router.post('/leave', adminController.leaveServer);
module.exports = router;

View File

@@ -0,0 +1,68 @@
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("server", "httpRequests");
// Modules
const http = require("http");
exports.requestOptions = class requestOptions {
/**
* Construct an HTTP request
* @param {*} method Method of request to use [GET, POST]
* @param {*} headers Headers of the request, not required but will get filled with default values if not set
* @param {*} hostname The destination of the request
* @param {*} port The port for the destination, will use 3001 by default
*/
constructor(path, method, hostname, port = 3001, headers = undefined, timeout = undefined) {
this.hostname = hostname;
this.path = path;
this.port = port;
this.method = method;
this.timeout = timeout;
if (method === "POST"){
this.headers = headers ?? {
'Content-Type': 'application/json',
}
}
}
}
/**
* Send the HTTP request to the server
* @param requestOptions
* @param data
* @param callback
*/
exports.sendHttpRequest = function sendHttpRequest(requestOptions, data, callback){
log.DEBUG("Sending a request to: ", requestOptions.hostname, requestOptions.port)
// Create the request
const req = http.request(requestOptions, res => {
res.on('data', (data) => {
const responseObject = {
"statusCode": res.statusCode,
"body": data
};
try {
responseObject.body = JSON.parse(responseObject.body)
}
catch (err) {
}
log.DEBUG("Response Object: ", responseObject);
callback(responseObject);
})
}).on('error', err => {
log.ERROR('Error: ', err.message)
// TODO need to handle if the server is down
})
if (requestOptions.timeout) {
req.setTimeout(requestOptions.timeout, () => {
callback(false);
});
}
// Write the data to the request and send it
req.write(data)
req.end()
}

View File

@@ -13,7 +13,9 @@ const nodesTable = `${databaseConfig.database_database}.nodes`;
connection.connect()
// Get all nodes the server knows about regardless of status
/** Get all nodes the server knows about regardless of status
* @param {*} callback Callback function
*/
exports.getAllNodes = (callback) => {
const sqlQuery = `SELECT * FROM ${nodesTable}`
runSQL(sqlQuery, (rows) => {
@@ -21,7 +23,9 @@ exports.getAllNodes = (callback) => {
})
}
// Get all nodes that have the online status set true (are online)
/** Get all nodes that have the online status set true (are online)
* @param callback Callback function
*/
exports.getOnlineNodes = (callback) => {
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE online = 1;`
runSQL(sqlQuery, (rows) => {
@@ -29,7 +33,10 @@ exports.getOnlineNodes = (callback) => {
})
}
// Get info on a node based on ID
/** Get info on a node based on ID
* @param nodeId The ID of the node
* @param callback Callback function
*/
exports.getNodeInfoFromId = (nodeId, callback) => {
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE id = ${nodeId}`
runSQL(sqlQuery, (rows) => {
@@ -39,7 +46,10 @@ exports.getNodeInfoFromId = (nodeId, callback) => {
})
}
// Add a new node to the DB
/** Add a new node to the DB
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.addNewNode = (nodeObject, callback) => {
if (!nodeObject.name) throw new Error("No name provided");
const name = nodeObject.name,
@@ -55,7 +65,10 @@ exports.addNewNode = (nodeObject, callback) => {
})
}
// Update the known info on a node
/** Update the known info on a node
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.updateNodeInfo = (nodeObject, callback) => {
const name = nodeObject.name,
ip = nodeObject.ip,

View File

@@ -6,4 +6,14 @@ exports.JsonToBuffer = (jsonObject) => {
// Convert a buffer from the DB to JSON object
exports.BufferToJson = (buffer) => {
return JSON.parse(buffer.toString());
}
}
/** Find a key in an object by its value
*
* @param {*} object The object to search
* @param {*} value The value to search the arrays in the object for
* @returns The key of the object that contains the value
*/
exports.getKeyByArrayValue = (object, value) => {
return Object.keys(object).find(key => object[key].includes(value));
}