Inital move (minus WIP tests)
This commit is contained in:
183
op25Handler/modules/op25ConfigGenerators.mjs
Normal file
183
op25Handler/modules/op25ConfigGenerators.mjs
Normal file
@@ -0,0 +1,183 @@
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
class OP25ConfigObject {
|
||||
constructor() { }
|
||||
|
||||
async exportToFile(filename) {
|
||||
try {
|
||||
const jsonConfig = JSON.stringify(this, null, 2);
|
||||
await fs.writeFile(filename, jsonConfig);
|
||||
console.log(`Config exported to ${filename}`);
|
||||
} catch (error) {
|
||||
console.error(`Error exporting config to ${filename}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class P25ConfigGenerator extends OP25ConfigObject {
|
||||
constructor({ systemName, controlChannels, tagsFile, whitelistFile = undefined }) {
|
||||
super();
|
||||
console.log("Generating P25 Config for:", systemName);
|
||||
const controlChannelsString = controlChannels.join(',');
|
||||
this.channels = [new channelConfig({
|
||||
"channelName": systemName,
|
||||
"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,
|
||||
"whitelist": whitelistFile
|
||||
});
|
||||
this.audio = new audioConfig({});
|
||||
this.terminal = new terminalConfig({});
|
||||
}
|
||||
}
|
||||
|
||||
export class NBFMConfigGenerator extends OP25ConfigObject {
|
||||
constructor({ systemName, frequency, nbfmSquelch = -70 }) {
|
||||
super();
|
||||
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": 2.0,
|
||||
"number_channels": 1
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
class metadataStreamConfig {
|
||||
constructor({ }) {
|
||||
this.module = "";
|
||||
this.streams = [];
|
||||
}
|
||||
}
|
||||
|
||||
class terminalConfig {
|
||||
constructor({ module = "terminal.py", terminalType = "http:0.0.0.0:8081" }) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
84
op25Handler/op25Handler.mjs
Normal file
84
op25Handler/op25Handler.mjs
Normal file
@@ -0,0 +1,84 @@
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user