Compare commits
21 Commits
3e7b387092
...
#21-op25-r
| Author | SHA1 | Date | |
|---|---|---|---|
| f369e09a0c | |||
|
|
2fbca72f63 | ||
| 0ff11589e9 | |||
| a2abe7e71d | |||
| b23a0768e3 | |||
| 27516a0a25 | |||
|
|
bc0fc23fb0 | ||
|
|
821e4f6a64 | ||
| 0f5ee3b3fb | |||
|
|
54aca6e401 | ||
|
|
851c0cfc47 | ||
|
|
e4ad4f412c | ||
|
|
e6181d51aa | ||
|
|
f706ac89b4 | ||
|
|
e54c80a95b | ||
|
|
c240007c2b | ||
|
|
aa4de9f326 | ||
|
|
1de895c973 | ||
|
|
d773323beb | ||
|
|
5d32a5131a | ||
|
|
c51190f6b6 |
@@ -1,57 +0,0 @@
|
|||||||
import simpleGit from 'simple-git';
|
|
||||||
import { restartService } from './serviceHandler.mjs'
|
|
||||||
import { launchProcess } from './subprocessHandler.mjs'
|
|
||||||
|
|
||||||
const git = simpleGit();
|
|
||||||
|
|
||||||
// Function to check for updates
|
|
||||||
export const checkForUpdates = async () => {
|
|
||||||
try {
|
|
||||||
// Fetch remote changes
|
|
||||||
await git.fetch();
|
|
||||||
|
|
||||||
// Get the latest commit hash
|
|
||||||
const latestCommitHash = await git.revparse(['@{u}']);
|
|
||||||
|
|
||||||
// Compare with the local commit hash
|
|
||||||
const localCommitHash = await git.revparse(['HEAD']);
|
|
||||||
|
|
||||||
if (latestCommitHash !== localCommitHash) {
|
|
||||||
console.log('An update is available. Updating...');
|
|
||||||
|
|
||||||
// Check if there have been any changes to the code
|
|
||||||
const gitStatus = await git.status()
|
|
||||||
console.log(gitStatus);
|
|
||||||
if (gitStatus.modified.length > 0){
|
|
||||||
// There is locally modified code
|
|
||||||
console.log("There is locally modified code, resetting...");
|
|
||||||
await git.stash();
|
|
||||||
await git.reset('hard', ['origin/master']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull the latest changes from the remote repository
|
|
||||||
await git.pull();
|
|
||||||
|
|
||||||
// Run the post-update script
|
|
||||||
console.log('Running post-update script...');
|
|
||||||
await launchProcess("bash", ['./post-update.sh'], true);
|
|
||||||
|
|
||||||
// Restart the application to apply the updates
|
|
||||||
console.log('Update completed successfully. Restarting the application...');
|
|
||||||
restartApplication();
|
|
||||||
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
console.log('The application is up to date.');
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking for updates:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to restart the application
|
|
||||||
export const restartApplication = () => {
|
|
||||||
console.log('Restarting the application...');
|
|
||||||
restartService('discord-radio-bot');
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { P25ConfigGenerator, NBFMConfigGenerator } from './modules/op25ConfigGenerators.mjs';
|
|
||||||
import { getAllPresets } from '../modules/radioPresetHandler.mjs';
|
|
||||||
import { startService, stopService } from '../modules/serviceHandler.mjs';
|
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
let currentSystem = undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates configuration based on the preset and restarts the OP25 service.
|
|
||||||
* @param {Object} preset The preset object containing system configuration.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
const createConfigAndRestartService = async (systemName, preset) => {
|
|
||||||
const { mode, frequencies, trunkFile, whitelistFile } = preset;
|
|
||||||
|
|
||||||
let generator;
|
|
||||||
if (mode === 'p25') {
|
|
||||||
console.log("Using P25 Config Generator based on preset mode", systemName, mode);
|
|
||||||
generator = new P25ConfigGenerator({
|
|
||||||
systemName,
|
|
||||||
controlChannels: frequencies,
|
|
||||||
tagsFile: trunkFile,
|
|
||||||
whitelistFile: whitelistFile !== 'none' ? whitelistFile : undefined
|
|
||||||
});
|
|
||||||
} else if (mode === 'nbfm') {
|
|
||||||
console.log("Using NBFM Config Generator based on preset mode", systemName, mode);
|
|
||||||
generator = new NBFMConfigGenerator({
|
|
||||||
systemName,
|
|
||||||
frequencies,
|
|
||||||
tagsFile: trunkFile
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unsupported mode: ${mode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const op25FilePath = process.env.OP25_FULL_PATH || './'; // Default to current directory if OP25_FULL_PATH is not set
|
|
||||||
const op25ConfigPath = `${op25FilePath}${op25FilePath.endsWith('/') ? 'active.cfg.json' : '/active.cfg.json'}`;
|
|
||||||
await generator.exportToFile(op25ConfigPath);
|
|
||||||
|
|
||||||
// Restart the service
|
|
||||||
await stopService('op25-multi_rx');
|
|
||||||
await startService('op25-multi_rx');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the OP25 service for the specified system.
|
|
||||||
* @param {string} systemName The name of the system to open.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
export const openOP25 = async (systemName) => {
|
|
||||||
currentSystem = systemName;
|
|
||||||
|
|
||||||
// Retrieve preset for the specified system name
|
|
||||||
const presets = await getAllPresets();
|
|
||||||
const preset = presets[systemName];
|
|
||||||
|
|
||||||
console.log("Found preset:", preset);
|
|
||||||
|
|
||||||
if (!preset) {
|
|
||||||
throw new Error(`Preset for system "${systemName}" not found.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createConfigAndRestartService(systemName, preset);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the OP25 service.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
export const closeOP25 = async () => {
|
|
||||||
currentSystem = undefined;
|
|
||||||
await stopService('op25-multi_rx');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current system.
|
|
||||||
* @returns {Promise<string | undefined>} The name of the current system.
|
|
||||||
*/
|
|
||||||
export const getCurrentSystem = async () => {
|
|
||||||
return currentSystem;
|
|
||||||
};
|
|
||||||
21
package.json
21
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "drb-client",
|
"name": "drb-client",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "client.js",
|
"main": "src/client.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha --timeout 10000"
|
"test": "mocha --timeout 10000"
|
||||||
},
|
},
|
||||||
@@ -11,23 +11,24 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.16.1",
|
"@discordjs/voice": "^0.17.0",
|
||||||
|
"axios": "^1.7.7",
|
||||||
"convert-units": "^2.3.4",
|
"convert-units": "^2.3.4",
|
||||||
"discord.js": "^14.14.1",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.4.5",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"prism-media": "^1.3.5",
|
"prism-media": "^1.3.5",
|
||||||
"replace-in-file": "^7.1.0",
|
"replace-in-file": "^7.2.0",
|
||||||
"simple-git": "^3.22.0",
|
"simple-git": "^3.25.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^5.1.0",
|
"chai": "^5.1.1",
|
||||||
"chai-http": "^4.4.0",
|
"chai-http": "^5.0.0",
|
||||||
"mocha": "^10.4.0",
|
"mocha": "^10.4.0",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
setup.sh
7
setup.sh
@@ -94,8 +94,8 @@ rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED
|
|||||||
|
|
||||||
# Getting the Python DAB
|
# Getting the Python DAB
|
||||||
echo "Installing PDAB and Dependencies"
|
echo "Installing PDAB and Dependencies"
|
||||||
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./discordAudioBot/pdab
|
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./pdab
|
||||||
pip3 install -r ./discordAudioBot/pdab/requirements.txt
|
pip3 install -r ./pdab/requirements.txt
|
||||||
|
|
||||||
# Create a systemd service file for the DRB Client
|
# Create a systemd service file for the DRB Client
|
||||||
echo "Adding DRB Node service..."
|
echo "Adding DRB Node service..."
|
||||||
@@ -173,7 +173,6 @@ echo "\n\n\t\tOP25 installation completed!\n\n"
|
|||||||
# Setting permissions on the directories created
|
# Setting permissions on the directories created
|
||||||
cd $ogPwd
|
cd $ogPwd
|
||||||
chown -R 1000:1000 ./*
|
chown -R 1000:1000 ./*
|
||||||
chown 1000:1000 .env
|
|
||||||
echo "Permissions set on the client directory!"
|
echo "Permissions set on the client directory!"
|
||||||
|
|
||||||
echo "\n\n\t\tNode installation Complete!"
|
echo "\n\n\t\tNode installation Complete!"
|
||||||
@@ -184,7 +183,7 @@ read -p "This script has installed all required components for the DRB client. A
|
|||||||
# Convert user input to lowercase for case-insensitive comparison
|
# Convert user input to lowercase for case-insensitive comparison
|
||||||
confirm="${confirm,,}"
|
confirm="${confirm,,}"
|
||||||
|
|
||||||
#echo "To configure the app, please go to http://$nodeIP:$nodePort" # TODO - uncomment when webapp is built
|
echo "To configure the app, please go to http://localhost:3000 after the reboot to configure this app"
|
||||||
echo "Thank you for joining the network!"
|
echo "Thank you for joining the network!"
|
||||||
|
|
||||||
# Prompt user to press any key before rebooting
|
# Prompt user to press any key before rebooting
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { DebugBuilder } from "./modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "client");
|
||||||
|
|
||||||
import { ClientNodeConfig } from './modules/clientObjectDefinitions.mjs';
|
import { ClientNodeConfig } from './modules/clientObjectDefinitions.mjs';
|
||||||
import { initSocketConnection } from './modules/socketClient.mjs';
|
import { initSocketConnection } from './modules/socketClient.mjs';
|
||||||
import { checkForUpdates } from './modules/selfUpdater.mjs'
|
import { checkForUpdates } from './modules/selfUpdater.mjs'
|
||||||
@@ -26,9 +29,9 @@ boot().then((openSocket) => {
|
|||||||
startServer(process.env.WEB_SERVER_PORT || 3000, openSocket);
|
startServer(process.env.WEB_SERVER_PORT || 3000, openSocket);
|
||||||
|
|
||||||
if (!openSocket) {
|
if (!openSocket) {
|
||||||
console.log(openSocket, "Waiting for setup");
|
log.INFO(openSocket, "Waiting for setup");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(openSocket, "Booted Sucessfully");
|
log.INFO(openSocket, "Booted Sucessfully");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
97
src/discordAudioBot/pdab-recorder.py
Normal file
97
src/discordAudioBot/pdab-recorder.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import pyaudio
|
||||||
|
import wave
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
from pydub import AudioSegment
|
||||||
|
from pydub.silence import detect_nonsilent
|
||||||
|
|
||||||
|
# Parameters for the audio stream
|
||||||
|
FORMAT = pyaudio.paInt16
|
||||||
|
CHANNELS = 2
|
||||||
|
RATE = 48000
|
||||||
|
CHUNK = 1024
|
||||||
|
SILENCE_THRESHOLD = -50 # Silence threshold (adjust for noise level)
|
||||||
|
SILENCE_DURATION = 1250 # n miliseconds of silence to stop recording
|
||||||
|
|
||||||
|
audio = pyaudio.PyAudio()
|
||||||
|
|
||||||
|
# Create the recordings directory if it doesn't exist
|
||||||
|
RECORDINGS_DIR = './recordings'
|
||||||
|
if not os.path.exists(RECORDINGS_DIR):
|
||||||
|
os.makedirs(RECORDINGS_DIR)
|
||||||
|
|
||||||
|
def get_filename():
|
||||||
|
"""Generate a filename with the current date and time, stored in the recordings directory."""
|
||||||
|
return os.path.join(RECORDINGS_DIR, datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".wav")
|
||||||
|
|
||||||
|
|
||||||
|
def save_wave_file(filename, audio_segment):
|
||||||
|
"""Save the given audio segment to a WAV file in the recordings directory."""
|
||||||
|
audio_segment.export(filename, format="wav")
|
||||||
|
print(f"Saved recording: {filename}")
|
||||||
|
|
||||||
|
|
||||||
|
def detect_nonsilent_chunk(sound, silence_thresh=SILENCE_THRESHOLD, silence_len=SILENCE_DURATION):
|
||||||
|
"""Detect if there's a nonsilent chunk in the sound."""
|
||||||
|
return detect_nonsilent(sound, min_silence_len=silence_len, silence_thresh=silence_thresh)
|
||||||
|
|
||||||
|
|
||||||
|
def record_transmissions(device_id: int):
|
||||||
|
stream = audio.open(format=FORMAT, channels=CHANNELS,
|
||||||
|
rate=RATE, input=True,
|
||||||
|
frames_per_buffer=CHUNK, input_device_index=device_id )
|
||||||
|
|
||||||
|
frames = []
|
||||||
|
recording = False
|
||||||
|
|
||||||
|
print("Listening for transmissions...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = stream.read(CHUNK)
|
||||||
|
frames.append(data)
|
||||||
|
|
||||||
|
# Convert current audio buffer to AudioSegment for processing
|
||||||
|
sound = AudioSegment(b''.join(frames), sample_width=audio.get_sample_size(FORMAT), frame_rate=RATE, channels=CHANNELS)
|
||||||
|
|
||||||
|
# Detect if there's sound (nonsilent chunk)
|
||||||
|
nonsilent_chunks = detect_nonsilent_chunk(sound)
|
||||||
|
|
||||||
|
if nonsilent_chunks and not recording:
|
||||||
|
print("Transmission detected, starting recording...")
|
||||||
|
recording = True
|
||||||
|
frames = [] # Reset frames to start fresh for this transmission
|
||||||
|
|
||||||
|
elif recording and not nonsilent_chunks:
|
||||||
|
# Check if there were valid nonsilent chunks before trimming
|
||||||
|
if len(nonsilent_chunks) > 0:
|
||||||
|
# Transmission has ended (silence detected for the required duration)
|
||||||
|
if len(frames) > 0:
|
||||||
|
# Save recording without leading/trailing silence
|
||||||
|
trimmed_audio = sound[nonsilent_chunks[0][0]:nonsilent_chunks[-1][1]]
|
||||||
|
filename = get_filename()
|
||||||
|
save_wave_file(filename, trimmed_audio)
|
||||||
|
|
||||||
|
recording = False
|
||||||
|
frames.clear() # Clear frames to prepare for the next transmission
|
||||||
|
print("Recording stopped, waiting for the next transmission...")
|
||||||
|
|
||||||
|
# Optionally: adjust silence threshold if needed
|
||||||
|
# E.g., try increasing silence_thresh to detect lower sounds
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("device_id", type=int, help="The ID of the audio device to use")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
args = parse_arguments()
|
||||||
|
print("Arguments: %s", args)
|
||||||
|
record_transmissions(args.device_id)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Stopping...")
|
||||||
|
finally:
|
||||||
|
audio.terminate()
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
// server.js
|
// server.js
|
||||||
|
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "pdabHandler.mjs");
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { Server } from 'socket.io';
|
import { Server } from 'socket.io';
|
||||||
@@ -16,35 +18,37 @@ let pdabProcess = false;
|
|||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
const port = process.env.PDAB_PORT || 3000;
|
||||||
|
|
||||||
let botCallback;
|
let botCallback;
|
||||||
|
|
||||||
export const initDiscordBotClient = (clientId, callback, runPDAB = true) => {
|
export const initDiscordBotClient = (clientId, callback, runPDAB = true) => {
|
||||||
botCallback = callback;
|
botCallback = callback;
|
||||||
|
|
||||||
if (runPDAB) launchProcess("python", [join(__dirname, "./pdab/main.py"), process.env.AUDIO_DEVICE_ID, clientId, port], false, false, join(__dirname, "./pdab"));
|
if (runPDAB) launchProcess("python", [join(__dirname, "../../pdab/main.py"), process.env.AUDIO_DEVICE_ID, clientId, port], false, false, join(__dirname, "../../pdab"));
|
||||||
pdabProcess = true; // TODO - Make this more dynamic
|
pdabProcess = true; // TODO - Make this more dynamic
|
||||||
|
|
||||||
|
// Start the PDAB Recorder
|
||||||
|
if (runPDAB) launchProcess("python", [join(__dirname, "./pdab-recorder.py"), process.env.AUDIO_DEVICE_ID], false, false, __dirname);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startPdabSocketServer = () => {
|
export const startPdabSocketServer = () => {
|
||||||
const port = process.env.PDAB_PORT || 3000;
|
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
console.log('A user connected');
|
log.INFO('A user connected');
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log('User disconnected');
|
log.INFO('User disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for the discord client ready event
|
// Listen for the discord client ready event
|
||||||
socket.on('discord_ready', (message) => {
|
socket.on('discord_ready', (message) => {
|
||||||
console.log("Message from local client", message);
|
log.INFO("Message from local client", message);
|
||||||
botCallback();
|
botCallback();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port, async () => {
|
server.listen(port, async () => {
|
||||||
console.log(`Server is running on port ${port}`);
|
log.INFO(`Server is running on port ${port}`);
|
||||||
});
|
});
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -62,7 +66,7 @@ export const closePdabSocketServer = () => {
|
|||||||
export const connectToChannel = (channelId) => {
|
export const connectToChannel = (channelId) => {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
io.timeout(25000).emit('join_server', { channelId: channelId }, (status, value) => {
|
io.timeout(25000).emit('join_server', { channelId: channelId }, (status, value) => {
|
||||||
console.log("Status returned from bot:", status, value);
|
log.INFO("Status returned from bot:", status, value);
|
||||||
res(value[0]);
|
res(value[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -72,7 +76,7 @@ export const connectToChannel = (channelId) => {
|
|||||||
export const leaveVoiceChannel = async (guildId) => {
|
export const leaveVoiceChannel = async (guildId) => {
|
||||||
return await new Promise((res) => {
|
return await new Promise((res) => {
|
||||||
io.timeout(25000).emit('leave_server', { guildId: guildId }, (status, clientRemainsOpen) => {
|
io.timeout(25000).emit('leave_server', { guildId: guildId }, (status, clientRemainsOpen) => {
|
||||||
console.log("Discord client remains open?", clientRemainsOpen);
|
log.INFO("Discord client remains open?", clientRemainsOpen);
|
||||||
res(clientRemainsOpen[0])
|
res(clientRemainsOpen[0])
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -89,13 +93,13 @@ export const setDiscordClientPrsense = (system) => {
|
|||||||
|
|
||||||
// Placeholder functions (replace with actual implementation)
|
// Placeholder functions (replace with actual implementation)
|
||||||
export const checkIfConnectedToVC = async (guildId) => {
|
export const checkIfConnectedToVC = async (guildId) => {
|
||||||
console.log("Pdab process var:", pdabProcess);
|
log.INFO("Pdab process var:", pdabProcess);
|
||||||
|
|
||||||
if (!pdabProcess) return false;
|
if (!pdabProcess) return false;
|
||||||
|
|
||||||
return await new Promise((res) => {
|
return await new Promise((res) => {
|
||||||
io.timeout(25000).emit('check_discord_vc_connected', { guildId: guildId }, (status, result) => {
|
io.timeout(25000).emit('check_discord_vc_connected', { guildId: guildId }, (status, result) => {
|
||||||
console.log(`Discord VC connected for guild ${guildId}: ${result}`);
|
log.INFO(`Discord VC connected for guild ${guildId}: ${result}`);
|
||||||
res((result[0]));
|
res((result[0]));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -104,7 +108,7 @@ export const checkIfConnectedToVC = async (guildId) => {
|
|||||||
export const requestDiscordUsername = (guildId) => {
|
export const requestDiscordUsername = (guildId) => {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
io.timeout(25000).emit('request_discord_username', { guildId: guildId }, (status, result) => {
|
io.timeout(25000).emit('request_discord_username', { guildId: guildId }, (status, result) => {
|
||||||
console.log(`Discord username: ${result[0]}`);
|
log.INFO(`Discord username: ${result[0]}`);
|
||||||
res(result[0]);
|
res(result[0]);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -113,7 +117,7 @@ export const requestDiscordUsername = (guildId) => {
|
|||||||
export const checkIfClientIsOpen = () => {
|
export const checkIfClientIsOpen = () => {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
io.timeout(25000).emit('check_client_is_open', (status, result) => {
|
io.timeout(25000).emit('check_client_is_open', (status, result) => {
|
||||||
console.log(`Client is open: ${result}`);
|
log.INFO(`Client is open: ${result}`);
|
||||||
res(result[0])
|
res(result[0])
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -122,7 +126,7 @@ export const checkIfClientIsOpen = () => {
|
|||||||
export const requestDiscordID = () => {
|
export const requestDiscordID = () => {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
io.timeout(25000).emit('request_discord_id', (status, result) => {
|
io.timeout(25000).emit('request_discord_id', (status, result) => {
|
||||||
console.log(`Discord ID: ${result}`);
|
log.INFO(`Discord ID: ${result}`);
|
||||||
res(result[0]);
|
res(result[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "pdabWrappers");
|
||||||
import { connectToChannel, leaveVoiceChannel, checkIfConnectedToVC, initDiscordBotClient, requestDiscordUsername, requestDiscordID, requestDiscordClientClose, closePdabSocketServer, setDiscordClientPrsense, startPdabSocketServer } from './pdabHandler.mjs';
|
import { connectToChannel, leaveVoiceChannel, checkIfConnectedToVC, initDiscordBotClient, requestDiscordUsername, requestDiscordID, requestDiscordClientClose, closePdabSocketServer, setDiscordClientPrsense, startPdabSocketServer } from './pdabHandler.mjs';
|
||||||
import { openOP25, closeOP25 } from '../op25Handler/op25Handler.mjs';
|
import { openOP25, closeOP25 } from '../op25Handler/op25Handler.mjs';
|
||||||
|
|
||||||
@@ -8,38 +10,38 @@ let activeDiscordClient = undefined;
|
|||||||
* @param {object} joinData The object containing all the information to join the server
|
* @param {object} joinData The object containing all the information to join the server
|
||||||
*/
|
*/
|
||||||
export const joinDiscordVC = async (joinData) => {
|
export const joinDiscordVC = async (joinData) => {
|
||||||
console.log("Join requested: ", joinData);
|
log.INFO("Join requested: ", joinData);
|
||||||
const connection = await new Promise(async (res) => {
|
const connection = await new Promise(async (res) => {
|
||||||
// Check if a client already exists
|
// Check if a client already exists
|
||||||
console.log("Checking if there is a client open");
|
log.INFO("Checking if there is a client open");
|
||||||
if (!await checkIfClientIsOpen()) {
|
if (!await checkIfClientIsOpen()) {
|
||||||
console.log("There is no open client, starting it now");
|
log.INFO("There is no open client, starting it now");
|
||||||
await startPdabSocketServer();
|
await startPdabSocketServer();
|
||||||
// Open an instance of OP25
|
// Open an instance of OP25
|
||||||
console.log("Starting OP25")
|
log.INFO("Starting OP25")
|
||||||
openOP25(joinData.system);
|
openOP25(joinData.system);
|
||||||
|
|
||||||
// Open a new client and join the requested channel with the requested ID
|
// Open a new client and join the requested channel with the requested ID
|
||||||
initDiscordBotClient(joinData.clientID, () => {
|
initDiscordBotClient(joinData.clientID, () => {
|
||||||
console.log("Started PDAB");
|
log.INFO("Started PDAB");
|
||||||
|
|
||||||
console.log("Setting the presense of the bot");
|
log.INFO("Setting the presense of the bot");
|
||||||
setDiscordClientPrsense(joinData.system);
|
setDiscordClientPrsense(joinData.system);
|
||||||
|
|
||||||
// Add the client object to the IO instance
|
// Add the client object to the IO instance
|
||||||
console.log("Connecting to channel")
|
log.INFO("Connecting to channel")
|
||||||
connectToChannel(joinData.channelID, (connectionStatus) => {
|
connectToChannel(joinData.channelID, (connectionStatus) => {
|
||||||
console.log("Bot Connected to VC:", connectionStatus);
|
log.INFO("Bot Connected to VC:", connectionStatus);
|
||||||
res(connectionStatus);
|
res(connectionStatus);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Join the requested channel with the requested ID
|
// Join the requested channel with the requested ID
|
||||||
console.log("There is an open client");
|
log.INFO("There is an open client");
|
||||||
|
|
||||||
console.log("Connecting to channel")
|
log.INFO("Connecting to channel")
|
||||||
const connection = connectToChannel(joinData.channelID);
|
const connection = connectToChannel(joinData.channelID);
|
||||||
console.log("Bot Connected to VC::");
|
log.INFO("Bot Connected to VC::");
|
||||||
res(connection);
|
res(connection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -52,12 +54,12 @@ export const joinDiscordVC = async (joinData) => {
|
|||||||
* @param {string} guildId The guild ID to disconnect from VC
|
* @param {string} guildId The guild ID to disconnect from VC
|
||||||
*/
|
*/
|
||||||
export const leaveDiscordVC = async (guildId) => {
|
export const leaveDiscordVC = async (guildId) => {
|
||||||
console.log("Leave requested");
|
log.INFO("Leave requested");
|
||||||
if (await checkIfConnectedToVC(guildId)) {
|
if (await checkIfConnectedToVC(guildId)) {
|
||||||
const clientRemainsOpen = await leaveVoiceChannel(guildId);
|
const clientRemainsOpen = await leaveVoiceChannel(guildId);
|
||||||
console.log("Client should remain open: ", clientRemainsOpen);
|
log.INFO("Client should remain open: ", clientRemainsOpen);
|
||||||
if (!clientRemainsOpen) {
|
if (!clientRemainsOpen) {
|
||||||
console.log("There are no open VC connections");
|
log.INFO("There are no open VC connections");
|
||||||
await closeOP25();
|
await closeOP25();
|
||||||
|
|
||||||
// Close the python client
|
// Close the python client
|
||||||
@@ -75,9 +77,9 @@ export const leaveDiscordVC = async (guildId) => {
|
|||||||
* @returns {boolean} If the node is connected to VC in the given guild
|
* @returns {boolean} If the node is connected to VC in the given guild
|
||||||
*/
|
*/
|
||||||
export const checkIfDiscordVCConnected = async (guildId) => {
|
export const checkIfDiscordVCConnected = async (guildId) => {
|
||||||
console.log("Requested status check");
|
log.INFO("Requested status check");
|
||||||
if (await checkIfConnectedToVC(guildId)) {
|
if (await checkIfConnectedToVC(guildId)) {
|
||||||
console.log("There is an open VC connection");
|
log.INFO("There is an open VC connection");
|
||||||
return (true);
|
return (true);
|
||||||
} else {
|
} else {
|
||||||
return (false);
|
return (false);
|
||||||
@@ -91,7 +93,7 @@ export const checkIfDiscordVCConnected = async (guildId) => {
|
|||||||
* @returns {string} The username of the bot in the given guild's VC
|
* @returns {string} The username of the bot in the given guild's VC
|
||||||
*/
|
*/
|
||||||
export const getDiscordUsername = async (guildId) => {
|
export const getDiscordUsername = async (guildId) => {
|
||||||
console.log("Requested username");
|
log.INFO("Requested username");
|
||||||
if (checkIfClientIsOpen()) {
|
if (checkIfClientIsOpen()) {
|
||||||
return await requestDiscordUsername(guildId)
|
return await requestDiscordUsername(guildId)
|
||||||
} else return (undefined);
|
} else return (undefined);
|
||||||
@@ -102,7 +104,7 @@ export const getDiscordUsername = async (guildId) => {
|
|||||||
* @returns {string} The ID of the active client
|
* @returns {string} The ID of the active client
|
||||||
*/
|
*/
|
||||||
export const getDiscordID = async () => {
|
export const getDiscordID = async () => {
|
||||||
console.log("Requested ID");
|
log.INFO("Requested ID");
|
||||||
if (checkIfClientIsOpen()) {
|
if (checkIfClientIsOpen()) {
|
||||||
return await requestDiscordID();
|
return await requestDiscordID();
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "client.express.setupRoutes");
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { generateUniqueID } from '../../modules/baseUtils.mjs';
|
import { generateUniqueID, ensureDirectoryExists } from '../../modules/baseUtils.mjs';
|
||||||
import { restartApplication } from '../../modules/selfUpdater.mjs'
|
import { restartApplication } from '../../modules/selfUpdater.mjs'
|
||||||
import { launchProcess } from '../../modules/subprocessHandler.mjs'
|
import { launchProcess } from '../../modules/subprocessHandler.mjs'
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ let nodeData = {};
|
|||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
const output = await launchProcess('python', ['./discordAudioBot/pdab/getDevices.py'], true, true)
|
const output = await launchProcess('python', ['./discordAudioBot/pdab/getDevices.py'], true, true)
|
||||||
console.log("Device List", output);
|
log.INFO("Device List", output);
|
||||||
res.render('setup/setup', { deviceList: output });
|
res.render('setup/setup', { deviceList: output });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ router.post('/', (req, res) => {
|
|||||||
// Handle form submission here
|
// Handle form submission here
|
||||||
const { clientName, clientLocation, clientCapabilities, audioDeviceId } = req.body;
|
const { clientName, clientLocation, clientCapabilities, audioDeviceId } = req.body;
|
||||||
|
|
||||||
console.log(clientName, clientLocation, clientCapabilities, audioDeviceId);
|
log.INFO(clientName, clientLocation, clientCapabilities, audioDeviceId);
|
||||||
|
|
||||||
nodeData.clientName = clientName;
|
nodeData.clientName = clientName;
|
||||||
nodeData.clientLocation = clientLocation;
|
nodeData.clientLocation = clientLocation;
|
||||||
@@ -44,11 +46,11 @@ router.post('/add-system', (req, res) => {
|
|||||||
|
|
||||||
// Store system information for later use
|
// Store system information for later use
|
||||||
// For now, let's just log the information
|
// For now, let's just log the information
|
||||||
console.log('System Name:', systemName);
|
log.INFO('System Name:', systemName);
|
||||||
console.log('Frequencies:', frequencies);
|
log.INFO('Frequencies:', frequencies);
|
||||||
console.log('Mode:', mode);
|
log.INFO('Mode:', mode);
|
||||||
console.log('Trunk File:', trunkFile);
|
log.INFO('Trunk File:', trunkFile);
|
||||||
console.log('Whitelist File:', whitelistFile);
|
log.INFO('Whitelist File:', whitelistFile);
|
||||||
|
|
||||||
// Store system information in the array
|
// Store system information in the array
|
||||||
systemsData.push({
|
systemsData.push({
|
||||||
@@ -67,7 +69,7 @@ router.post('/add-system', (req, res) => {
|
|||||||
router.post('/finish-setup', async (req, res) => {
|
router.post('/finish-setup', async (req, res) => {
|
||||||
// Write collected information to .env file
|
// Write collected information to .env file
|
||||||
// For now, let's just log the collected information
|
// For now, let's just log the collected information
|
||||||
console.log('Collected System Information:', nodeData, systemsData);
|
log.INFO('Collected System Information:', nodeData, systemsData);
|
||||||
|
|
||||||
if (!await exportCsv(nodeData)) return res.status(500).send('Error writing to .env file');
|
if (!await exportCsv(nodeData)) return res.status(500).send('Error writing to .env file');
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ export default router;
|
|||||||
|
|
||||||
const exportCsv = (nodeData) => {
|
const exportCsv = (nodeData) => {
|
||||||
const nuid = generateUniqueID();
|
const nuid = generateUniqueID();
|
||||||
console.log(`Generated a new unique ID for this node: '${nuid}'`);
|
log.INFO(`Generated a new unique ID for this node: '${nuid}'`);
|
||||||
const envData = {
|
const envData = {
|
||||||
CLIENT_NUID: nuid,
|
CLIENT_NUID: nuid,
|
||||||
CLIENT_NAME: nodeData.clientName,
|
CLIENT_NAME: nodeData.clientName,
|
||||||
@@ -104,13 +106,29 @@ const exportCsv = (nodeData) => {
|
|||||||
|
|
||||||
// Write to .env file
|
// Write to .env file
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
fs.writeFile('.env', envContent, (err) => {
|
fs.access('.env', fs.constants.F_OK, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error writing to .env file:', err);
|
// File doesn't exist, create it
|
||||||
res(false);
|
fs.writeFile('.env', envContent, (writeErr) => {
|
||||||
|
if (writeErr) {
|
||||||
|
log.ERROR('Error writing to .env file:', writeErr);
|
||||||
|
res(false);
|
||||||
|
} else {
|
||||||
|
log.INFO('.env file created successfully');
|
||||||
|
res(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('.env file updated successfully');
|
// File exists, update it
|
||||||
res(true);
|
fs.writeFile('.env', envContent, (writeErr) => {
|
||||||
|
if (writeErr) {
|
||||||
|
log.ERROR('Error writing to .env file:', writeErr);
|
||||||
|
res(false);
|
||||||
|
} else {
|
||||||
|
log.INFO('.env file updated successfully');
|
||||||
|
res(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -129,15 +147,35 @@ const exportSystems = (systemsData) => {
|
|||||||
whitelistFile: system.whitelistFile || ''
|
whitelistFile: system.whitelistFile || ''
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
ensureDirectoryExists(radioPresetsPath);
|
||||||
|
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
fs.writeFile(radioPresetsPath, JSON.stringify(radioPresetsData, null, 4), (err) => {
|
fs.access(radioPresetsPath, fs.constants.F_OK, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error writing to radioPresets.json:', err);
|
// File doesn't exist, create it
|
||||||
res(false);
|
fs.writeFile(radioPresetsPath, JSON.stringify(radioPresetsData, null, 4), (writeErr) => {
|
||||||
|
if (writeErr) {
|
||||||
|
log.ERROR('Error writing to radioPresets.json:', writeErr);
|
||||||
|
res(false);
|
||||||
|
} else {
|
||||||
|
log.INFO('radioPresets.json created successfully');
|
||||||
|
res(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('radioPresets.json updated successfully');
|
// File exists, update it
|
||||||
res(true);
|
fs.writeFile(radioPresetsPath, JSON.stringify(radioPresetsData, null, 4), (writeErr) => {
|
||||||
|
if (writeErr) {
|
||||||
|
log.ERROR('Error writing to radioPresets.json:', writeErr);
|
||||||
|
res(false);
|
||||||
|
} else {
|
||||||
|
log.INFO('radioPresets.json updated successfully');
|
||||||
|
res(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "client.express.server");
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -19,7 +21,7 @@ const startServer = (port, openSocket) => {
|
|||||||
if (openSocket) isSetupComplete = true;
|
if (openSocket) isSetupComplete = true;
|
||||||
|
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(`Server running on port ${port}`);
|
log.INFO(`Server running on port ${port}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "cliHandler");
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +20,7 @@ export const executeCommand = (command, args) => {
|
|||||||
|
|
||||||
childProcess.stderr.on('data', (data) => {
|
childProcess.stderr.on('data', (data) => {
|
||||||
// Log any errors to stderr
|
// Log any errors to stderr
|
||||||
console.error(data.toString());
|
log.ERROR(data.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
childProcess.on('error', (error) => {
|
childProcess.on('error', (error) => {
|
||||||
@@ -38,7 +38,7 @@ export class ClientNodeConfig {
|
|||||||
_name = process.env.CLIENT_NAME,
|
_name = process.env.CLIENT_NAME,
|
||||||
_location = process.env.CLIENT_LOCATION,
|
_location = process.env.CLIENT_LOCATION,
|
||||||
_nearbySystems = getAllPresets(),
|
_nearbySystems = getAllPresets(),
|
||||||
_capabilities = process.env.CLIENT_CAPABILITIES.split(", "),
|
_capabilities = process.env.CLIENT_CAPABILITIES ? process.env.CLIENT_CAPABILITIES.split(", ") : '',
|
||||||
_serverIp = process.env.SERVER_IP,
|
_serverIp = process.env.SERVER_IP,
|
||||||
_serverPort = process.env.SERVER_PORT,
|
_serverPort = process.env.SERVER_PORT,
|
||||||
}) {
|
}) {
|
||||||
71
src/modules/debugger.mjs
Normal file
71
src/modules/debugger.mjs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Import necessary modules
|
||||||
|
import debug from 'debug';
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
config();
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
import { inspect } from 'util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a given message to the log file
|
||||||
|
* @param {any} logMessage The message to write to the log file
|
||||||
|
* @param {string} appName The app name that created the log entry
|
||||||
|
*/
|
||||||
|
const writeToLog = async (logMessage, appName) => {
|
||||||
|
const logLocation = join(process.env.LOG_LOCATION ?? `./logs/${appName}.log`);
|
||||||
|
|
||||||
|
// Ensure the log directory exists
|
||||||
|
try {
|
||||||
|
await fs.mkdir(dirname(logLocation), { recursive: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the message is a string
|
||||||
|
logMessage = `${String(logMessage)}\n`;
|
||||||
|
|
||||||
|
// Write to the file
|
||||||
|
try {
|
||||||
|
await fs.writeFile(logLocation, logMessage, { encoding: 'utf-8', flag: 'a+' });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the different logging methods for a function
|
||||||
|
* Namespace template = ("[app]:[fileName]:['INFO', 'WARNING', 'DEBUG', 'ERROR']")
|
||||||
|
* @param {string} appName The name of the app to be used in the 'app' portion of the namespace
|
||||||
|
* @param {string} fileName The name of the file calling the builder to be used in the 'fileName' portion of the namespace
|
||||||
|
*/
|
||||||
|
export class DebugBuilder {
|
||||||
|
constructor(appName, fileName) {
|
||||||
|
const buildLogger = (level) => (...messageParts) => {
|
||||||
|
const logger = debug(`${appName}:${fileName}:${level}`);
|
||||||
|
logger(messageParts);
|
||||||
|
|
||||||
|
const timeStamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' });
|
||||||
|
const message = `${timeStamp} - ${appName}:${fileName}:${level}\t-\t${messageParts.map(part => inspect(part)).join(' ')}`;
|
||||||
|
|
||||||
|
// Write to console
|
||||||
|
console.log(message);
|
||||||
|
|
||||||
|
// Write to logfile
|
||||||
|
writeToLog(message, appName);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.INFO = buildLogger('INFO');
|
||||||
|
this.DEBUG = buildLogger('DEBUG');
|
||||||
|
this.VERBOSE = buildLogger('VERBOSE');
|
||||||
|
this.WARN = buildLogger('WARNING');
|
||||||
|
this.ERROR = (...messageParts) => {
|
||||||
|
buildLogger('ERROR')(...messageParts);
|
||||||
|
|
||||||
|
if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) {
|
||||||
|
writeToLog("!--- EXITING ---!", appName);
|
||||||
|
const exitDelay = parseInt(process.env.EXIT_ON_ERROR_DELAY, 10) || 0;
|
||||||
|
setTimeout(() => process.exit(1), exitDelay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
// Modules
|
// Modules
|
||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "radioPresetsHandler");
|
||||||
import { writeFile, existsSync, readFileSync } from 'fs';
|
import { writeFile, existsSync, readFileSync } from 'fs';
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
import { ensureDirectoryExists } from "./baseUtils.mjs";
|
import { ensureDirectoryExists } from "./baseUtils.mjs";
|
||||||
@@ -16,12 +18,12 @@ const configFilePath = process.env.CONFIG_PATH;
|
|||||||
* @param {function} callback The function to be called when this wrapper completes
|
* @param {function} callback The function to be called when this wrapper completes
|
||||||
*/
|
*/
|
||||||
const writePresets = async (presets, callback = undefined) => {
|
const writePresets = async (presets, callback = undefined) => {
|
||||||
console.log(`${__dirname}`);
|
log.INFO(`${__dirname}`);
|
||||||
await ensureDirectoryExists(configFilePath);
|
await ensureDirectoryExists(configFilePath);
|
||||||
writeFile(configFilePath, JSON.stringify(presets), (err) => {
|
writeFile(configFilePath, JSON.stringify(presets), (err) => {
|
||||||
// Error checking
|
// Error checking
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
console.log("Write Complete");
|
log.INFO("Write Complete");
|
||||||
if (callback) callback(); else return
|
if (callback) callback(); else return
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -38,7 +40,7 @@ const sanitizeFrequencies = async (frequenciesArray) => {
|
|||||||
sanitizedFrequencyArray.push(convertFrequencyToHertz(freq));
|
sanitizedFrequencyArray.push(convertFrequencyToHertz(freq));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sanitized Frequency Array", sanitizedFrequencyArray);
|
log.INFO("Sanitized Frequency Array", sanitizedFrequencyArray);
|
||||||
return sanitizedFrequencyArray;
|
return sanitizedFrequencyArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,19 +53,19 @@ const convertFrequencyToHertz = async (frequency) => {
|
|||||||
// check if the passed value is a number
|
// check if the passed value is a number
|
||||||
if (typeof frequency == 'number' && !isNaN(frequency)) {
|
if (typeof frequency == 'number' && !isNaN(frequency)) {
|
||||||
if (Number.isInteger(frequency)) {
|
if (Number.isInteger(frequency)) {
|
||||||
console.log(`${frequency} is an integer.`);
|
log.INFO(`${frequency} is an integer.`);
|
||||||
// Check to see if the frequency has the correct length
|
// Check to see if the frequency has the correct length
|
||||||
if (frequency >= 1000000) return frequency
|
if (frequency >= 1000000) return frequency
|
||||||
if (frequency >= 100 && frequency <= 999) return frequency * 1000000
|
if (frequency >= 100 && frequency <= 999) return frequency * 1000000
|
||||||
console.log("Frequency hasn't matched filters: ", frequency);
|
log.INFO("Frequency hasn't matched filters: ", frequency);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(`${frequency} is a float value.`);
|
log.INFO(`${frequency} is a float value.`);
|
||||||
// Convert to a string to remove the decimal in place and then correct the length
|
// Convert to a string to remove the decimal in place and then correct the length
|
||||||
return parseInt(converter(frequency).from("MHz").to("Hz"));
|
return parseInt(converter(frequency).from("MHz").to("Hz"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`${frequency} is not a number`);
|
log.INFO(`${frequency} is not a number`);
|
||||||
frequency = convertFrequencyToHertz(parseFloat(frequency));
|
frequency = convertFrequencyToHertz(parseFloat(frequency));
|
||||||
|
|
||||||
return parseInt(frequency)
|
return parseInt(frequency)
|
||||||
@@ -75,8 +77,11 @@ const convertFrequencyToHertz = async (frequency) => {
|
|||||||
* @returns {any} The object containing the different systems the bot is near
|
* @returns {any} The object containing the different systems the bot is near
|
||||||
*/
|
*/
|
||||||
export const getAllPresets = () => {
|
export const getAllPresets = () => {
|
||||||
|
// If the config path hasn't been set by setup
|
||||||
|
if (!configFilePath) return {};
|
||||||
|
|
||||||
const presetDir = resolve(configFilePath);
|
const presetDir = resolve(configFilePath);
|
||||||
console.log(`Getting presets from directory: '${presetDir}'`);
|
log.INFO(`Getting presets from directory: '${presetDir}'`);
|
||||||
if (existsSync(presetDir)) return JSON.parse(readFileSync(presetDir));
|
if (existsSync(presetDir)) return JSON.parse(readFileSync(presetDir));
|
||||||
else return {};
|
else return {};
|
||||||
}
|
}
|
||||||
63
src/modules/selfUpdater.mjs
Normal file
63
src/modules/selfUpdater.mjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "selfUpdater");
|
||||||
|
import simpleGit from 'simple-git';
|
||||||
|
import { restartService } from './serviceHandler.mjs';
|
||||||
|
import { launchProcess } from './subprocessHandler.mjs';
|
||||||
|
|
||||||
|
const git = simpleGit();
|
||||||
|
|
||||||
|
// Function to check for updates
|
||||||
|
export const checkForUpdates = async () => {
|
||||||
|
try {
|
||||||
|
// Fetch remote changes
|
||||||
|
await git.fetch();
|
||||||
|
|
||||||
|
// Get the current branch
|
||||||
|
const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
||||||
|
log.INFO(`Current branch is ${currentBranch}`);
|
||||||
|
|
||||||
|
// Get the latest commit hash for the current branch
|
||||||
|
const latestCommitHash = await git.revparse([`${currentBranch}@{u}`]);
|
||||||
|
|
||||||
|
// Compare with the local commit hash
|
||||||
|
const localCommitHash = await git.revparse(['HEAD']);
|
||||||
|
|
||||||
|
if (latestCommitHash !== localCommitHash) {
|
||||||
|
log.INFO(`An update is available on branch ${currentBranch}. Updating...`);
|
||||||
|
|
||||||
|
// Check if there have been any changes to the code
|
||||||
|
const gitStatus = await git.status();
|
||||||
|
log.INFO(gitStatus);
|
||||||
|
if (gitStatus.modified.length > 0){
|
||||||
|
// There is locally modified code
|
||||||
|
log.INFO("There is locally modified code, stashing changes...");
|
||||||
|
await git.stash();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are on the correct branch and pull the latest changes
|
||||||
|
await git.checkout(currentBranch);
|
||||||
|
await git.pull('origin', currentBranch);
|
||||||
|
|
||||||
|
// Run the post-update script
|
||||||
|
log.INFO('Running post-update script...');
|
||||||
|
await launchProcess("bash", ['./post-update.sh'], true);
|
||||||
|
|
||||||
|
// Restart the application to apply the updates
|
||||||
|
log.INFO('Update completed successfully. Restarting the application...');
|
||||||
|
restartApplication();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.INFO('The application is up to date.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.ERROR('Error checking for updates:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to restart the application
|
||||||
|
export const restartApplication = () => {
|
||||||
|
log.INFO('Restarting the application...');
|
||||||
|
restartService('discord-radio-bot');
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "serviceHandler");
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +11,7 @@ const executeCommand = (command) => {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(command, (error, stdout, stderr) => {
|
exec(command, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(`Command failed with error: ${error.message}`);
|
log.ERROR(`Command failed with error: ${error.message}`);
|
||||||
resolve({ stdout, stderr });
|
resolve({ stdout, stderr });
|
||||||
} else {
|
} else {
|
||||||
resolve({ stdout, stderr });
|
resolve({ stdout, stderr });
|
||||||
@@ -27,7 +29,7 @@ export const startService = async (serviceName) => {
|
|||||||
try {
|
try {
|
||||||
await executeCommand(`sudo systemctl start ${serviceName}.service`);
|
await executeCommand(`sudo systemctl start ${serviceName}.service`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to start service: ${error.message}`);
|
log.ERROR(`Failed to start service: ${error.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ export const restartService = async (serviceName) => {
|
|||||||
try {
|
try {
|
||||||
await executeCommand(`sudo systemctl restart ${serviceName}.service`);
|
await executeCommand(`sudo systemctl restart ${serviceName}.service`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to restart service: ${error.message}`);
|
log.ERROR(`Failed to restart service: ${error.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,6 +55,6 @@ export const stopService = async (serviceName) => {
|
|||||||
try {
|
try {
|
||||||
await executeCommand(`sudo systemctl stop ${serviceName}.service`);
|
await executeCommand(`sudo systemctl stop ${serviceName}.service`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to stop service: ${error.message}`);
|
log.ERROR(`Failed to stop service: ${error.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "socketClient");
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
import { logIntoServerWrapper, nodeCheckStatus, nodeJoinServer, nodeLeaveServer, nodeGetUsername, nodeCheckDiscordClientStatus, nodeCheckCurrentSystem, nodeUpdate, nodeGetDiscordID } from "./socketClientWrappers.mjs";
|
import { logIntoServerWrapper, nodeCheckStatus, nodeJoinServer, nodeLeaveServer, nodeGetUsername, nodeCheckDiscordClientStatus, nodeCheckCurrentSystem, nodeUpdate, nodeGetDiscordID } from "./socketClientWrappers.mjs";
|
||||||
|
|
||||||
@@ -14,13 +16,13 @@ export const initSocketConnection = async (localNodeConfig) => {
|
|||||||
// Socket Events ('system' events persay)
|
// Socket Events ('system' events persay)
|
||||||
// When the socket connects to the node server
|
// When the socket connects to the node server
|
||||||
socket.on('connect', async () => {
|
socket.on('connect', async () => {
|
||||||
console.log('Connected to the server');
|
log.INFO('Connected to the server');
|
||||||
await logIntoServerWrapper(socket, localNodeConfig);
|
await logIntoServerWrapper(socket, localNodeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
// When the socket disconnects from the node server
|
// When the socket disconnects from the node server
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log('Disconnected from the server');
|
log.INFO('Disconnected from the server');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Node events/commands
|
// Node events/commands
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "subprocessHandler");
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@@ -35,14 +37,14 @@ export const launchProcess = (processName, args, waitForClose = false, returnOut
|
|||||||
// Get the stdout from the child process
|
// Get the stdout from the child process
|
||||||
childProcess.stdout.setEncoding('utf8');
|
childProcess.stdout.setEncoding('utf8');
|
||||||
childProcess.stdout.on('data', (data) => {
|
childProcess.stdout.on('data', (data) => {
|
||||||
if (process.env.NODE_ENV === "development") console.log(`Data from ${processName}:`, data);
|
if (process.env.NODE_ENV === "development") log.INFO(`Data from ${processName}:`, data);
|
||||||
scriptOutput += data.toString();
|
scriptOutput += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the stderr from the child process
|
// Get the stderr from the child process
|
||||||
childProcess.stderr.setEncoding('utf8');
|
childProcess.stderr.setEncoding('utf8');
|
||||||
childProcess.stderr.on('data', (data) => {
|
childProcess.stderr.on('data', (data) => {
|
||||||
if (process.env.NODE_ENV === "development") console.log(`Data from ${processName}:`, data);
|
if (process.env.NODE_ENV === "development") log.INFO(`Data from ${processName}:`, data);
|
||||||
scriptOutput += data.toString();
|
scriptOutput += data.toString();
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -50,8 +52,8 @@ export const launchProcess = (processName, args, waitForClose = false, returnOut
|
|||||||
childProcess.on('exit', (code, signal) => {
|
childProcess.on('exit', (code, signal) => {
|
||||||
// Remove reference to the process when it exits
|
// Remove reference to the process when it exits
|
||||||
delete runningProcesses[processName];
|
delete runningProcesses[processName];
|
||||||
console.log(`${processName} process exited with code ${code} and signal ${signal}`);
|
log.INFO(`${processName} process exited with code ${code} and signal ${signal}`);
|
||||||
console.log("Child process console output: ", scriptOutput);
|
log.INFO("Child process console output: ", scriptOutput);
|
||||||
// Return the full script output if requested
|
// Return the full script output if requested
|
||||||
if (returnOutput === true) {
|
if (returnOutput === true) {
|
||||||
return res(scriptOutput)
|
return res(scriptOutput)
|
||||||
@@ -64,9 +66,9 @@ export const launchProcess = (processName, args, waitForClose = false, returnOut
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${processName} process started.`);
|
log.INFO(`${processName} process started.`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${processName} process is already running.`);
|
log.INFO(`${processName} process is already running.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,9 +99,9 @@ export const killProcess = (processName) => {
|
|||||||
const childProcess = runningProcesses[processName];
|
const childProcess = runningProcesses[processName];
|
||||||
if (childProcess) {
|
if (childProcess) {
|
||||||
childProcess.kill();
|
childProcess.kill();
|
||||||
console.log(`${processName} process killed.`);
|
log.INFO(`${processName} process killed.`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${processName} process is not running.`);
|
log.INFO(`${processName} process is not running.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
// Modules
|
// Modules
|
||||||
|
import { DebugBuilder } from "./debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "updateConfig");
|
||||||
import replace from 'replace-in-file';
|
import replace from 'replace-in-file';
|
||||||
|
|
||||||
class Options {
|
class Options {
|
||||||
@@ -19,7 +21,7 @@ class Options {
|
|||||||
export const updateId = async (updatedId) => {
|
export const updateId = async (updatedId) => {
|
||||||
await updateConfig('CLIENT_NUID', updatedId);
|
await updateConfig('CLIENT_NUID', updatedId);
|
||||||
process.env.CLIENT_NUID = updatedId;
|
process.env.CLIENT_NUID = updatedId;
|
||||||
console.log("Updated NUID to: ", updatedId);
|
log.INFO("Updated NUID to: ", updatedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +51,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
|||||||
this.updateConfig('CLIENT_NAME', newConfigObject.name);
|
this.updateConfig('CLIENT_NAME', newConfigObject.name);
|
||||||
updatedKeys.push({ 'CLIENT_NAME': newConfigObject.name });
|
updatedKeys.push({ 'CLIENT_NAME': newConfigObject.name });
|
||||||
process.env.CLIENT_NAME = newConfigObject.name;
|
process.env.CLIENT_NAME = newConfigObject.name;
|
||||||
console.log("Updated name to: ", newConfigObject.name);
|
log.INFO("Updated name to: ", newConfigObject.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (configKeys.includes("ip")) {
|
if (configKeys.includes("ip")) {
|
||||||
@@ -57,7 +59,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
|||||||
this.updateConfig('CLIENT_IP', newConfigObject.ip);
|
this.updateConfig('CLIENT_IP', newConfigObject.ip);
|
||||||
updatedKeys.push({ 'CLIENT_IP': newConfigObject.ip });
|
updatedKeys.push({ 'CLIENT_IP': newConfigObject.ip });
|
||||||
process.env.CLIENT_IP = newConfigObject.ip;
|
process.env.CLIENT_IP = newConfigObject.ip;
|
||||||
console.log("Updated ip to: ", newConfigObject.ip);
|
log.INFO("Updated ip to: ", newConfigObject.ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (configKeys.includes("port")) {
|
if (configKeys.includes("port")) {
|
||||||
@@ -65,7 +67,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
|||||||
this.updateConfig('CLIENT_PORT', newConfigObject.port);
|
this.updateConfig('CLIENT_PORT', newConfigObject.port);
|
||||||
updatedKeys.push({ 'CLIENT_PORT': newConfigObject.port });
|
updatedKeys.push({ 'CLIENT_PORT': newConfigObject.port });
|
||||||
process.env.CLIENT_PORT = newConfigObject.port;
|
process.env.CLIENT_PORT = newConfigObject.port;
|
||||||
console.log("Updated port to: ", newConfigObject.port);
|
log.INFO("Updated port to: ", newConfigObject.port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (configKeys.includes("location")) {
|
if (configKeys.includes("location")) {
|
||||||
@@ -73,7 +75,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
|||||||
this.updateConfig('CLIENT_LOCATION', newConfigObject.location);
|
this.updateConfig('CLIENT_LOCATION', newConfigObject.location);
|
||||||
updatedKeys.push({ 'CLIENT_LOCATION': newConfigObject.location });
|
updatedKeys.push({ 'CLIENT_LOCATION': newConfigObject.location });
|
||||||
process.env.CLIENT_LOCATION = newConfigObject.location;
|
process.env.CLIENT_LOCATION = newConfigObject.location;
|
||||||
console.log("Updated location to: ", newConfigObject.location);
|
log.INFO("Updated location to: ", newConfigObject.location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
|
|||||||
export function updateConfig (key, value) {
|
export function updateConfig (key, value) {
|
||||||
const options = new Options(key, value);
|
const options = new Options(key, value);
|
||||||
|
|
||||||
console.log("Options:", options);
|
log.INFO("Options:", options);
|
||||||
|
|
||||||
updateConfigFile(options, (updatedFiles) => {
|
updateConfigFile(options, (updatedFiles) => {
|
||||||
// Do Something
|
// Do Something
|
||||||
@@ -102,8 +104,8 @@ export function updateConfig (key, value) {
|
|||||||
*/
|
*/
|
||||||
function updateConfigFile(options, callback) {
|
function updateConfigFile(options, callback) {
|
||||||
replace(options, (error, changedFiles) => {
|
replace(options, (error, changedFiles) => {
|
||||||
if (error) return console.error('Error occurred:', error);
|
if (error) return log.ERROR('Error occurred:', error);
|
||||||
console.log('Updated config file: ', changedFiles);
|
log.INFO('Updated config file: ', changedFiles);
|
||||||
callback(changedFiles);
|
callback(changedFiles);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "op25ConfigGenerator");
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
class OP25ConfigObject {
|
class OP25ConfigObject {
|
||||||
@@ -7,9 +9,9 @@ class OP25ConfigObject {
|
|||||||
try {
|
try {
|
||||||
const jsonConfig = JSON.stringify(this, null, 2);
|
const jsonConfig = JSON.stringify(this, null, 2);
|
||||||
await fs.writeFile(filename, jsonConfig);
|
await fs.writeFile(filename, jsonConfig);
|
||||||
console.log(`Config exported to ${filename}`);
|
log.INFO(`Config exported to ${filename}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error exporting config to ${filename}: ${error}`);
|
log.ERROR(`Error exporting config to ${filename}: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +19,7 @@ class OP25ConfigObject {
|
|||||||
export class P25ConfigGenerator extends OP25ConfigObject {
|
export class P25ConfigGenerator extends OP25ConfigObject {
|
||||||
constructor({ systemName, controlChannels, tagsFile, whitelistFile = undefined }) {
|
constructor({ systemName, controlChannels, tagsFile, whitelistFile = undefined }) {
|
||||||
super();
|
super();
|
||||||
console.log("Generating P25 Config for:", systemName);
|
log.INFO("Generating P25 Config for:", systemName);
|
||||||
const controlChannelsString = controlChannels.join(',');
|
const controlChannelsString = controlChannels.join(',');
|
||||||
this.channels = [new channelConfig({
|
this.channels = [new channelConfig({
|
||||||
"channelName": systemName,
|
"channelName": systemName,
|
||||||
160
src/op25Handler/op25Handler.mjs
Normal file
160
src/op25Handler/op25Handler.mjs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||||
|
const log = new DebugBuilder("client", "op25Handler");
|
||||||
|
import { P25ConfigGenerator, NBFMConfigGenerator } from './modules/op25ConfigGenerators.mjs';
|
||||||
|
import { getAllPresets } from '../modules/radioPresetHandler.mjs';
|
||||||
|
import { startService, stopService } from '../modules/serviceHandler.mjs';
|
||||||
|
import axios from 'axios'; // Import axios for HTTP requests
|
||||||
|
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
let currentSystem = undefined;
|
||||||
|
let crashDetectionInterval; // Variable to store the crash detection interval ID
|
||||||
|
|
||||||
|
// Sleep utility to add delays between retries
|
||||||
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the health of the OP25 web portal by making an HTTP HEAD request.
|
||||||
|
* If the portal does not respond or there is an issue, retries a specified number of times.
|
||||||
|
* If all retry attempts fail, it restarts the OP25 service.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function checkServiceHealth
|
||||||
|
* @returns {Promise<void>} Resolves if the web portal is healthy or after the restart process is triggered.
|
||||||
|
* @throws Will log errors related to the health check or service restart.
|
||||||
|
*/
|
||||||
|
const checkServiceHealth = async () => {
|
||||||
|
try {
|
||||||
|
log.INFO("Checking OP25 web portal health...");
|
||||||
|
// Perform an HTTP HEAD request to the web portal with a 5-second timeout
|
||||||
|
await axios({ method: "get", url: 'http://localhost:8081', timeout: 5000 });
|
||||||
|
log.INFO("Web portal is healthy.");
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'ECONNABORTED') {
|
||||||
|
log.ERROR("Request timed out. The web portal took too long to respond.");
|
||||||
|
} else if (error.response) {
|
||||||
|
log.ERROR(`Web portal responded with status ${error.response.status}: ${error.response.statusText}`);
|
||||||
|
} else if (error.request) {
|
||||||
|
log.ERROR("No response received from web portal.");
|
||||||
|
} else {
|
||||||
|
log.ERROR(`Unexpected error occurred: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry mechanism
|
||||||
|
const retryAttempts = 3;
|
||||||
|
const delayBetweenRetries = 3000; // 3 seconds delay
|
||||||
|
|
||||||
|
for (let i = 1; i <= retryAttempts; i++) {
|
||||||
|
log.INFO(`Retrying to check web portal health... Attempt ${i}/${retryAttempts}`);
|
||||||
|
try {
|
||||||
|
await sleep(delayBetweenRetries); // Add delay before retrying
|
||||||
|
await axios({ method: "get", url: 'http://localhost:8081', timeout: 5000 });
|
||||||
|
log.INFO("Web portal is healthy on retry.");
|
||||||
|
return;
|
||||||
|
} catch (retryError) {
|
||||||
|
log.ERROR(`Retry ${i} failed: ${retryError.message}`);
|
||||||
|
if (i === retryAttempts) {
|
||||||
|
log.ERROR("All retry attempts failed. Restarting the service...");
|
||||||
|
await restartOp25();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates configuration based on the preset and restarts the OP25 service.
|
||||||
|
* @param {Object} preset The preset object containing system configuration.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const createConfigAndRestartService = async (systemName, preset) => {
|
||||||
|
const { mode, frequencies, trunkFile, whitelistFile } = preset;
|
||||||
|
|
||||||
|
let generator;
|
||||||
|
if (mode === 'p25') {
|
||||||
|
log.INFO("Using P25 Config Generator based on preset mode", systemName, mode);
|
||||||
|
generator = new P25ConfigGenerator({
|
||||||
|
systemName,
|
||||||
|
controlChannels: frequencies,
|
||||||
|
tagsFile: trunkFile,
|
||||||
|
whitelistFile: whitelistFile !== 'none' ? whitelistFile : undefined
|
||||||
|
});
|
||||||
|
} else if (mode === 'nbfm') {
|
||||||
|
log.INFO("Using NBFM Config Generator based on preset mode", systemName, mode);
|
||||||
|
generator = new NBFMConfigGenerator({
|
||||||
|
systemName,
|
||||||
|
frequencies,
|
||||||
|
tagsFile: trunkFile
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported mode: ${mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const op25FilePath = process.env.OP25_FULL_PATH || './'; // Default to current directory if OP25_FULL_PATH is not set
|
||||||
|
const op25ConfigPath = `${op25FilePath}${op25FilePath.endsWith('/') ? 'active.cfg.json' : '/active.cfg.json'}`;
|
||||||
|
await generator.exportToFile(op25ConfigPath);
|
||||||
|
|
||||||
|
await restartOp25();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the OP25 service for the specified system.
|
||||||
|
* @param {string} systemName The name of the system to open.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const openOP25 = async (systemName) => {
|
||||||
|
currentSystem = systemName;
|
||||||
|
|
||||||
|
// Retrieve preset for the specified system name
|
||||||
|
const presets = await getAllPresets();
|
||||||
|
const preset = presets[systemName];
|
||||||
|
|
||||||
|
log.INFO("Found preset:", preset);
|
||||||
|
|
||||||
|
if (!preset) {
|
||||||
|
throw new Error(`Preset for system "${systemName}" not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await createConfigAndRestartService(systemName, preset);
|
||||||
|
|
||||||
|
// Start OP25 crash detection
|
||||||
|
if (!crashDetectionInterval) {
|
||||||
|
crashDetectionInterval = setInterval(checkServiceHealth, 30000); // Check every 30 seconds
|
||||||
|
log.INFO("Started crash detection.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restarts the OP25 service without changing the config.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const restartOp25 = async () => {
|
||||||
|
// Restart the service
|
||||||
|
await stopService('op25-multi_rx');
|
||||||
|
await startService('op25-multi_rx');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the OP25 service.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const closeOP25 = async () => {
|
||||||
|
currentSystem = undefined;
|
||||||
|
await stopService('op25-multi_rx');
|
||||||
|
|
||||||
|
// Stop crash detection
|
||||||
|
if (crashDetectionInterval) {
|
||||||
|
clearInterval(crashDetectionInterval);
|
||||||
|
crashDetectionInterval = null;
|
||||||
|
log.INFO("Stopped crash detection.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current system.
|
||||||
|
* @returns {Promise<string | undefined>} The name of the current system.
|
||||||
|
*/
|
||||||
|
export const getCurrentSystem = async () => {
|
||||||
|
return currentSystem;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user