renamed gui_vue to gui

This commit is contained in:
DJ2LS 2023-10-22 10:20:32 +02:00
parent 2684d3be39
commit ff82b0e0f4
105 changed files with 52 additions and 18365 deletions

View File

@ -16,13 +16,11 @@ updates:
# Maintain dependencies for npm
- package-ecosystem: "npm"
directory: "/gui_vue"
directory: "/gui"
schedule:
interval: "daily"
target-branch: "ls-gui-single-pager"
# Maintain dependencies for pip
- package-ecosystem: "pip"
directory: "/"

10
.gitignore vendored
View File

@ -24,9 +24,9 @@ coverage.sh
coverage.xml
#ignore node_modules
/gui_vue/node_modules/
/gui/node_modules/
#Ignore gui_vue build items
/gui_vue/dist
/gui_vue/release
/gui_vue/dist-electron
#Ignore gui build items
/gui/dist
/gui/release
/gui/dist-electron

104
gui/.gitignore vendored
View File

@ -1,104 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

View File

@ -1,347 +0,0 @@
var net = require("net");
const path = require("path");
const { ipcRenderer } = require("electron");
const log = require("electron-log");
const daemonLog = log.scope("daemon");
// https://stackoverflow.com/a/26227660
var appDataFolder =
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Application Support"
: process.env.HOME + "/.config");
var configFolder = path.join(appDataFolder, "FreeDATA");
var configPath = path.join(configFolder, "config.json");
const config = require(configPath);
var daemon = new net.Socket();
var socketchunk = ""; // Current message, per connection.
// global to keep track of daemon connection error emissions
var daemonShowConnectStateError = 1;
// global for storing ip information
var daemon_port = config.daemon_port;
var daemon_host = config.daemon_host;
setTimeout(connectDAEMON, 500);
function connectDAEMON() {
if (daemonShowConnectStateError == 1) {
daemonLog.info("connecting to daemon");
}
//clear message buffer after reconnecting or initial connection
socketchunk = "";
daemon.connect(config.daemon_port, config.daemon_host);
//client.setTimeout(5000);
}
daemon.on("connect", function (err) {
daemonLog.info("daemon connection established");
let Data = {
daemon_connection: daemon.readyState,
};
ipcRenderer.send("request-update-daemon-connection", Data);
daemonShowConnectStateError = 1;
});
daemon.on("error", function (err) {
if (daemonShowConnectStateError == 1) {
daemonLog.error("daemon connection error");
daemonLog.info("Make sure the daemon is started.");
daemonLog.info('Run "python daemon.py" in the tnc directory.');
daemonShowConnectStateError = 0;
}
setTimeout(connectDAEMON, 500);
daemon.destroy();
let Data = {
daemon_connection: daemon.readyState,
};
ipcRenderer.send("request-update-daemon-connection", Data);
});
/*
client.on('close', function(data) {
console.log(' TNC connection closed');
setTimeout(connectTNC, 2000)
let Data = {
daemon_connection: daemon.readyState,
};
ipcRenderer.send('request-update-daemon-connection', Data);
});
*/
daemon.on("end", function (data) {
daemonLog.warn("daemon connection ended");
daemon.destroy();
setTimeout(connectDAEMON, 500);
let Data = {
daemon_connection: daemon.readyState,
};
ipcRenderer.send("request-update-daemon-connection", Data);
});
//exports.writeCommand = function(command){
writeDaemonCommand = function (command) {
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
// if socket opened, we are able to run commands
if (daemon.readyState == "open") {
//uiMain.setDAEMONconnection('open')
daemon.write(command + "\n");
}
if (daemon.readyState == "closed") {
//uiMain.setDAEMONconnection('closed')
}
if (daemon.readyState == "opening") {
//uiMain.setDAEMONconnection('opening')
}
let Data = {
daemon_connection: daemon.readyState,
};
ipcRenderer.send("request-update-daemon-connection", Data);
};
// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in"
daemon.on("data", function (socketdata) {
/*
inspired by:
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
*/
socketdata = socketdata.toString("utf8"); // convert data to string
socketchunk += socketdata; // append data to buffer so we can stick long data together
// check if we received begin and end of json data
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
var data = "";
// split data into chunks if we received multiple commands
socketchunk = socketchunk.split("\n");
data = JSON.parse(socketchunk[0]);
// search for empty entries in socketchunk and remove them
for (i = 0; i < socketchunk.length; i++) {
if (socketchunk[i] === "") {
socketchunk.splice(i, 1);
}
}
//iterate through socketchunks array to execute multiple commands in row
for (i = 0; i < socketchunk.length; i++) {
//check if data is not empty
if (socketchunk[i].length > 0) {
//try to parse JSON
try {
data = JSON.parse(socketchunk[i]);
} catch (e) {
console.log(e); // "SyntaxError
daemonLog.error(e);
daemonLog.debug(socketchunk[i]);
socketchunk = "";
}
}
if (data["command"] == "daemon_state") {
let Data = {
input_devices: data["input_devices"],
output_devices: data["output_devices"],
python_version: data["python_version"],
hamlib_version: data["hamlib_version"],
serial_devices: data["serial_devices"],
tnc_running_state: data["daemon_state"][0]["status"],
ram_usage: data["ram"],
cpu_usage: data["cpu"],
version: data["version"],
};
ipcRenderer.send("request-update-daemon-state", Data);
}
if (data["command"] == "test_hamlib") {
let Data = {
hamlib_result: data["result"],
};
ipcRenderer.send("request-update-hamlib-test", Data);
}
}
//finally delete message buffer
socketchunk = "";
}
});
function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
exports.getDaemonState = function () {
//function getDaemonState(){
command = '{"type" : "get", "command" : "daemon_state"}';
writeDaemonCommand(command);
};
// START TNC
// ` `== multi line string
exports.startTNC = function (
mycall,
mygrid,
rx_audio,
tx_audio,
radiocontrol,
devicename,
deviceport,
pttprotocol,
pttport,
serialspeed,
data_bits,
stop_bits,
handshake,
rigctld_ip,
rigctld_port,
enable_fft,
enable_scatter,
low_bandwidth_mode,
tuning_range_fmin,
tuning_range_fmax,
enable_fsk,
tx_audio_level,
respond_to_cq,
rx_buffer_size,
enable_explorer,
explorer_stats,
auto_tune,
tx_delay,
tci_ip,
tci_port,
enable_mesh,
) {
var json_command = JSON.stringify({
type: "set",
command: "start_tnc",
parameter: [
{
mycall: mycall,
mygrid: mygrid,
rx_audio: rx_audio,
tx_audio: tx_audio,
radiocontrol: radiocontrol,
devicename: devicename,
deviceport: deviceport,
pttprotocol: pttprotocol,
pttport: pttport,
serialspeed: serialspeed,
data_bits: data_bits,
stop_bits: stop_bits,
handshake: handshake,
rigctld_port: rigctld_port,
rigctld_ip: rigctld_ip,
enable_scatter: enable_scatter,
enable_fft: enable_fft,
enable_fsk: enable_fsk,
low_bandwidth_mode: low_bandwidth_mode,
tuning_range_fmin: tuning_range_fmin,
tuning_range_fmax: tuning_range_fmax,
tx_audio_level: tx_audio_level,
respond_to_cq: respond_to_cq,
rx_buffer_size: rx_buffer_size,
enable_explorer: enable_explorer,
enable_stats: explorer_stats,
enable_auto_tune: auto_tune,
tx_delay: tx_delay,
tci_ip: tci_ip,
tci_port: tci_port,
enable_mesh: enable_mesh,
},
],
});
daemonLog.debug(json_command);
writeDaemonCommand(json_command);
};
// STOP TNC
exports.stopTNC = function () {
command = '{"type" : "set", "command": "stop_tnc" , "parameter": "---" }';
writeDaemonCommand(command);
};
// TEST HAMLIB
exports.testHamlib = function (
radiocontrol,
devicename,
deviceport,
serialspeed,
pttprotocol,
pttport,
data_bits,
stop_bits,
handshake,
rigctld_ip,
rigctld_port,
) {
var json_command = JSON.stringify({
type: "get",
command: "test_hamlib",
parameter: [
{
radiocontrol: radiocontrol,
devicename: devicename,
deviceport: deviceport,
pttprotocol: pttprotocol,
pttport: pttport,
serialspeed: serialspeed,
data_bits: data_bits,
stop_bits: stop_bits,
handshake: handshake,
rigctld_port: rigctld_port,
rigctld_ip: rigctld_ip,
},
],
});
daemonLog.debug(json_command);
writeDaemonCommand(json_command);
};
//Save myCall
exports.saveMyCall = function (callsign) {
command =
'{"type" : "set", "command": "mycallsign" , "parameter": "' +
callsign +
'"}';
writeDaemonCommand(command);
};
// Save myGrid
exports.saveMyGrid = function (grid) {
command =
'{"type" : "set", "command": "mygrid" , "parameter": "' + grid + '"}';
writeDaemonCommand(command);
};
ipcRenderer.on("action-update-daemon-ip", (event, arg) => {
daemon.destroy();
let Data = {
busy_state: "-",
arq_state: "-",
//channel_state: "-",
frequency: "-",
mode: "-",
bandwidth: "-",
dbfs_level: 0,
};
ipcRenderer.send("request-update-tnc-state", Data);
daemon_port = arg.port;
daemon_host = arg.adress;
connectDAEMON();
});

View File

@ -1,37 +0,0 @@
const fs = require("fs");
const { ipcRenderer } = require("electron");
/**
* Save config and update config setting globally
* @param {string} config - config data
* @param {string} configPath
*/
exports.saveConfig = function (config, configPath) {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
ipcRenderer.send("set-config-global", config);
};
/**
* Binary to ASCII replacement
* @param {string} data in normal/usual utf-8 format
* @returns base64 encoded string
*/
exports.btoa_FD = function (data) {
return Buffer.from(data, "utf-8").toString("base64");
};
/**
* ASCII to Binary replacement
* @param {string} data in base64 encoding
* @returns utf-8 normal/usual string
*/
exports.atob_FD = function (data) {
return Buffer.from(data, "base64").toString("utf-8");
};
/**
* UTF8 to ASCII btoa
* @param {string} data in base64 encoding
* @returns base64 bota compatible data for use in browser
*/
exports.atob = function (data) {
return window.btoa(Buffer.from(data, "base64").toString("utf8"));
};

View File

@ -1,998 +0,0 @@
const { app, BrowserWindow, ipcMain, dialog, shell } = require("electron");
const https = require("https");
const { autoUpdater } = require("electron-updater");
const path = require("path");
const fs = require("fs");
const os = require("os");
const spawn = require("child_process").spawn;
const log = require("electron-log");
const mainLog = log.scope("main");
const daemonProcessLog = log.scope("freedata-daemon");
const mime = require("mime");
const net = require("net");
const FD = require("./freedata");
//Useful for debugging event emitter memory leaks
//require('events').EventEmitter.defaultMaxListeners = 10;
//process.traceProcessWarnings=true;
const sysInfo = log.scope("system information");
sysInfo.info("SYSTEM INFORMATION ----------------------------- ");
sysInfo.info("APP VERSION : " + app.getVersion());
sysInfo.info("PLATFORM : " + os.platform());
sysInfo.info("ARCHITECTURE: " + os.arch());
sysInfo.info("FREE MEMORY: " + os.freemem());
sysInfo.info("TOTAL MEMORY: " + os.totalmem());
sysInfo.info("LOAD AVG : " + os.loadavg());
sysInfo.info("RELEASE : " + os.release());
sysInfo.info("TYPE : " + os.type());
sysInfo.info("VERSION : " + os.version());
sysInfo.info("UPTIME : " + os.uptime());
app.setName("FreeDATA");
var appDataFolder =
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Application Support"
: process.env.HOME + "/.config");
var configFolder = path.join(appDataFolder, "FreeDATA");
var configPath = path.join(configFolder, "config.json");
// create config folder if not exists
if (!fs.existsSync(configFolder)) {
fs.mkdirSync(configFolder);
}
// create config file if not exists with defaults
const configDefaultSettings =
'{\
"tnc_host": "127.0.0.1",\
"tnc_port": "3000",\
"daemon_host": "127.0.0.1",\
"daemon_port": "3001",\
"mycall": "AA0AA-0",\
"mygrid": "JN40aa",\
"radiocontrol" : "disabled",\
"hamlib_deviceid": "RIG_MODEL_DUMMY_NOVFO",\
"hamlib_deviceport": "ignore",\
"hamlib_stop_bits": "ignore",\
"hamlib_data_bits": "ignore",\
"hamlib_handshake": "ignore",\
"hamlib_serialspeed": "ignore",\
"hamlib_dtrstate": "ignore",\
"hamlib_pttprotocol": "ignore",\
"hamlib_ptt_port": "ignore",\
"hamlib_dcd": "ignore",\
"hamlbib_serialspeed_ptt": "9600",\
"hamlib_rigctld_port" : "4532",\
"hamlib_rigctld_ip" : "127.0.0.1",\
"hamlib_rigctld_path" : "",\
"hamlib_rigctld_server_port" : "4532",\
"hamlib_rigctld_custom_args": "",\
"tci_port" : "50001",\
"tci_ip" : "127.0.0.1",\
"spectrum": "waterfall",\
"tnclocation": "localhost",\
"enable_scatter" : "False",\
"enable_fft" : "False",\
"enable_fsk" : "False",\
"low_bandwidth_mode" : "False",\
"theme" : "default",\
"screen_height" : 430,\
"screen_width" : 1050,\
"update_channel" : "latest",\
"beacon_interval" : 300,\
"received_files_folder" : "None",\
"tuning_range_fmin" : "-50.0",\
"tuning_range_fmax" : "50.0",\
"respond_to_cq" : "True",\
"rx_buffer_size" : "16", \
"enable_explorer" : "False", \
"wftheme": 2, \
"high_graphics" : "True",\
"explorer_stats" : "False", \
"auto_tune" : "False", \
"enable_is_writing" : "True", \
"shared_folder_path" : ".", \
"enable_request_profile" : "True", \
"enable_request_shared_folder" : "False", \
"max_retry_attempts" : 5, \
"enable_auto_retry" : "False", \
"tx_delay" : 0, \
"auto_start": 0, \
"enable_sys_notification": 1, \
"enable_mesh_features": "False" \
}';
if (!fs.existsSync(configPath)) {
fs.writeFileSync(configPath, configDefaultSettings);
}
// load settings
var config = require(configPath);
//config validation
// check running config against default config.
// if parameter not exists, add it to running config to prevent errors
sysInfo.info("CONFIG VALIDATION ----------------------------- ");
var parsedConfig = JSON.parse(configDefaultSettings);
for (key in parsedConfig) {
if (config.hasOwnProperty(key)) {
sysInfo.info("FOUND SETTTING [" + key + "]: " + config[key]);
} else {
sysInfo.error("MISSING SETTTING [" + key + "] : " + parsedConfig[key]);
config[key] = parsedConfig[key];
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
}
}
sysInfo.info("------------------------------------------ ");
/*
var chatDB = path.join(configFolder, 'chatDB.json')
// create chat database file if not exists
const configContentChatDB = `
{ "chatDB" : [{
"id" : "00000000",
"timestamp" : 1234566,
"mycall" : "AA0AA",
"dxcall" : "AB0AB",
"dxgrid" : "JN1200",
"message" : "hallowelt"
}]
}
`;
if (!fs.existsSync(chatDB)) {
fs.writeFileSync(chatDB, configContentChatDB);
}
*/
/*
// Creates receivedFiles folder if not exists
// https://stackoverflow.com/a/26227660
var appDataFolder = process.env.HOME
var applicationFolder = path.join(appDataFolder, "FreeDATA");
var receivedFilesFolder = path.join(applicationFolder, "receivedFiles");
// https://stackoverflow.com/a/13544465
fs.mkdir(receivedFilesFolder, {
recursive: true
}, function(err) {
console.log(err);
});
*/
let win = null;
let logViewer = null;
var daemonProcess = null;
// create a splash screen
function createSplashScreen() {
splashScreen = new BrowserWindow({
height: 250,
width: 250,
transparent: true,
frame: false,
alwaysOnTop: true,
});
splashScreen.loadFile("src/splash.html");
splashScreen.center();
}
function createWindow() {
win = new BrowserWindow({
width: config.screen_width,
height: config.screen_height,
show: false,
autoHideMenuBar: true,
icon: "src/img/icon.png",
webPreferences: {
//preload: path.join(__dirname, 'preload-main.js'),
backgroundThrottle: false,
preload: require.resolve("./preload-main.js"),
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: false,
sandbox: false,
//https://stackoverflow.com/questions/53390798/opening-new-window-electron/53393655
//https://github.com/electron/remote
},
});
// hide menu bar
win.setMenuBarVisibility(false);
//open dev tools
/*win.webContents.openDevTools({
mode: 'undocked',
activate: true,
})
*/
win.loadFile("src/index.html");
// Emitted when the window is closed.
win.on("closed", function () {
console.log("closing all windows.....");
close_all();
});
win.once("ready-to-show", () => {
log.transports.file.level = "debug";
autoUpdater.logger = log.scope("updater");
autoUpdater.channel = config.update_channel;
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.autoDownload = true;
autoUpdater.checkForUpdatesAndNotify();
//autoUpdater.quitAndInstall();
});
}
app.whenReady().then(() => {
// show splash screen
createSplashScreen();
// create main window
createWindow();
// wait some time, then close splash screen and show main windows
setTimeout(function () {
splashScreen.close();
win.show();
}, 3000);
//Generate daemon binary path
var daemonPath = "";
switch (os.platform().toLowerCase()) {
case "darwin":
case "linux":
daemonPath = path.join(process.resourcesPath, "tnc", "freedata-daemon");
break;
case "win32":
case "win64":
daemonPath = path.join(
process.resourcesPath,
"tnc",
"freedata-daemon.exe",
);
break;
default:
console.log("Unhandled OS Platform: ", os.platform());
break;
}
//Start daemon binary if it exists
if (fs.existsSync(daemonPath)) {
mainLog.info("Starting freedata-daemon binary");
daemonProcess = spawn(daemonPath, [], {
cwd: path.join(daemonPath, ".."),
});
// return process messages
daemonProcess.on("error", (err) => {
daemonProcessLog.error(`error when starting daemon: ${err}`);
});
daemonProcess.on("message", (data) => {
daemonProcessLog.info(`${data}`);
});
daemonProcess.stdout.on("data", (data) => {
daemonProcessLog.info(`${data}`);
});
daemonProcess.stderr.on("data", (data) => {
daemonProcessLog.info(`${data}`);
let arg = {
entry: `${data}`,
};
// send info to log only if log screen available
// it seems an error occurs when updating
if (logViewer !== null && logViewer !== "") {
try {
//logViewer.webContents.send("action-update-log", arg);
} catch (e) {
// empty for keeping error stuff silent
// this is important to avoid error messages if we are going to close the app while
// an logging information will be pushed to the logger
}
}
});
daemonProcess.on("close", (code) => {
daemonProcessLog.warn(`daemonProcess exited with code ${code}`);
});
} else {
daemonProcess = null;
daemonPath = null;
mainLog.info("Daemon binary doesn't exist--normal for dev environments.");
}
win.send("action-set-app-version", app.getVersion());
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
app.on("window-all-closed", () => {
close_all();
});
// IPC HANDLER
//Update configuration globally
ipcMain.on("set-config-global", (event, data) => {
config = data;
win.webContents.send("update-config", config);
});
//Show/update task bar/button progressbar
ipcMain.on("request-show-electron-progressbar", (event, data) => {
win.setProgressBar(data / 100);
});
ipcMain.on("request-update-dbclean-spinner", () => {
//Turn off dbclean spinner
win.webContents.send("action-update-dbclean-spinner");
});
// UPDATE TNC CONNECTION
ipcMain.on("request-update-tnc-ip", (event, data) => {
win.webContents.send("action-update-tnc-ip", data);
});
// UPDATE DAEMON CONNECTION
ipcMain.on("request-update-daemon-ip", (event, data) => {
win.webContents.send("action-update-daemon-ip", data);
});
ipcMain.on("request-update-tnc-state", (event, arg) => {
win.webContents.send("action-update-tnc-state", arg);
//meshViewer.send("action-update-mesh-table", arg);
//data.webContents.send('action-update-tnc-state', arg);
});
/*
ipcMain.on('request-update-data-state', (event, arg) => {
//win.webContents.send('action-update-data-state', arg);
//data.webContents.send('action-update-data-state', arg);
});
ipcMain.on('request-update-heard-stations', (event, arg) => {
win.webContents.send('action-update-heard-stations', arg);
});
*/
ipcMain.on("request-update-daemon-state", (event, arg) => {
win.webContents.send("action-update-daemon-state", arg);
});
ipcMain.on("request-update-hamlib-test", (event, arg) => {
win.webContents.send("action-update-hamlib-test", arg);
});
ipcMain.on("request-update-tnc-connection", (event, arg) => {
win.webContents.send("action-update-tnc-connection", arg);
});
ipcMain.on("request-update-daemon-connection", (event, arg) => {
win.webContents.send("action-update-daemon-connection", arg);
});
ipcMain.on("run-tnc-command", (event, arg) => {
win.webContents.send("run-tnc-command", arg);
});
ipcMain.on("tnc-fec-iswriting", (event, arg) => {
win.webContents.send("run-tnc-command-fec-iswriting");
});
ipcMain.on("request-update-rx-buffer", (event, arg) => {
win.webContents.send("action-update-rx-buffer", arg);
});
/*
ipcMain.on('request-update-rx-msg-buffer', (event, arg) => {
win.webContents.send('action-update-rx-msg-buffer', arg);
});
*/
ipcMain.on("request-new-msg-received", (event, arg) => {
win.webContents.send("action-new-msg-received", arg);
});
ipcMain.on("request-update-transmission-status", (event, arg) => {
//chat.webContents.send("action-update-transmission-status", arg);
win.webContents.send("action-update-transmission-status", arg);
});
ipcMain.on("request-update-reception-status", (event, arg) => {
win.webContents.send("action-update-reception-status", arg);
//chat.webContents.send("action-update-reception-status", arg);
});
//Called by main to query chat if there are new messages
ipcMain.on("request-update-unread-messages", () => {
//mainLog.info("Got request to check if chat has new messages")
win.webContents.send("action-update-unread-messages");
});
//Called by chat to notify main if there are new messages
ipcMain.on("request-update-unread-messages-main", (event, arg) => {
win.webContents.send("action-update-unread-messages-main", arg);
//mainLog.info("Received reply from chat and ?new messages = " +arg);
});
//Called by main to notify chat we should clean the DB
ipcMain.on("request-clean-db", () => {
win.webContents.send("action-clean-db");
});
//file selector
ipcMain.on("get-file-path", (event, data) => {
dialog
.showOpenDialog({
defaultPath: path.join(__dirname, "../"),
buttonLabel: "Select File",
properties: ["openFile"],
})
.then((filePaths) => {
if (filePaths.canceled == false) {
win.webContents.send(data.action, { path: filePaths });
}
});
});
//folder selector
ipcMain.on("get-folder-path", (event, data) => {
dialog
.showOpenDialog({
defaultPath: path.join(__dirname, "../"),
buttonLabel: "Select folder",
properties: ["openDirectory"],
})
.then((folderPaths) => {
if (folderPaths.canceled == false) {
win.webContents.send(data.action, { path: folderPaths });
//win.webContents.send(data.action, { path: filePaths });
}
});
});
//open folder
ipcMain.on("open-folder", (event, data) => {
shell.showItemInFolder(data.path);
});
//select file
ipcMain.on("select-file", (event, data) => {
dialog
.showOpenDialog({
defaultPath: path.join(__dirname, "../"),
buttonLabel: "Select file",
properties: ["openFile"],
})
.then((filepath) => {
console.log(filepath.filePaths[0]);
try {
//fs.readFile(filepath.filePaths[0], 'utf8', function (err, data) {
//Has to be binary
fs.readFile(filepath.filePaths[0], "binary", function (err, data) {
console.log(data.length);
console.log(data);
var filename = path.basename(filepath.filePaths[0]);
var mimeType = mime.getType(filename);
console.log(mimeType);
if (mimeType == "" || mimeType == null) {
mimeType = "plain/text";
}
win.webContents.send("return-selected-files", {
data: data,
mime: mimeType,
filename: filename,
});
});
} catch (err) {
console.log(err);
}
});
});
//select image file
ipcMain.on("select-user-image", (event, data) => {
dialog
.showOpenDialog({
defaultPath: path.join(__dirname, "../"),
buttonLabel: "Select file",
properties: ["openFile"],
})
.then((filepath) => {
console.log(filepath.filePaths[0]);
try {
// read data as base64 which makes conversion to blob easier
fs.readFile(filepath.filePaths[0], "base64", function (err, data) {
var filename = path.basename(filepath.filePaths[0]);
var mimeType = mime.getType(filename);
if (mimeType == "" || mimeType == null) {
mimeType = "plain/text";
}
win.webContents.send("return-select-user-image", {
data: data,
mime: mimeType,
filename: filename,
});
});
} catch (err) {
console.log(err);
}
});
});
// read files in folder - use case "shared folder"
ipcMain.on("read-files-in-folder", (event, data) => {
let fileList = [];
if (config["enable_request_shared_folder"].toLowerCase() == "false") {
//mainLog.info("Shared file folder is disable, not populating fileList");
win.webContents.send("return-shared-folder-files", {
files: fileList,
});
return;
}
let folder = data.folder;
let files = fs.readdirSync(folder);
console.log(folder);
console.log(files);
files.forEach((file) => {
try {
let filePath = folder + "/" + file;
if (fs.lstatSync(filePath).isFile()) {
let fileSizeInBytes = fs.statSync(filePath).size;
let extension = path.extname(filePath);
fileList.push({
name: file,
extension: extension.substring(1),
size: fileSizeInBytes,
});
}
} catch (err) {
console.log(err);
}
});
win.webContents.send("return-shared-folder-files", {
files: fileList,
});
});
//save file to folder
ipcMain.on("save-file-to-folder", (event, data) => {
console.log(data.file);
dialog.showSaveDialog({ defaultPath: data.filename }).then((filepath) => {
console.log(filepath.filePath);
console.log(data.file);
try {
let arraybuffer = Buffer.from(data.file, "base64").toString("utf-8");
console.log(arraybuffer);
//Has to be binary
fs.writeFile(
filepath.filePath,
arraybuffer,
"binary",
function (err, data) {},
);
} catch (err) {
console.log(err);
}
});
});
//tnc messages START --------------------------------------
// FEC iswriting received
ipcMain.on("request-show-fec-toast-iswriting", (event, data) => {
win.webContents.send("action-show-fec-toast-iswriting", data);
win.webContents.send("action-show-feciswriting", data);
});
// CQ TRANSMITTING
ipcMain.on("request-show-cq-toast-transmitting", (event, data) => {
win.webContents.send("action-show-cq-toast-transmitting", data);
});
// CQ RECEIVED
ipcMain.on("request-show-cq-toast-received", (event, data) => {
win.webContents.send("action-show-cq-toast-received", data);
});
// QRV TRANSMITTING
ipcMain.on("request-show-qrv-toast-transmitting", (event, data) => {
win.webContents.send("action-show-qrv-toast-transmitting", data);
});
// QRV RECEIVED
ipcMain.on("request-show-qrv-toast-received", (event, data) => {
win.webContents.send("action-show-qrv-toast-received", data);
});
// BEACON TRANSMITTING
ipcMain.on("request-show-beacon-toast-transmitting", (event, data) => {
win.webContents.send("action-show-beacon-toast-transmitting", data);
});
// BEACON RECEIVED
ipcMain.on("request-show-beacon-toast-received", (event, data) => {
win.webContents.send("action-show-beacon-toast-received", data);
});
// PING TRANSMITTING
ipcMain.on("request-show-ping-toast-transmitting", (event, data) => {
win.webContents.send("action-show-ping-toast-transmitting", data);
});
// PING RECEIVED
ipcMain.on("request-show-ping-toast-received", (event, data) => {
win.webContents.send("action-show-ping-toast-received", data);
});
// PING RECEIVED ACK
ipcMain.on("request-show-ping-toast-received-ack", (event, data) => {
win.webContents.send("action-show-ping-toast-received-ack", data);
});
// ARQ DATA CHANNEL OPENING
ipcMain.on("request-show-arq-toast-datachannel-opening", (event, data) => {
win.webContents.send("action-show-arq-toast-datachannel-opening", data);
});
// ARQ DATA CHANNEL WAITING
ipcMain.on("request-show-arq-toast-datachannel-waiting", (event, data) => {
win.webContents.send("action-show-arq-toast-datachannel-waiting", data);
});
// ARQ DATA CHANNEL OPEN
ipcMain.on("request-show-arq-toast-datachannel-opened", (event, data) => {
win.webContents.send("action-show-arq-toast-datachannel-opened", data);
});
// ARQ DATA RECEIVED OPENER
ipcMain.on(
"request-show-arq-toast-datachannel-received-opener",
(event, data) => {
win.webContents.send(
"action-show-arq-toast-datachannel-received-opener",
data,
);
},
);
// ARQ TRANSMISSION FAILED
ipcMain.on("request-show-arq-toast-transmission-failed", (event, data) => {
win.webContents.send("action-show-arq-toast-transmission-failed", data);
});
// ARQ TRANSMISSION FAILED
ipcMain.on("request-show-arq-toast-transmission-failed-ver", (event, data) => {
win.webContents.send("action-show-arq-toast-transmission-failed-ver", data);
});
// ARQ TRANSMISSION RECEIVING
ipcMain.on("request-show-arq-toast-transmission-receiving", (event, data) => {
win.webContents.send("action-show-arq-toast-transmission-receiving", data);
});
// ARQ TRANSMISSION RECEIVED
ipcMain.on("request-show-arq-toast-transmission-received", (event, data) => {
win.webContents.send("action-show-arq-toast-transmission-received", data);
});
// ARQ TRANSMISSION TRANSMITTING
ipcMain.on(
"request-show-arq-toast-transmission-transmitting",
(event, data) => {
win.webContents.send(
"action-show-arq-toast-transmission-transmitting",
data,
);
},
);
// ARQ TRANSMISSION TRANSMITTED
ipcMain.on("request-show-arq-toast-transmission-transmitted", (event, data) => {
win.webContents.send("action-show-arq-toast-transmission-transmitted", data);
});
// ARQ SESSION CONNECTING
ipcMain.on("request-show-arq-toast-session-connecting", (event, data) => {
win.webContents.send("action-show-arq-toast-session-connecting", data);
});
// ARQ SESSION WAITING
ipcMain.on("request-show-arq-toast-session-waiting", (event, data) => {
win.webContents.send("action-show-arq-toast-session-waiting", data);
});
// ARQ SESSION CONNECTED
ipcMain.on("request-show-arq-toast-session-connected", (event, data) => {
win.webContents.send("action-show-arq-toast-session-connected", data);
});
// ARQ SESSION CLOSE
ipcMain.on("request-show-arq-toast-session-close", (event, data) => {
win.webContents.send("action-show-arq-toast-session-close", data);
});
// ARQ SESSION FAILED
ipcMain.on("request-show-arq-toast-session-failed", (event, data) => {
win.webContents.send("action-show-arq-toast-session-failed", data);
});
//tnc messages END --------------------------------------
//restart and install udpate
ipcMain.on("request-restart-and-install", (event, data) => {
close_sub_processes();
autoUpdater.quitAndInstall();
});
// LISTENER FOR UPDATER EVENTS
autoUpdater.on("update-available", (info) => {
mainLog.info("update available");
let arg = {
status: "update-available",
info: info,
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("update-not-available", (info) => {
mainLog.info("update not available");
let arg = {
status: "update-not-available",
info: info,
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("update-downloaded", (info) => {
mainLog.info("update downloaded");
let arg = {
status: "update-downloaded",
info: info,
};
win.webContents.send("action-updater", arg);
// we need to call this at this point.
// if an update is available and we are force closing the app
// the entire screen crashes...
//mainLog.info('quit application and install update');
//autoUpdater.quitAndInstall();
});
autoUpdater.on("checking-for-update", () => {
mainLog.info("checking for update");
let arg = {
status: "checking-for-update",
version: app.getVersion(),
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("download-progress", (progress) => {
let arg = {
status: "download-progress",
progress: progress,
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("error", (error) => {
mainLog.info("update error");
let arg = {
status: "error",
progress: error,
};
win.webContents.send("action-updater", arg);
mainLog.error("AUTO UPDATER : " + error);
});
function close_sub_processes() {
mainLog.warn("closing sub processes");
// closing the tnc binary if not closed when closing application and also our daemon which has been started by the gui
try {
if (daemonProcess != null) {
daemonProcess.kill();
}
} catch (e) {
mainLog.error(e);
}
mainLog.warn("closing tnc and daemon");
try {
if (os.platform() == "win32" || os.platform() == "win64") {
spawn("Taskkill", ["/IM", "freedata-tnc.exe", "/F"]);
spawn("Taskkill", ["/IM", "freedata-daemon.exe", "/F"]);
}
if (os.platform() == "linux") {
spawn("pkill", ["-9", "freedata-tnc"]);
spawn("pkill", ["-9", "freedata-daemon"]);
}
if (os.platform() == "darwin") {
spawn("pkill", ["-9", "freedata-tnc"]);
spawn("pkill", ["-9", "freedata-daemon"]);
}
} catch (e) {
mainLog.error(e);
}
}
function close_all() {
// function for closing the application with closing all used processes
close_sub_processes();
mainLog.warn("quitting app");
win.destroy();
app.quit();
}
// RUN RIGCTLD
ipcMain.on("request-start-rigctld", (event, data) => {
try {
let rigctld_proc = spawn(data.path, data.parameters, {
windowsVerbatimArguments: true,
});
rigctld_proc.on("exit", function (code) {
console.log("rigctld process exited with code " + code);
// if rigctld crashes, error code is -2
// then we are going to restart rigctld
// this "fixes" a problem with latest rigctld on raspberry pi
//if (code == -2){
// setTimeout(ipcRenderer.send('request-start-rigctld', data), 500);
//}
//let rigctld_proc = spawn(data.path, data.parameters);
});
} catch (e) {
console.log(e);
}
/*
const rigctld = exec(data.path, data.parameters);
rigctld.stdout.on("data", data => {
console.log(`stdout: ${data}`);
});
*/
});
// STOP RIGCTLD
ipcMain.on("request-stop-rigctld", (event, data) => {
mainLog.warn("closing rigctld");
try {
if (os.platform() == "win32" || os.platform() == "win64") {
spawn("Taskkill", ["/IM", "rigctld.exe", "/F"]);
}
if (os.platform() == "linux") {
spawn("pkill", ["-9", "rigctld"]);
}
if (os.platform() == "darwin") {
spawn("pkill", ["-9", "rigctld"]);
}
} catch (e) {
mainLog.error(e);
}
});
// CHECK RIGCTLD CONNECTION
// create new socket so we are not reopening every time a new one
var rigctld_connection = new net.Socket();
var rigctld_connection_state = false;
var rigctld_events_wired = false;
ipcMain.on("request-check-rigctld", (event, data) => {
try {
let Data = {
state: "unknown",
};
if (!rigctld_connection_state) {
rigctld_connection = new net.Socket();
rigctld_events_wired = false;
rigctld_connection.connect(data.port, data.ip);
}
// Check if we have created a new socket object and attach listeners if not already created
if (typeof rigctld_connection != "undefined" && !rigctld_events_wired) {
rigctld_connection.on("connect", function () {
rigctld_events_wired = true;
mainLog.info("Starting rigctld event listeners");
rigctld_connection_state = true;
Data["state"] = "Connected";
Data["active"] = true;
if (win !== null && win !== "" && typeof win != "undefined") {
// try catch for being sure we have a clean app close
try {
win.webContents.send("action-check-rigctld", Data);
} catch (e) {
console.log(e);
}
}
});
rigctld_connection.on("error", function () {
rigctld_connection_state = false;
Data["state"] = "Not Connected";
Data["active"] = false;
if (win !== null && win !== "" && typeof win != "undefined") {
// try catch for being sure we have a clean app close
try {
win.webContents.send("action-check-rigctld", Data);
} catch (e) {
console.log(e);
}
}
});
rigctld_connection.on("end", function () {
rigctld_connection_state = false;
});
}
} catch (e) {
console.log(e);
}
});
function downloadJsonUrlToFile(url, callsignPath) {
// https://nodejs.org/api/https.html#httpsgetoptions-callback
https
.get(url, (res) => {
//console.log("statusCode:", res.statusCode);
//console.log("headers:", res.headers);
res.on("data", (d) => {
//console.log(d);
let json = JSON.parse(d);
fs.writeFileSync(callsignPath, JSON.stringify(json, null, 2));
sysInfo.info(
"Download " + url + " return statuscode: " + res.statusCode,
);
});
})
.on("error", (e) => {
console.error(e);
});
}
function downloadCallsignReverseLookupData() {
sysInfo.info("Downloading callsigns.json");
var callsignPath = path.join(configFolder, "callsigns.json");
downloadJsonUrlToFile(
"https://api.freedata.app/callsign_lookup.php",
callsignPath,
);
}
downloadCallsignReverseLookupData();

View File

@ -1,15 +1,17 @@
{
"name": "FreeDATA",
"version": "0.10.2-alpha.1",
"description": "FreeDATA ",
"main": "main.js",
"private": true,
"version": "0.11.0-alpha",
"main": "dist-electron/main/index.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"engines": {
"node": ">=18.17.0",
"npm": ">=9.0.0"
"start": "git pull && npm i && vite",
"dev": "vite",
"check" : "vue-tsc --noEmit",
"build": "vue-tsc --noEmit && vite build && electron-builder",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue src",
"lint-fix": "eslint --ext .js,.vue --fix src"
},
"repository": {
"type": "git",
@ -28,9 +30,8 @@
},
"homepage": "https://freedata.app",
"dependencies": {
"@electron/asar": "^3.2.4",
"@electron/osx-sign": "^1.0.4",
"@popperjs/core": "^2.11.8",
"@electron/notarize": "^2.1.0",
"@vueuse/electron": "^10.4.1",
"blob-util": "^2.0.2",
"bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5",
@ -38,73 +39,44 @@
"browser-image-compression": "^2.0.2",
"chart.js": "^4.3.3",
"chartjs-plugin-annotation": "^3.0.1",
"electron-builder-notarize": "^1.5.1",
"electron-log": "^4.4.8",
"electron-updater": "^6.1.1",
"electron-updater": "^6.1.4",
"emoji-picker-element": "^1.18.3",
"emoji-picker-element-data": "^1.4.0",
"express-pouchdb": "^4.2.0",
"file-saver": "^2.0.5",
"mime": "^3.0.0",
"pinia": "^2.1.6",
"pouchdb": "^8.0.1",
"pouchdb-browser": "^8.0.1",
"pouchdb-express-router": "^0.0.11",
"pouchdb-find": "^8.0.1",
"pouchdb-replication": "^8.0.1",
"pouchdb-upsert": "^2.2.0",
"qth-locator": "^2.1.0",
"utf8": "^3.0.0",
"uuid": "^9.0.0"
"sass": "^1.66.1",
"socket.io": "^4.7.2",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-chartjs": "^5.2.0",
"vuemoji-picker": "^0.2.0"
},
"devDependencies": {
"@electron/notarize": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@vitejs/plugin-vue": "^4.4.0",
"electron": "^27.0.0",
"electron-builder": "^24.6.3",
"electron-builder-notarize": "^1.5.1"
},
"build": {
"productName": "FreeDATA",
"appId": "app.freedata",
"afterSign": "electron-builder-notarize",
"npmRebuild": "false",
"directories": {
"buildResources": "build",
"output": "dist"
},
"mac": {
"target": [
"default"
],
"icon": "build/icon.png",
"hardenedRuntime": true,
"entitlements": "build/entitlements.plist",
"entitlementsInherit": "build/entitlements.plist",
"gatekeeperAssess": false
},
"win": {
"icon": "build/icon.png",
"target": [
"nsis"
]
},
"linux": {
"icon": "build/icon.png",
"target": [
"AppImage"
],
"category": "Development"
},
"publish": {
"provider": "github",
"releaseType": "release"
},
"extraResources": [
{
"from": "../tnc/dist/tnc/",
"to": "tnc",
"filter": [
"**/*",
"!**/.git"
]
}
]
"eslint": "^8.50.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard-with-typescript": "^39.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-n": "^16.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.17.0",
"typescript": "^5.2.2",
"vite": "^4.3.2",
"vite-plugin-electron": "^0.14.0",
"vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4",
"vue-tsc": "^1.4.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,186 +0,0 @@
const path = require("path");
const { ipcRenderer } = require("electron");
// https://stackoverflow.com/a/26227660
var appDataFolder =
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Application Support"
: process.env.HOME + "/.config");
var configFolder = path.join(appDataFolder, "FreeDATA");
var configPath = path.join(configFolder, "config.json");
const config = require(configPath);
// WINDOW LISTENER
window.addEventListener("DOMContentLoaded", () => {
document
.getElementById("enable_filter_info")
.addEventListener("click", () => {
if (document.getElementById("enable_filter_info").checked) {
display_class("table-info", true);
} else {
display_class("table-info", false);
}
});
document
.getElementById("enable_filter_debug")
.addEventListener("click", () => {
if (document.getElementById("enable_filter_debug").checked) {
display_class("table-debug", true);
} else {
display_class("table-debug", false);
}
});
document
.getElementById("enable_filter_warning")
.addEventListener("click", () => {
if (document.getElementById("enable_filter_warning").checked) {
display_class("table-warning", true);
} else {
display_class("table-warning", false);
}
});
document
.getElementById("enable_filter_error")
.addEventListener("click", () => {
if (document.getElementById("enable_filter_error").checked) {
display_class("table-danger", true);
} else {
display_class("table-danger", false);
}
});
});
function display_class(class_name, state) {
var collection = document.getElementsByClassName(class_name);
console.log(collection);
for (let i = 0; i < collection.length; i++) {
if (state == true) {
collection[i].style.display = "table-row";
} else {
collection[i].style.display = "None";
}
}
}
ipcRenderer.on("action-update-log", (event, arg) => {
var entry = arg.entry;
// remove ANSI characters from string, caused by color logging
// https://stackoverflow.com/a/29497680
entry = entry.replace(
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
"",
);
var tbl = document.getElementById("log");
var row = document.createElement("tr");
var timestamp = document.createElement("td");
var timestampText = document.createElement("span");
//datetime = new Date();
//timestampText.innerText = datetime.toISOString();
timestampText.innerText = entry.slice(0, 19);
timestamp.appendChild(timestampText);
var type = document.createElement("td");
var typeText = document.createElement("span");
// typeText.innerText = entry.slice(10, 30).match(/[\[](.*)[^\]]/g);
console.log(entry.match(/\[[^\]]+\]/g));
try {
typeText.innerText = entry.match(/\[[^\]]+\]/g)[0];
} catch (e) {
typeText.innerText = "-";
}
// let res = str.match(/[\[](.*)[^\]]/g);
type.appendChild(typeText);
var area = document.createElement("td");
var areaText = document.createElement("span");
//areaText.innerText = entry.slice(10, 50).match(/[\] \[](.*)[^\]]/g);
//areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
try {
areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
} catch (e) {
areaText.innerText = "-";
}
area.appendChild(areaText);
var logEntry = document.createElement("td");
var logEntryText = document.createElement("span");
try {
logEntryText.innerText = entry.split("]")[2];
} catch (e) {
logEntryText.innerText = "-";
}
logEntry.appendChild(logEntryText);
row.appendChild(timestamp);
row.appendChild(type);
row.appendChild(area);
row.appendChild(logEntry);
//row.classList.add("table-blablubb");
/*
if (logEntryText.innerText.includes('ALSA lib pcm')) {
row.classList.add("table-secondary");
}
*/
if (typeText.innerText.includes("info")) {
row.classList.add("table-info");
}
if (typeText.innerText.includes("debug")) {
row.classList.add("table-secondary");
}
if (typeText.innerText.includes("warning")) {
row.classList.add("table-warning");
}
if (typeText.innerText.includes("error")) {
row.classList.add("table-danger");
}
if (document.getElementById("enable_filter_info").checked) {
row.style.display = "table-row";
display_class("table-info", true);
} else {
row.style.display = "None";
display_class("table-info", false);
}
if (document.getElementById("enable_filter_debug").checked) {
row.style.display = "table-row";
display_class("table-secondary", true);
} else {
row.style.display = "None";
display_class("table-secondary", false);
}
if (document.getElementById("enable_filter_warning").checked) {
row.style.display = "table-row";
display_class("table-warning", true);
} else {
row.style.display = "None";
display_class("table-warning", false);
}
if (document.getElementById("enable_filter_error").checked) {
row.style.display = "table-row";
display_class("table-danger", true);
} else {
row.style.display = "None";
display_class("table-danger", false);
}
tbl.appendChild(row);
// scroll to bottom of page
// https://stackoverflow.com/a/11715670
window.scrollTo(0, document.body.scrollHeight);
});

File diff suppressed because it is too large Load Diff

View File

@ -1,231 +0,0 @@
const path = require("path");
const { ipcRenderer } = require("electron");
// https://stackoverflow.com/a/26227660
var appDataFolder =
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Application Support"
: process.env.HOME + "/.config");
var configFolder = path.join(appDataFolder, "FreeDATA");
var configPath = path.join(configFolder, "config.json");
const config = require(configPath);
var callsignPath = path.join(configFolder, "callsigns.json");
const callsigns = require(callsignPath);
// WINDOW LISTENER
window.addEventListener("DOMContentLoaded", () => {
// startPing button clicked
document
.getElementById("transmit_mesh_ping")
.addEventListener("click", () => {
var dxcallsign = document
.getElementById("dxCallMesh")
.value.toUpperCase();
if (dxcallsign == "" || dxcallsign == null || dxcallsign == undefined)
return;
//pauseButton(document.getElementById("transmit_mesh_ping"), 2000);
ipcRenderer.send("run-tnc-command", {
command: "mesh_ping",
dxcallsign: dxcallsign,
});
});
});
ipcRenderer.on("action-update-mesh-table", (event, arg) => {
var routes = arg.routing_table;
if (typeof routes == "undefined") {
return;
}
var tbl = document.getElementById("mesh-table");
if (tbl !== null) {
tbl.innerHTML = "";
}
for (i = 0; i < routes.length; i++) {
var row = document.createElement("tr");
var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString(
navigator.language,
{
hourCycle: "h23",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
},
);
var timestamp = document.createElement("td");
var timestampText = document.createElement("span");
timestampText.innerText = datetime;
timestamp.appendChild(timestampText);
var dxcall = document.createElement("td");
var dxcallText = document.createElement("span");
dxcallText.innerText = routes[i]["dxcall"];
// check for callsign in callsign list, else use checksum
for (let call in callsigns) {
if (callsigns[call] == routes[i]["dxcall"]) {
dxcallText.innerText += " (" + call + ")";
continue;
}
}
dxcall.appendChild(dxcallText);
var router = document.createElement("td");
var routerText = document.createElement("span");
routerText.innerText = routes[i]["router"];
// check for callsign in callsign list, else use checksum
for (let call in callsigns) {
if (callsigns[call] == routes[i]["router"]) {
routerText.innerHTML += `<span class="badge ms-2 bg-secondary">${call}</span>`;
continue;
}
}
router.appendChild(routerText);
var hops = document.createElement("td");
var hopsText = document.createElement("span");
hopsText.innerText = routes[i]["hops"];
hops.appendChild(hopsText);
var score = document.createElement("td");
var scoreText = document.createElement("span");
scoreText.innerText = routes[i]["score"];
score.appendChild(scoreText);
var snr = document.createElement("td");
var snrText = document.createElement("span");
snrText.innerText = routes[i]["snr"];
snr.appendChild(snrText);
row.appendChild(timestamp);
row.appendChild(dxcall);
row.appendChild(router);
row.appendChild(hops);
row.appendChild(score);
row.appendChild(snr);
tbl.appendChild(row);
}
/*-------------------------------------------*/
var routes = arg.mesh_signalling_table;
//console.log(routes);
if (typeof routes == "undefined") {
return;
}
var tbl = document.getElementById("mesh-signalling-table");
if (tbl !== null) {
tbl.innerHTML = "";
}
for (i = 0; i < routes.length; i++) {
var row = document.createElement("tr");
var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString(
navigator.language,
{
hourCycle: "h23",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
},
);
var timestamp = document.createElement("td");
var timestampText = document.createElement("span");
timestampText.innerText = datetime;
timestamp.appendChild(timestampText);
var destination = document.createElement("td");
var destinationText = document.createElement("span");
destinationText.innerText = routes[i]["destination"];
// check for callsign in callsign list, else use checksum
for (let call in callsigns) {
if (callsigns[call] == routes[i]["destination"]) {
destinationText.innerHTML += `<span class="badge ms-2 bg-secondary">${call}</span>`;
continue;
}
}
destination.appendChild(destinationText);
var origin = document.createElement("td");
var originText = document.createElement("span");
originText.innerText = routes[i]["origin"];
// check for callsign in callsign list, else use checksum
for (let call in callsigns) {
if (callsigns[call] == routes[i]["origin"]) {
originText.innerHTML += `<span class="badge ms-2 bg-secondary">${call}</span>`;
continue;
}
}
origin.appendChild(originText);
var frametype = document.createElement("td");
var frametypeText = document.createElement("span");
frametypeText.innerText = routes[i]["frametype"];
frametype.appendChild(frametypeText);
var payload = document.createElement("td");
var payloadText = document.createElement("span");
payloadText.innerText = routes[i]["payload"];
payload.appendChild(payloadText);
var attempt = document.createElement("td");
var attemptText = document.createElement("span");
attemptText.innerText = routes[i]["attempt"];
attempt.appendChild(attemptText);
var status = document.createElement("td");
var statusText = document.createElement("span");
//statusText.innerText = routes[i]["status"];
switch (routes[i]["status"]) {
case "acknowledged":
var status_icon = '<i class="bi bi-check-circle-fill"></i>';
var status_color = "bg-success";
break;
case "acknowledging":
var status_icon = '<i class="bi bi-check-circle"></i>';
var status_color = "bg-warning";
break;
case "forwarding":
var status_icon = '<i class="bi bi-arrow-left-right"></i>';
var status_color = "bg-secondary";
break;
case "awaiting_ack":
var status_icon = '<i class="bi bi-clock-history"></i>';
var status_color = "bg-info";
break;
default:
var status_icon = '<i class="bi bi-question-circle-fill"></i>';
var status_color = "bg-primary";
break;
}
statusText.innerHTML = `
<span class="badge ${status_color}">${status_icon}</span>
<span class="badge ${status_color}">${routes[i]["status"]}</span>
`;
status.appendChild(statusText);
row.appendChild(timestamp);
row.appendChild(destination);
row.appendChild(origin);
row.appendChild(frametype);
row.appendChild(payload);
row.appendChild(attempt);
row.appendChild(status);
tbl.appendChild(row);
}
});

View File

Before

Width:  |  Height:  |  Size: 590 KiB

After

Width:  |  Height:  |  Size: 590 KiB

View File

@ -1,939 +0,0 @@
var net = require("net");
const path = require("path");
const { ipcRenderer } = require("electron");
const FD = require("./freedata");
const log = require("electron-log");
const socketLog = log.scope("tnc");
//const utf8 = require("utf8");
// https://stackoverflow.com/a/26227660
var appDataFolder =
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Application Support"
: process.env.HOME + "/.config");
var configFolder = path.join(appDataFolder, "FreeDATA");
var configPath = path.join(configFolder, "config.json");
const config = require(configPath);
var client = new net.Socket();
var socketchunk = ""; // Current message, per connection.
// split character
const split_char = "\0;\1;";
// globals for getting new data only if available so we are saving bandwidth
var rxBufferLengthTnc = 0;
var rxBufferLengthGui = 0;
//var rxMsgBufferLengthTnc = 0;
//var rxMsgBufferLengthGui = 0;
// global to keep track of TNC connection error emissions
var tncShowConnectStateError = 1;
// global for storing ip information
var tnc_port = config.tnc_port;
var tnc_host = config.tnc_host;
// network connection Timeout
setTimeout(connectTNC, 2000);
function connectTNC() {
//exports.connectTNC = function(){
//socketLog.info('connecting to TNC...')
//clear message buffer after reconnecting or initial connection
socketchunk = "";
if (config.tnclocation == "localhost") {
client.connect(3000, "127.0.0.1");
} else {
client.connect(tnc_port, tnc_host);
}
}
client.on("connect", function (data) {
socketLog.info("TNC connection established");
let Data = {
busy_state: "-",
arq_state: "-",
//channel_state: "-",
frequency: "-",
mode: "-",
bandwidth: "-",
dbfs_level: 0,
};
ipcRenderer.send("request-update-tnc-state", Data);
// also update tnc connection state
ipcRenderer.send("request-update-tnc-connection", {
tnc_connection: client.readyState,
});
tncShowConnectStateError = 1;
});
client.on("error", function (data) {
if (tncShowConnectStateError == 1) {
socketLog.error("TNC connection error");
tncShowConnectStateError = 0;
}
setTimeout(connectTNC, 500);
client.destroy();
let Data = {
tnc_connection: client.readyState,
busy_state: "-",
arq_state: "-",
//channel_state: "-",
frequency: "-",
mode: "-",
bandwidth: "-",
dbfs_level: 0,
};
ipcRenderer.send("request-update-tnc-state", Data);
ipcRenderer.send("request-update-tnc-connection", {
tnc_connection: client.readyState,
});
});
/*
client.on('close', function(data) {
socketLog.info(' TNC connection closed');
setTimeout(connectTNC, 2000)
});
*/
client.on("end", function (data) {
socketLog.info("TNC connection ended");
ipcRenderer.send("request-update-tnc-connection", {
tnc_connection: client.readyState,
});
client.destroy();
setTimeout(connectTNC, 500);
});
writeTncCommand = function (command) {
//socketLog.info(command)
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
// if socket opened, we are able to run commands
if (client.readyState == "open") {
client.write(command + "\n");
}
if (client.readyState == "closed") {
socketLog.info("CLOSED!");
}
if (client.readyState == "opening") {
socketLog.info("connecting to TNC...");
}
};
client.on("data", function (socketdata) {
ipcRenderer.send("request-update-tnc-connection", {
tnc_connection: client.readyState,
});
/*
inspired by:
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
*/
socketdata = socketdata.toString("utf8"); // convert data to string
socketchunk += socketdata; // append data to buffer so we can stick long data together
// check if we received begin and end of json data
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
var data = "";
// split data into chunks if we received multiple commands
socketchunk = socketchunk.split("\n");
//don't think this is needed anymore
//data = JSON.parse(socketchunk[0])
// search for empty entries in socketchunk and remove them
for (i = 0; i < socketchunk.length; i++) {
if (socketchunk[i] === "") {
socketchunk.splice(i, 1);
}
}
//iterate through socketchunks array to execute multiple commands in row
for (i = 0; i < socketchunk.length; i++) {
//check if data is not empty
if (socketchunk[i].length > 0) {
//try to parse JSON
try {
data = JSON.parse(socketchunk[i]);
} catch (e) {
socketLog.info("Throwing away data!!!!\n" + e); // "SyntaxError
//socketLog.info(e); // "SyntaxError
socketLog.info(socketchunk[i]);
socketchunk = "";
//If we're here, I don't think we want to process any data that may be in data variable
continue;
}
}
if (data["command"] == "tnc_state") {
//socketLog.info(data)
// set length of RX Buffer to global variable
rxBufferLengthTnc = data["rx_buffer_length"];
//rxMsgBufferLengthTnc = data["rx_msg_buffer_length"];
let Data = {
mycallsign: data["mycallsign"],
mygrid: data["mygrid"],
ptt_state: data["ptt_state"],
busy_state: data["tnc_state"],
arq_state: data["arq_state"],
arq_session: data["arq_session"],
//channel_state: data['CHANNEL_STATE'],
frequency: data["frequency"],
speed_level: data["speed_level"],
mode: data["mode"],
bandwidth: data["bandwidth"],
dbfs_level: data["audio_dbfs"],
fft: data["fft"],
channel_busy: data["channel_busy"],
channel_busy_slot: data["channel_busy_slot"],
scatter: data["scatter"],
info: data["info"],
rx_buffer_length: data["rx_buffer_length"],
rx_msg_buffer_length: data["rx_msg_buffer_length"],
tx_n_max_retries: data["tx_n_max_retries"],
arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"],
arq_tx_n_bursts: data["arq_tx_n_bursts"],
arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"],
arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"],
arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"],
arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"],
arq_n_arq_frames_per_data_frame:
data["arq_n_arq_frames_per_data_frame"],
arq_bytes_per_minute: data["arq_bytes_per_minute"],
arq_seconds_until_finish: data["arq_seconds_until_finish"],
arq_compression_factor: data["arq_compression_factor"],
total_bytes: data["total_bytes"],
arq_transmission_percent: data["arq_transmission_percent"],
stations: data["stations"],
routing_table: data["routing_table"],
mesh_signalling_table: data["mesh_signalling_table"],
beacon_state: data["beacon_state"],
hamlib_status: data["hamlib_status"],
listen: data["listen"],
audio_recording: data["audio_recording"],
speed_list: data["speed_list"],
strength: data["strength"],
is_codec2_traffic: data["is_codec2_traffic"],
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
};
ipcRenderer.send("request-update-tnc-state", Data);
//continue to next for loop iteration, nothing else needs to be done here
continue;
}
// ----------- catch tnc messages START -----------
if (data["freedata"] == "tnc-message") {
switch (data["fec"]) {
case "is_writing":
// RX'd FECiswriting
ipcRenderer.send("request-show-fec-toast-iswriting", {
data: [data],
});
break;
case "broadcast":
// RX'd FEC BROADCAST
var encoded_data = FD.atob_FD(data["data"]);
var splitted_data = encoded_data.split(split_char);
var messageArray = [];
if (splitted_data[0] == "m") {
messageArray.push(data);
console.log(data);
}
let Messages = {
data: messageArray,
};
ipcRenderer.send("request-new-msg-received", Messages);
break;
}
switch (data["cq"]) {
case "transmitting":
// CQ TRANSMITTING
ipcRenderer.send("request-show-cq-toast-transmitting", {
data: [data],
});
break;
case "received":
// CQ RECEIVED
ipcRenderer.send("request-show-cq-toast-received", {
data: [data],
});
break;
}
switch (data["qrv"]) {
case "transmitting":
// QRV TRANSMITTING
ipcRenderer.send("request-show-qrv-toast-transmitting", {
data: [data],
});
break;
case "received":
// QRV RECEIVED
ipcRenderer.send("request-show-qrv-toast-received", {
data: [data],
});
break;
}
switch (data["beacon"]) {
case "transmitting":
// BEACON TRANSMITTING
ipcRenderer.send("request-show-beacon-toast-transmitting", {
data: [data],
});
break;
case "received":
// BEACON RECEIVED
ipcRenderer.send("request-show-beacon-toast-received", {
data: [data],
});
ipcRenderer.send("request-new-msg-received", { data: [data] });
break;
}
switch (data["ping"]) {
case "transmitting":
// PING TRANSMITTING
ipcRenderer.send("request-show-ping-toast-transmitting", {
data: [data],
});
break;
case "received":
// PING RECEIVED
ipcRenderer.send("request-show-ping-toast-received", {
data: [data],
});
ipcRenderer.send("request-new-msg-received", { data: [data] });
break;
case "acknowledge":
// PING ACKNOWLEDGE
ipcRenderer.send("request-show-ping-toast-received-ack", {
data: [data],
});
ipcRenderer.send("request-new-msg-received", { data: [data] });
break;
}
// ARQ SESSION && freedata == tnc-message
if (data["arq"] == "session") {
switch (data["status"]) {
case "connecting":
// ARQ Open
ipcRenderer.send("request-show-arq-toast-session-connecting", {
data: [data],
});
break;
case "connected":
// ARQ Opening
ipcRenderer.send("request-show-arq-toast-session-connected", {
data: [data],
});
break;
case "waiting":
// ARQ Opening
ipcRenderer.send("request-show-arq-toast-session-waiting", {
data: [data],
});
break;
case "close":
// ARQ Closing
ipcRenderer.send("request-show-arq-toast-session-close", {
data: [data],
});
break;
case "failed":
// ARQ Failed
ipcRenderer.send("request-show-arq-toast-session-failed", {
data: [data],
});
break;
}
}
// ARQ TRANSMISSION && freedata == tnc-message
if (data["arq"] == "transmission") {
switch (data["status"]) {
case "opened":
// ARQ Open
ipcRenderer.send("request-show-arq-toast-datachannel-opened", {
data: [data],
});
break;
case "opening":
// ARQ Opening IRS/ISS
if (data["irs"] == "False") {
ipcRenderer.send("request-show-arq-toast-datachannel-opening", {
data: [data],
});
ipcRenderer.send("request-update-transmission-status", {
data: [data],
});
} else {
ipcRenderer.send(
"request-show-arq-toast-datachannel-received-opener",
{ data: [data] },
);
ipcRenderer.send("request-update-reception-status", {
data: [data],
});
}
break;
case "waiting":
// ARQ waiting
ipcRenderer.send("request-show-arq-toast-datachannel-waiting", {
data: [data],
});
break;
case "receiving":
// ARQ RX
ipcRenderer.send("request-update-reception-status", {
data: [data],
});
break;
case "failed":
// ARQ TX Failed
if (data["reason"] == "protocol version missmatch") {
ipcRenderer.send(
"request-show-arq-toast-transmission-failed-ver",
{ data: [data] },
);
} else {
ipcRenderer.send("request-show-arq-toast-transmission-failed", {
data: [data],
});
}
switch (data["irs"]) {
case "True":
ipcRenderer.send("request-update-reception-status", {
data: [data],
});
break;
default:
ipcRenderer.send("request-update-transmission-status", {
data: [data],
});
break;
}
break;
case "received":
// ARQ Received
ipcRenderer.send("request-show-arq-toast-transmission-received", {
data: [data],
});
ipcRenderer.send("request-update-reception-status", {
data: [data],
});
dataArray = [];
messageArray = [];
socketLog.info(data);
// we need to encode here to do a deep check for checking if file or message
//var encoded_data = atob(data['data'])
var encoded_data = FD.atob_FD(data["data"]);
var splitted_data = encoded_data.split(split_char);
if (splitted_data[0] == "f") {
dataArray.push(data);
}
if (splitted_data[0] == "m") {
messageArray.push(data);
console.log(data);
}
rxBufferLengthGui = dataArray.length;
let Files = {
data: dataArray,
};
ipcRenderer.send("request-update-rx-buffer", Files);
ipcRenderer.send("request-new-msg-received", Files);
//rxMsgBufferLengthGui = messageArray.length;
let Messages = {
data: messageArray,
};
ipcRenderer.send("request-new-msg-received", Messages);
break;
case "transmitting":
// ARQ transmitting
ipcRenderer.send(
"request-show-arq-toast-transmission-transmitting",
{ data: [data] },
);
ipcRenderer.send("request-update-transmission-status", {
data: [data],
});
break;
case "transmitted":
// ARQ transmitted
ipcRenderer.send(
"request-show-arq-toast-transmission-transmitted",
{ data: [data] },
);
ipcRenderer.send("request-update-transmission-status", {
data: [data],
});
break;
}
}
}
// ----------- catch tnc info messages END -----------
// if we manually checking for the rx buffer we are getting an array of multiple data
if (data["command"] == "rx_buffer") {
socketLog.info(data);
// iterate through buffer list and sort it to file or message array
dataArray = [];
messageArray = [];
for (i = 0; i < data["data-array"].length; i++) {
try {
// we need to encode here to do a deep check for checking if file or message
//var encoded_data = atob(data['data-array'][i]['data'])
var encoded_data = FD.atob_FD(data["data-array"][i]["data"]);
var splitted_data = encoded_data.split(split_char);
if (splitted_data[0] == "f") {
dataArray.push(data["data-array"][i]);
}
if (splitted_data[0] == "m") {
messageArray.push(data["data-array"][i]);
}
} catch (e) {
socketLog.info(e);
}
}
rxBufferLengthGui = dataArray.length;
let Files = {
data: dataArray,
};
ipcRenderer.send("request-update-rx-buffer", Files);
//rxMsgBufferLengthGui = messageArray.length;
let Messages = {
data: messageArray,
};
//ipcRenderer.send('request-update-rx-msg-buffer', Messages);
ipcRenderer.send("request-new-msg-received", Messages);
}
}
//finally delete message buffer
socketchunk = "";
}
});
function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
//Get TNC State
exports.getTncState = function () {
command = '{"type" : "get", "command" : "tnc_state"}';
writeTncCommand(command);
};
//Get DATA State
exports.getDataState = function () {
command = '{"type" : "get", "command" : "data_state"}';
//writeTncCommand(command)
};
// Send Ping
exports.sendPing = function (dxcallsign) {
command =
'{"type" : "ping", "command" : "ping", "dxcallsign" : "' +
dxcallsign +
'"}';
writeTncCommand(command);
};
// Send Mesh Ping
exports.sendMeshPing = function (dxcallsign) {
command =
'{"type" : "mesh", "command" : "ping", "dxcallsign" : "' +
dxcallsign +
'"}';
writeTncCommand(command);
};
// Send CQ
exports.sendCQ = function () {
command = '{"type" : "broadcast", "command" : "cqcqcq"}';
writeTncCommand(command);
};
// Set AUDIO Level
exports.setTxAudioLevel = function (value) {
command =
'{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}';
writeTncCommand(command);
};
// Send File
exports.sendFile = function (
dxcallsign,
mode,
frames,
filename,
filetype,
data,
checksum,
) {
socketLog.info(data);
socketLog.info(filetype);
socketLog.info(filename);
var datatype = "f";
data =
datatype +
split_char +
filename +
split_char +
filetype +
split_char +
checksum +
split_char +
data;
socketLog.info(data);
//socketLog.info(btoa(data))
//Btoa / atob will not work with charsets > 8 bits (i.e. the emojis); should probably move away from using it
//TODO: Will need to update anyother occurences and throughly test
//data = btoa(data)
data = FD.btoa_FD(data);
command =
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
dxcallsign +
'", "mode" : "' +
mode +
'", "n_frames" : "' +
frames +
'", "data" : "' +
data +
'"}]}';
writeTncCommand(command);
};
// Send Message
exports.sendMessage = function (
dxcallsign,
mode,
frames,
data,
checksum,
uuid,
command,
) {
data = FD.btoa_FD(
"m" +
split_char +
command +
split_char +
checksum +
split_char +
uuid +
split_char +
data,
);
command =
'{"type" : "arq", "command" : "send_raw", "uuid" : "' +
uuid +
'", "parameter" : [{"dxcallsign" : "' +
dxcallsign +
'", "mode" : "' +
mode +
'", "n_frames" : "' +
frames +
'", "data" : "' +
data +
'", "attempts": "10"}]}';
socketLog.info(command);
socketLog.info("-------------------------------------");
writeTncCommand(command);
};
// Send Request message
//It would be then „m + split + request + split + request-type“
function sendRequest(dxcallsign, mode, frames, data, command) {
data = FD.btoa_FD("m" + split_char + command + split_char + data);
command =
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
dxcallsign +
'", "mode" : "' +
mode +
'", "n_frames" : "' +
frames +
'", "data" : "' +
data +
'", "attempts": "10"}]}';
socketLog.info(command);
socketLog.info("--------------REQ--------------------");
writeTncCommand(command);
}
// Send Response message
//It would be then „m + split + request + split + request-type“
function sendResponse(dxcallsign, mode, frames, data, command) {
data = FD.btoa_FD("m" + split_char + command + split_char + data);
command =
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
dxcallsign +
'", "mode" : "' +
mode +
'", "n_frames" : "' +
frames +
'", "data" : "' +
data +
'", "attempts": "10"}]}';
socketLog.info(command);
socketLog.info("--------------RES--------------------");
writeTncCommand(command);
}
//Send station info request
exports.sendRequestInfo = function (dxcallsign) {
//Command 0 = user/station information
//Command 1 = shared folder list
//Command 2 = shared file transfer
sendRequest(dxcallsign, 255, 1, "0", "req");
};
//Send shared folder file list request
exports.sendRequestSharedFolderList = function (dxcallsign) {
//Command 0 = user/station information
//Command 1 = shared folder list
//Command 2 = shared file transfer
sendRequest(dxcallsign, 255, 1, "1", "req");
};
//Send shared file request
exports.sendRequestSharedFile = function (dxcallsign, file) {
//Command 0 = user/station information
//Command 1 = shared folder list
//Command 2 = shared file transfer
sendRequest(dxcallsign, 255, 1, "2" + file, "req");
};
//Send station info response
exports.sendResponseInfo = function (dxcallsign, userinfo) {
//Command 0 = user/station information
//Command 1 = shared folder list
//Command 2 = shared file transfer
sendResponse(dxcallsign, 255, 1, userinfo, "res-0");
};
//Send shared folder response
exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) {
//Command 0 = user/station information
//Command 1 = shared folder list
//Command 2 = shared file transfer
sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1");
};
//Send shared file response
exports.sendResponseSharedFile = function (
dxcallsign,
sharedFile,
sharedFileData,
) {
console.log(
"In sendResponseSharedFile",
dxcallsign,
sharedFile,
sharedFileData,
);
//Command 0 = user/station information
//Command 1 = shared folder list
//Command 2 = shared file transfer
sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2");
};
//STOP TRANSMISSION
exports.stopTransmission = function () {
command = '{"type" : "arq", "command": "stop_transmission"}';
writeTncCommand(command);
};
// Get RX BUffer
exports.getRxBuffer = function () {
command = '{"type" : "get", "command" : "rx_buffer"}';
// call command only if new data arrived
if (rxBufferLengthGui != rxBufferLengthTnc) {
writeTncCommand(command);
}
};
// START BEACON
exports.startBeacon = function (interval) {
command =
'{"type" : "broadcast", "command" : "start_beacon", "parameter": "' +
interval +
'"}';
writeTncCommand(command);
};
// STOP BEACON
exports.stopBeacon = function () {
command = '{"type" : "broadcast", "command" : "stop_beacon"}';
writeTncCommand(command);
};
// OPEN ARQ SESSION
exports.connectARQ = function (dxcallsign) {
command =
'{"type" : "arq", "command" : "connect", "dxcallsign": "' +
dxcallsign +
'", "attempts": "10"}';
writeTncCommand(command);
};
// CLOSE ARQ SESSION
exports.disconnectARQ = function () {
command = '{"type" : "arq", "command" : "disconnect"}';
writeTncCommand(command);
};
// SEND TEST FRAME
exports.sendTestFrame = function () {
command = '{"type" : "set", "command" : "send_test_frame"}';
writeTncCommand(command);
};
// SEND FEC
exports.sendFEC = function (mode, payload) {
command =
'{"type" : "fec", "command" : "transmit", "mode" : "' +
mode +
'", "payload" : "' +
payload +
'"}';
writeTncCommand(command);
};
// SEND FEC IS WRITING
exports.sendFecIsWriting = function (mycallsign) {
command =
'{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' +
mycallsign +
'"}';
writeTncCommand(command);
};
// SEND FEC TO BROADCASTCHANNEL
exports.sendBroadcastChannel = function (channel, data_out, uuid) {
let checksum = "";
let command = "";
let data = FD.btoa_FD(
"m" +
split_char +
channel +
//split_char +
//checksum +
split_char +
uuid +
split_char +
data_out,
);
console.log(data.length);
let payload = data;
command =
'{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' +
payload +
'"}';
writeTncCommand(command);
};
// RECORD AUDIO
exports.record_audio = function () {
command = '{"type" : "set", "command" : "record_audio"}';
writeTncCommand(command);
};
// SET FREQUENCY
exports.set_frequency = function (frequency) {
command =
'{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}";
writeTncCommand(command);
};
// SET MODE
exports.set_mode = function (mode) {
command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}';
console.log(command);
writeTncCommand(command);
};
ipcRenderer.on("action-update-tnc-ip", (event, arg) => {
client.destroy();
let Data = {
busy_state: "-",
arq_state: "-",
//channel_state: "-",
frequency: "-",
mode: "-",
bandwidth: "-",
dbfs_level: 0,
};
ipcRenderer.send("request-update-tnc-state", Data);
tnc_port = arg.port;
tnc_host = arg.adress;
connectTNC();
});
// https://stackoverflow.com/a/50579690
// crc32 calculation
//console.log(crc32('abc'));
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
var crc32 = function (r) {
for (var a, o = [], c = 0; c < 256; c++) {
a = c;
for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1;
o[c] = a;
}
for (var n = -1, t = 0; t < r.length; t++)
n = (n >>> 8) ^ o[255 & (n ^ r.charCodeAt(t))];
return (-1 ^ n) >>> 0;
};

View File

@ -1,261 +0,0 @@
<option value="1">Hamlib Dummy</option>
<option value="2">Hamlib NET rigctl</option>
<option value="4">FLRig FLRig</option>
<option value="5">TRXManager TRXManager 5.7.630+</option>
<option value="6">Hamlib Dummy No VFO</option>
<option value="1001">Yaesu FT-847</option>
<option value="1003">Yaesu FT-1000D</option>
<option value="1004">Yaesu MARK-V FT-1000MP</option>
<option value="1005">Yaesu FT-747GX</option>
<option value="1006">Yaesu FT-757GX</option>
<option value="1007">Yaesu FT-757GXII</option>
<option value="1009">Yaesu FT-767GX</option>
<option value="1010">Yaesu FT-736R</option>
<option value="1011">Yaesu FT-840</option>
<option value="1013">Yaesu FT-900</option>
<option value="1014">Yaesu FT-920</option>
<option value="1015">Yaesu FT-890</option>
<option value="1016">Yaesu FT-990</option>
<option value="1017">Yaesu FRG-100</option>
<option value="1018">Yaesu FRG-9600</option>
<option value="1019">Yaesu FRG-8800</option>
<option value="1020">Yaesu FT-817</option>
<option value="1021">Yaesu FT-100</option>
<option value="1022">Yaesu FT-857</option>
<option value="1023">Yaesu FT-897</option>
<option value="1024">Yaesu FT-1000MP</option>
<option value="1025">Yaesu MARK-V Field FT-1000MP</option>
<option value="1026">Yaesu VR-5000</option>
<option value="1027">Yaesu FT-450</option>
<option value="1028">Yaesu FT-950</option>
<option value="1029">Yaesu FT-2000</option>
<option value="1030">Yaesu FTDX-9000</option>
<option value="1031">Yaesu FT-980</option>
<option value="1032">Yaesu FTDX-5000</option>
<option value="1033">Vertex Standard VX-1700</option>
<option value="1034">Yaesu FTDX-1200</option>
<option value="1035">Yaesu FT-991</option>
<option value="1036">Yaesu FT-891</option>
<option value="1037">Yaesu FTDX-3000</option>
<option value="1038">Yaesu FT-847UNI</option>
<option value="1039">Yaesu FT-600</option>
<option value="1040">Yaesu FTDX-101D</option>
<option value="1041">Yaesu FT-818</option>
<option value="1042">Yaesu FTDX-10</option>
<option value="1043">Yaesu FT-897D</option>
<option value="1044">Yaesu FTDX-101MP</option>
<option value="2001">Kenwood TS-50S</option>
<option value="2002">Kenwood TS-440S</option>
<option value="2003">Kenwood TS-450S</option>
<option value="2004">Kenwood TS-570D</option>
<option value="2005">Kenwood TS-690S</option>
<option value="2006">Kenwood TS-711</option>
<option value="2007">Kenwood TS-790</option>
<option value="2008">Kenwood TS-811</option>
<option value="2009">Kenwood TS-850</option>
<option value="2010">Kenwood TS-870S</option>
<option value="2011">Kenwood TS-940S</option>
<option value="2012">Kenwood TS-950S</option>
<option value="2013">Kenwood TS-950SDX</option>
<option value="2014">Kenwood TS-2000</option>
<option value="2015">Kenwood R-5000</option>
<option value="2016">Kenwood TS-570S</option>
<option value="2017">Kenwood TH-D7A</option>
<option value="2019">Kenwood TH-F6A</option>
<option value="2020">Kenwood TH-F7E</option>
<option value="2021">Elecraft K2</option>
<option value="2022">Kenwood TS-930</option>
<option value="2023">Kenwood TH-G71</option>
<option value="2024">Kenwood TS-680S</option>
<option value="2025">Kenwood TS-140S</option>
<option value="2026">Kenwood TM-D700</option>
<option value="2027">Kenwood TM-V7</option>
<option value="2028">Kenwood TS-480</option>
<option value="2029">Elecraft K3</option>
<option value="2030">Kenwood TRC-80</option>
<option value="2031">Kenwood TS-590S</option>
<option value="2032">SigFox Transfox</option>
<option value="2033">Kenwood TH-D72A</option>
<option value="2034">Kenwood TM-D710(G)</option>
<option value="2036">FlexRadio 6xxx</option>
<option value="2037">Kenwood TS-590SG</option>
<option value="2038">Elecraft XG3</option>
<option value="2039">Kenwood TS-990s</option>
<option value="2040">OpenHPSDR PiHPSDR</option>
<option value="2041">Kenwood TS-890S</option>
<option value="2042">Kenwood TH-D74</option>
<option value="2043">Elecraft K3S</option>
<option value="2044">Elecraft KX2</option>
<option value="2045">Elecraft KX3</option>
<option value="2046">Hilberling PT-8000A</option>
<option value="2047">Elecraft K4</option>
<option value="2048">FlexRadio/ANAN PowerSDR/Thetis</option>
<option value="2049">Malachite DSP</option>
<option value="3002">Icom IC-1275</option>
<option value="3003">Icom IC-271</option>
<option value="3004">Icom IC-275</option>
<option value="3006">Icom IC-471</option>
<option value="3007">Icom IC-475</option>
<option value="3009">Icom IC-706</option>
<option value="3010">Icom IC-706MkII</option>
<option value="3011">Icom IC-706MkIIG</option>
<option value="3012">Icom IC-707</option>
<option value="3013">Icom IC-718</option>
<option value="3014">Icom IC-725</option>
<option value="3015">Icom IC-726</option>
<option value="3016">Icom IC-728</option>
<option value="3017">Icom IC-729</option>
<option value="3019">Icom IC-735</option>
<option value="3020">Icom IC-736</option>
<option value="3021">Icom IC-737</option>
<option value="3022">Icom IC-738</option>
<option value="3023">Icom IC-746</option>
<option value="3024">Icom IC-751</option>
<option value="3026">Icom IC-756</option>
<option value="3027">Icom IC-756PRO</option>
<option value="3028">Icom IC-761</option>
<option value="3029">Icom IC-765</option>
<option value="3030">Icom IC-775</option>
<option value="3031">Icom IC-781</option>
<option value="3032">Icom IC-820H</option>
<option value="3034">Icom IC-821H</option>
<option value="3035">Icom IC-970</option>
<option value="3036">Icom IC-R10</option>
<option value="3037">Icom IC-R71</option>
<option value="3038">Icom IC-R72</option>
<option value="3039">Icom IC-R75</option>
<option value="3040">Icom IC-R7000</option>
<option value="3041">Icom IC-R7100</option>
<option value="3042">Icom ICR-8500</option>
<option value="3043">Icom IC-R9000</option>
<option value="3044">Icom IC-910</option>
<option value="3045">Icom IC-78</option>
<option value="3046">Icom IC-746PRO</option>
<option value="3047">Icom IC-756PROII</option>
<option value="3051">Ten-Tec Omni VI Plus</option>
<option value="3052">Optoelectronics OptoScan535</option>
<option value="3053">Optoelectronics OptoScan456</option>
<option value="3054">Icom IC ID-1</option>
<option value="3055">Icom IC-703</option>
<option value="3056">Icom IC-7800</option>
<option value="3057">Icom IC-756PROIII</option>
<option value="3058">Icom IC-R20</option>
<option value="3060">Icom IC-7000</option>
<option value="3061">Icom IC-7200</option>
<option value="3062">Icom IC-7700</option>
<option value="3063">Icom IC-7600</option>
<option value="3064">Ten-Tec Delta II</option>
<option value="3065">Icom IC-92D</option>
<option value="3066">Icom IC-R9500</option>
<option value="3067">Icom IC-7410</option>
<option value="3068">Icom IC-9100</option>
<option value="3069">Icom IC-RX7</option>
<option value="3070">Icom IC-7100</option>
<option value="3071">Icom ID-5100</option>
<option value="3072">Icom IC-2730</option>
<option value="3073">Icom IC-7300</option>
<option value="3074">Microtelecom Perseus</option>
<option value="3075">Icom IC-785x</option>
<option value="3076">Xeigu X108G</option>
<option value="3077">Icom IC-R6</option>
<option value="3078">Icom IC-7610</option>
<option value="3079">Icom IC-R8600</option>
<option value="3080">Icom IC-R30</option>
<option value="3081">Icom IC-9700</option>
<option value="3082">Icom ID-4100</option>
<option value="3083">Icom ID-31</option>
<option value="3084">Icom ID-51</option>
<option value="3085">Icom IC-705</option>
<option value="4001">Icom IC-PCR1000</option>
<option value="4002">Icom IC-PCR100</option>
<option value="4003">Icom IC-PCR1500</option>
<option value="4004">Icom IC-PCR2500</option>
<option value="5001">AOR AR8200</option>
<option value="5002">AOR AR8000</option>
<option value="5003">AOR AR7030</option>
<option value="5004">AOR AR5000</option>
<option value="5005">AOR AR3030</option>
<option value="5006">AOR AR3000A</option>
<option value="5008">AOR AR2700</option>
<option value="5013">AOR AR8600</option>
<option value="5014">AOR AR5000A</option>
<option value="5015">AOR AR7030 Plus</option>
<option value="5016">AOR SR2200</option>
<option value="6005">JRC NRD-525</option>
<option value="6006">JRC NRD-535D</option>
<option value="6007">JRC NRD-545 DSP</option>
<option value="8001">Uniden BC780xlt</option>
<option value="8002">Uniden BC245xlt</option>
<option value="8003">Uniden BC895xlt</option>
<option value="8004">Radio Shack PRO-2052</option>
<option value="8006">Uniden BC250D</option>
<option value="8010">Uniden BCD-396T</option>
<option value="8011">Uniden BCD-996T</option>
<option value="8012">Uniden BC898T</option>
<option value="9002">Drake R-8A</option>
<option value="9003">Drake R-8B</option>
<option value="10004">Lowe HF-235</option>
<option value="11003">Racal RA6790/GM</option>
<option value="11005">Racal RA3702</option>
<option value="12004">Watkins-Johnson WJ-8888</option>
<option value="14002">Skanti TRP8000</option>
<option value="14004">Skanti TRP 8255 S R</option>
<option value="15001">Winradio WR-1000</option>
<option value="15002">Winradio WR-1500</option>
<option value="15003">Winradio WR-1550</option>
<option value="15004">Winradio WR-3100</option>
<option value="15005">Winradio WR-3150</option>
<option value="15006">Winradio WR-3500</option>
<option value="15007">Winradio WR-3700</option>
<option value="15009">Winradio WR-G313</option>
<option value="16001">Ten-Tec TT-550</option>
<option value="16002">Ten-Tec TT-538 Jupiter</option>
<option value="16003">Ten-Tec RX-320</option>
<option value="16004">Ten-Tec RX-340</option>
<option value="16005">Ten-Tec RX-350</option>
<option value="16007">Ten-Tec TT-516 Argonaut V</option>
<option value="16008">Ten-Tec TT-565 Orion</option>
<option value="16009">Ten-Tec TT-585 Paragon</option>
<option value="16011">Ten-Tec TT-588 Omni VII</option>
<option value="16012">Ten-Tec RX-331</option>
<option value="16013">Ten-Tec TT-599 Eagle</option>
<option value="17001">Alinco DX-77</option>
<option value="17002">Alinco DX-SR8</option>
<option value="18001">Kachina 505DSP</option>
<option value="22001">TAPR DSP-10</option>
<option value="23001">Flex-radio SDR-1000</option>
<option value="23003">DTTS Microwave Society DttSP IPC</option>
<option value="23004">DTTS Microwave Society DttSP UDP</option>
<option value="24001">RFT EKD-500</option>
<option value="25001">Elektor Elektor 3/04</option>
<option value="25002">SAT-Schneider DRT1</option>
<option value="25003">Coding Technologies Digital World Traveller</option>
<option value="25006">AmQRP DDS-60</option>
<option value="25007">Elektor Elektor SDR-USB</option>
<option value="25008">mRS miniVNA</option>
<option value="25009">SoftRock Si570 AVR-USB</option>
<option value="25011">KTH-SDR kit Si570 PIC-USB</option>
<option value="25012">FiFi FiFi-SDR</option>
<option value="25013">AMSAT-UK FUNcube Dongle</option>
<option value="25014">N2ADR HiQSDR</option>
<option value="25015">Funkamateur FA-SDR</option>
<option value="25016">AE9RB Si570 Peaberry V1</option>
<option value="25017">AE9RB Si570 Peaberry V2</option>
<option value="25018">AMSAT-UK FUNcube Dongle Pro+</option>
<option value="25019">HobbyPCB RS-HFIQ</option>
<option value="26001">Video4Linux SW/FM radio</option>
<option value="26002">Video4Linux2 SW/FM radio</option>
<option value="27001">Rohde&Schwarz ESMC</option>
<option value="27002">Rohde&Schwarz EB200</option>
<option value="27003">Rohde&Schwarz XK2100</option>
<option value="28001">Philips/Simoco PRM8060</option>
<option value="29001">ADAT www.adat.ch ADT-200A</option>
<option value="30001">Icom IC-M700PRO</option>
<option value="30002">Icom IC-M802</option>
<option value="30003">Icom IC-M710</option>
<option value="30004">Icom IC-M803</option>
<option value="31001">Dorji DRA818V</option>
<option value="31002">Dorji DRA818U</option>
<option value="32001">Barrett 2050</option>
<option value="32002">Barrett 950</option>
<option value="33001">ELAD FDM-DUO</option>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
</head>
<body style="overflow: hidden">
<img src="img/icon_cube_border.png" width="100%" height="100%" />
</body>
</html>

View File

@ -107,3 +107,11 @@ https://stackoverflow.com/a/9622873
[data-bs-theme="dark"] {
/* default dark theme mods */
}
.modal-backdrop {
background-color: transparent;
}
.modal-backdrop.in {
filter: alpha(opacity=10);
opacity: .1
}

View File

@ -1,607 +0,0 @@
/*=============================================================
Filename: Spectrogram-1v00.js
JavaScript graphics functions to draw Spectrograms.
Date Description By
-------|-------------------------------------------------|---
12Nov18 First beta ARC
17Nov18 Added offset into data buffer ARC
08May19 this.imageURL URL added
bugfix: fixed isNaN test
Changed sgStart, sgStop to start, stop
Added options object to constructors ARC
10May19 Enabled Left to Right as well as Top to Bottom ARC
11May19 Added RasterscanSVG ARC
12May19 Added blnkline for horizontal ratser scans ARC
13May19 Eliminated unneccessary putImageData ARC
14May19 Removed toDataURL, not used drawImage is better
bugfix: SVG RHC names swapped ARC
02Jun19 bugfix: startOfs not honored in horizontalNewLine ARC
03Jun19 Flipped the SVG and RHC names for waterfalls ARC
04Jun19 Unflip SVG and RHC for horizontal mode ARC
Swap "SVG" & "RHC" strings to match fn names ARC
05Jun19 bugfix: WaterfallSVG scrolling wrong way ARC
10Jun19 bugfix: support lineRate=0 for static display
bugfix: ipBufPtr must be a ptr to a ptr ARC
11Jun19 Make ipBuffers an Array of Arrays, if lineRate=0
use all buffers else use only ipBuffer[0] ARC
13Jun19 Use Waterfall and Rasterscan plus direction
Use Boolean rater than string compare ARC
16Jun19 Use const and let ARC
20Jun19 Change order of parameters ARC
21Jun19 Add setLineRate method ARC
06Jul19 Released as Rev 1v00 ARC
==============================================================*/
var Waterfall, Rasterscan;
(function () {
Waterfall = function (ipBufAry, w, h, dir, options) {
var direction = typeof dir === "string" ? dir.toLowerCase() : "down";
switch (direction) {
case "up":
return new Spectrogram(ipBufAry, w, h, "WF", false, true, options);
case "down":
default:
return new Spectrogram(ipBufAry, w, h, "WF", true, true, options);
case "left":
return new Spectrogram(ipBufAry, w, h, "WF", false, false, options);
case "right":
return new Spectrogram(ipBufAry, w, h, "WF", true, false, options);
}
};
Rasterscan = function (ipBufAry, w, h, dir, options) {
const direction = typeof dir === "string" ? dir.toLowerCase() : "down";
switch (direction) {
case "up":
return new Spectrogram(ipBufAry, w, h, "RS", true, true, options);
case "down":
default:
return new Spectrogram(ipBufAry, w, h, "RS", false, true, options);
case "left":
return new Spectrogram(ipBufAry, w, h, "RS", false, false, options);
case "right":
return new Spectrogram(ipBufAry, w, h, "RS", true, false, options);
}
};
function Spectrogram(ipBufAry, w, h, sgMode, rhc, vert, options) {
const opt = typeof options === "object" ? options : {}; // avoid undeclared object errors
let offScreenCtx; // offscreen canvas drawing context
const pxPerLine = w || 200;
const lines = h || 200;
let lineRate = 30; // requested line rate for dynamic waterfalls
let interval = 0; // msec
let startOfs = 0;
const lineBuf = new ArrayBuffer(pxPerLine * 4); // 1 line
const lineBuf8 = new Uint8ClampedArray(lineBuf);
const lineImgData = new ImageData(lineBuf8, pxPerLine, 1); // 1 line of canvas pixels
let pageImgData; // lines * pxPerLine of canvas pixels
let ipBuf8; // map input data to 0..255 unsigned bytes
const blankBuf = new ArrayBuffer(pxPerLine * 4); // 1 line
const blankBuf8 = new Uint8ClampedArray(blankBuf);
const blankImgData = new ImageData(blankBuf8, pxPerLine, 1); // 1 line of canvas pixels
const clearBuf = new ArrayBuffer(pxPerLine * lines * 4); // fills with 0s ie. rgba 0,0,0,0 = transparent
const clearBuf8 = new Uint8ClampedArray(clearBuf);
let clearImgData;
let nextLine = 0;
let timerID = null;
let running = false;
let sgTime = 0;
let sgStartTime = 0;
// Matlab Jet ref: stackoverflow.com grayscale-to-red-green-blue-matlab-jet-color-scale
let colMap = [
[0, 0, 128, 255],
[0, 0, 131, 255],
[0, 0, 135, 255],
[0, 0, 139, 255],
[0, 0, 143, 255],
[0, 0, 147, 255],
[0, 0, 151, 255],
[0, 0, 155, 255],
[0, 0, 159, 255],
[0, 0, 163, 255],
[0, 0, 167, 255],
[0, 0, 171, 255],
[0, 0, 175, 255],
[0, 0, 179, 255],
[0, 0, 183, 255],
[0, 0, 187, 255],
[0, 0, 191, 255],
[0, 0, 195, 255],
[0, 0, 199, 255],
[0, 0, 203, 255],
[0, 0, 207, 255],
[0, 0, 211, 255],
[0, 0, 215, 255],
[0, 0, 219, 255],
[0, 0, 223, 255],
[0, 0, 227, 255],
[0, 0, 231, 255],
[0, 0, 235, 255],
[0, 0, 239, 255],
[0, 0, 243, 255],
[0, 0, 247, 255],
[0, 0, 251, 255],
[0, 0, 255, 255],
[0, 4, 255, 255],
[0, 8, 255, 255],
[0, 12, 255, 255],
[0, 16, 255, 255],
[0, 20, 255, 255],
[0, 24, 255, 255],
[0, 28, 255, 255],
[0, 32, 255, 255],
[0, 36, 255, 255],
[0, 40, 255, 255],
[0, 44, 255, 255],
[0, 48, 255, 255],
[0, 52, 255, 255],
[0, 56, 255, 255],
[0, 60, 255, 255],
[0, 64, 255, 255],
[0, 68, 255, 255],
[0, 72, 255, 255],
[0, 76, 255, 255],
[0, 80, 255, 255],
[0, 84, 255, 255],
[0, 88, 255, 255],
[0, 92, 255, 255],
[0, 96, 255, 255],
[0, 100, 255, 255],
[0, 104, 255, 255],
[0, 108, 255, 255],
[0, 112, 255, 255],
[0, 116, 255, 255],
[0, 120, 255, 255],
[0, 124, 255, 255],
[0, 128, 255, 255],
[0, 131, 255, 255],
[0, 135, 255, 255],
[0, 139, 255, 255],
[0, 143, 255, 255],
[0, 147, 255, 255],
[0, 151, 255, 255],
[0, 155, 255, 255],
[0, 159, 255, 255],
[0, 163, 255, 255],
[0, 167, 255, 255],
[0, 171, 255, 255],
[0, 175, 255, 255],
[0, 179, 255, 255],
[0, 183, 255, 255],
[0, 187, 255, 255],
[0, 191, 255, 255],
[0, 195, 255, 255],
[0, 199, 255, 255],
[0, 203, 255, 255],
[0, 207, 255, 255],
[0, 211, 255, 255],
[0, 215, 255, 255],
[0, 219, 255, 255],
[0, 223, 255, 255],
[0, 227, 255, 255],
[0, 231, 255, 255],
[0, 235, 255, 255],
[0, 239, 255, 255],
[0, 243, 255, 255],
[0, 247, 255, 255],
[0, 251, 255, 255],
[0, 255, 255, 255],
[4, 255, 251, 255],
[8, 255, 247, 255],
[12, 255, 243, 255],
[16, 255, 239, 255],
[20, 255, 235, 255],
[24, 255, 231, 255],
[28, 255, 227, 255],
[32, 255, 223, 255],
[36, 255, 219, 255],
[40, 255, 215, 255],
[44, 255, 211, 255],
[48, 255, 207, 255],
[52, 255, 203, 255],
[56, 255, 199, 255],
[60, 255, 195, 255],
[64, 255, 191, 255],
[68, 255, 187, 255],
[72, 255, 183, 255],
[76, 255, 179, 255],
[80, 255, 175, 255],
[84, 255, 171, 255],
[88, 255, 167, 255],
[92, 255, 163, 255],
[96, 255, 159, 255],
[100, 255, 155, 255],
[104, 255, 151, 255],
[108, 255, 147, 255],
[112, 255, 143, 255],
[116, 255, 139, 255],
[120, 255, 135, 255],
[124, 255, 131, 255],
[128, 255, 128, 255],
[131, 255, 124, 255],
[135, 255, 120, 255],
[139, 255, 116, 255],
[143, 255, 112, 255],
[147, 255, 108, 255],
[151, 255, 104, 255],
[155, 255, 100, 255],
[159, 255, 96, 255],
[163, 255, 92, 255],
[167, 255, 88, 255],
[171, 255, 84, 255],
[175, 255, 80, 255],
[179, 255, 76, 255],
[183, 255, 72, 255],
[187, 255, 68, 255],
[191, 255, 64, 255],
[195, 255, 60, 255],
[199, 255, 56, 255],
[203, 255, 52, 255],
[207, 255, 48, 255],
[211, 255, 44, 255],
[215, 255, 40, 255],
[219, 255, 36, 255],
[223, 255, 32, 255],
[227, 255, 28, 255],
[231, 255, 24, 255],
[235, 255, 20, 255],
[239, 255, 16, 255],
[243, 255, 12, 255],
[247, 255, 8, 255],
[251, 255, 4, 255],
[255, 255, 0, 255],
[255, 251, 0, 255],
[255, 247, 0, 255],
[255, 243, 0, 255],
[255, 239, 0, 255],
[255, 235, 0, 255],
[255, 231, 0, 255],
[255, 227, 0, 255],
[255, 223, 0, 255],
[255, 219, 0, 255],
[255, 215, 0, 255],
[255, 211, 0, 255],
[255, 207, 0, 255],
[255, 203, 0, 255],
[255, 199, 0, 255],
[255, 195, 0, 255],
[255, 191, 0, 255],
[255, 187, 0, 255],
[255, 183, 0, 255],
[255, 179, 0, 255],
[255, 175, 0, 255],
[255, 171, 0, 255],
[255, 167, 0, 255],
[255, 163, 0, 255],
[255, 159, 0, 255],
[255, 155, 0, 255],
[255, 151, 0, 255],
[255, 147, 0, 255],
[255, 143, 0, 255],
[255, 139, 0, 255],
[255, 135, 0, 255],
[255, 131, 0, 255],
[255, 128, 0, 255],
[255, 124, 0, 255],
[255, 120, 0, 255],
[255, 116, 0, 255],
[255, 112, 0, 255],
[255, 108, 0, 255],
[255, 104, 0, 255],
[255, 100, 0, 255],
[255, 96, 0, 255],
[255, 92, 0, 255],
[255, 88, 0, 255],
[255, 84, 0, 255],
[255, 80, 0, 255],
[255, 76, 0, 255],
[255, 72, 0, 255],
[255, 68, 0, 255],
[255, 64, 0, 255],
[255, 60, 0, 255],
[255, 56, 0, 255],
[255, 52, 0, 255],
[255, 48, 0, 255],
[255, 44, 0, 255],
[255, 40, 0, 255],
[255, 36, 0, 255],
[255, 32, 0, 255],
[255, 28, 0, 255],
[255, 24, 0, 255],
[255, 20, 0, 255],
[255, 16, 0, 255],
[255, 12, 0, 255],
[255, 8, 0, 255],
[255, 4, 0, 255],
[255, 0, 0, 255],
[251, 0, 0, 255],
[247, 0, 0, 255],
[243, 0, 0, 255],
[239, 0, 0, 255],
[235, 0, 0, 255],
[231, 0, 0, 255],
[227, 0, 0, 255],
[223, 0, 0, 255],
[219, 0, 0, 255],
[215, 0, 0, 255],
[211, 0, 0, 255],
[207, 0, 0, 255],
[203, 0, 0, 255],
[199, 0, 0, 255],
[195, 0, 0, 255],
[191, 0, 0, 255],
[187, 0, 0, 255],
[183, 0, 0, 255],
[179, 0, 0, 255],
[175, 0, 0, 255],
[171, 0, 0, 255],
[167, 0, 0, 255],
[163, 0, 0, 255],
[159, 0, 0, 255],
[155, 0, 0, 255],
[151, 0, 0, 255],
[147, 0, 0, 255],
[143, 0, 0, 255],
[139, 0, 0, 255],
[135, 0, 0, 255],
[131, 0, 0, 255],
[0, 0, 0, 0],
];
function incrLine() {
if ((vert && !rhc) || (!vert && rhc)) {
nextLine++;
if (nextLine >= lines) {
nextLine = 0;
}
} else {
nextLine--;
if (nextLine < 0) {
nextLine = lines - 1;
}
}
}
function updateWaterfall() {
// update dynamic waterfalls at a fixed rate
let sgDiff;
// grab latest line of data, write it to off screen buffer, inc 'nextLine'
sgNewLine();
// loop to write data data at the desired rate, data is being updated asynchronously
// ref for accurate timeout: http://www.sitepoint.com/creating-accurate-timers-in-javascript
sgTime += interval;
sgDiff = Date.now() - sgStartTime - sgTime;
if (running) {
timerID = setTimeout(updateWaterfall, interval - sgDiff);
}
}
function sgSetLineRate(newRate) {
if (isNaN(newRate) || newRate > 50 || newRate < 0) {
console.error("invalid line rate [0 <= lineRate < 50 lines/sec]");
// don't change the lineRate;
} else if (newRate === 0) {
// static (one pass) raster
lineRate = 0;
} else {
lineRate = newRate;
interval = 1000 / lineRate; // msec
}
}
this.setLineRate = sgSetLineRate;
function setProperty(propertyName, value) {
if (typeof propertyName !== "string" || value === undefined) {
// null is OK, forces default
return;
}
switch (propertyName.toLowerCase()) {
case "linerate":
sgSetLineRate(value); // setLine does checks for number etc
break;
case "startbin":
if (!isNaN(value) && value > 0) {
startOfs = value;
}
break;
case "onscreenparentid":
if (typeof value === "string" && document.getElementById(value)) {
demoCvsId = value;
}
break;
case "colormap":
if (
Array.isArray(value) &&
Array.isArray(value[0]) &&
value[0].length == 4
) {
colMap = value; // value must be an array of 4 element arrays to get here
if (colMap.length < 256) {
// fill out the remaining colors with last color
for (let i = colMap.length; i < 256; i++) {
colMap[i] = colMap[colMap.length - 1];
}
}
}
break;
default:
break;
}
}
function verticalNewLine() {
let tmpImgData, ipBuf8;
if (sgMode == "WF") {
if (rhc) {
// shift the current display down 1 line, oldest line drops off
tmpImgData = offScreenCtx.getImageData(0, 0, pxPerLine, lines - 1);
offScreenCtx.putImageData(tmpImgData, 0, 1);
} else {
// shift the current display up 1 line, oldest line drops off
tmpImgData = offScreenCtx.getImageData(0, 1, pxPerLine, lines - 1);
offScreenCtx.putImageData(tmpImgData, 0, 0);
}
}
ipBuf8 = Uint8ClampedArray.from(ipBufAry[0]);
for (
let sigVal, rgba, opIdx = 0, ipIdx = startOfs;
ipIdx < pxPerLine + startOfs;
opIdx += 4, ipIdx++
) {
sigVal = ipBuf8[ipIdx] || 0; // if input line too short add zeros
rgba = colMap[sigVal]; // array of rgba values
// byte reverse so number aa bb gg rr
lineBuf8[opIdx] = rgba[0]; // red
lineBuf8[opIdx + 1] = rgba[1]; // green
lineBuf8[opIdx + 2] = rgba[2]; // blue
lineBuf8[opIdx + 3] = rgba[3]; // alpha
}
offScreenCtx.putImageData(lineImgData, 0, nextLine);
if (sgMode === "RS") {
incrLine();
// if not static draw a white line in front of the current line to indicate new data point
if (lineRate) {
offScreenCtx.putImageData(blankImgData, 0, nextLine);
}
}
}
function horizontalNewLine() {
let tmpImgData, ipBuf8;
if (sgMode == "WF") {
if (rhc) {
// shift the current display right 1 line, oldest line drops off
tmpImgData = offScreenCtx.getImageData(0, 0, lines - 1, pxPerLine);
offScreenCtx.putImageData(tmpImgData, 1, 0);
} else {
// shift the current display left 1 line, oldest line drops off
tmpImgData = offScreenCtx.getImageData(1, 0, lines - 1, pxPerLine);
offScreenCtx.putImageData(tmpImgData, 0, 0);
}
}
// refresh the page image (it was just shifted)
pageImgData = offScreenCtx.getImageData(0, 0, lines, pxPerLine);
if (ipBufAry[0].constructor !== Uint8Array) {
ipBuf8 = Uint8ClampedArray.from(ipBufAry[0]); // clamp input values to 0..255 range
} else {
ipBuf8 = ipBufAry[0]; // conversion already done
}
for (let sigVal, rgba, opIdx, ipIdx = 0; ipIdx < pxPerLine; ipIdx++) {
sigVal = ipBuf8[ipIdx + startOfs] || 0; // if input line too short add zeros
rgba = colMap[sigVal]; // array of rgba values
opIdx = 4 * ((pxPerLine - ipIdx - 1) * lines + nextLine);
// byte reverse so number aa bb gg rr
pageImgData.data[opIdx] = rgba[0]; // red
pageImgData.data[opIdx + 1] = rgba[1]; // green
pageImgData.data[opIdx + 2] = rgba[2]; // blue
pageImgData.data[opIdx + 3] = rgba[3]; // alpha
}
if (sgMode === "RS") {
incrLine();
// if not draw a white line in front of the current line to indicate new data point
if (lineRate) {
for (let j = 0; j < pxPerLine; j++) {
if (rhc) {
opIdx = 4 * (j * lines + nextLine);
} else {
opIdx = 4 * ((pxPerLine - j - 1) * lines + nextLine);
}
// byte reverse so number aa bb gg rr
pageImgData.data[opIdx] = 255; // red
pageImgData.data[opIdx + 1] = 255; // green
pageImgData.data[opIdx + 2] = 255; // blue
pageImgData.data[opIdx + 3] = 255; // alpha
}
}
}
offScreenCtx.putImageData(pageImgData, 0, 0);
}
const sgNewLine = vert ? verticalNewLine : horizontalNewLine; // function pointers
//===== set all the options ================
for (let prop in opt) {
// check that this is opt's own property, not inherited from prototype
if (opt.hasOwnProperty(prop)) {
setProperty(prop, opt[prop]);
}
}
// ===== now make the exposed properties and methods ===============
this.newLine = sgNewLine;
this.offScreenCvs = document.createElement("canvas");
if (vert) {
this.offScreenCvs.setAttribute("width", pxPerLine); // reset canvas pixels width
this.offScreenCvs.setAttribute("height", lines); // don't use style for this
clearImgData = new ImageData(clearBuf8, pxPerLine, lines);
} // data written in columns
else {
this.offScreenCvs.setAttribute("width", lines); // reset canvas pixels width
this.offScreenCvs.setAttribute("height", pxPerLine); // don't use style for this
clearImgData = new ImageData(clearBuf8, lines, pxPerLine);
}
offScreenCtx = this.offScreenCvs.getContext("2d");
this.clear = function () {
offScreenCtx.putImageData(clearImgData, 0, 0);
};
this.start = function () {
sgStartTime = Date.now();
sgTime = 0;
running = true;
updateWaterfall(); // start the update loop
};
this.stop = function () {
running = false;
if (timerID) {
clearTimeout(timerID);
}
// reset where the next line is to be written
if (sgMode === "RS") {
if (vert) {
nextLine = rhc ? lines - 1 : 0;
} else {
nextLine = rhc ? 0 : lines - 1;
}
} // WF
else {
nextLine = rhc ? 0 : lines - 1;
}
};
// make a white line, it will show the input line for RS displays
blankBuf8.fill(255);
// make a full canvas of the color map 0 values
for (let i = 0; i < pxPerLine * lines * 4; i += 4) {
// byte reverse so number aa bb gg rr
clearBuf8[i] = colMap[0][0]; // red
clearBuf8[i + 1] = colMap[0][1]; // green
clearBuf8[i + 2] = colMap[0][2]; // blue
clearBuf8[i + 3] = colMap[0][3]; // alpha
}
// for diagnostics only
if (typeof demoCvsId == "string") {
document.getElementById(demoCvsId).appendChild(this.offScreenCvs);
}
// initialize the direction and first line position
this.stop();
// everything is set
// if dynamic, wait for the start or newLine methods to be called
}
})();

View File

@ -1,488 +0,0 @@
/*
* Copyright (c) 2019 Jeppe Ledet-Pedersen
* This software is released under the MIT license.
* See the LICENSE file for further details.
*/
"use strict";
Spectrum.prototype.squeeze = function (value, out_min, out_max) {
if (value <= this.min_db) return out_min;
else if (value >= this.max_db) return out_max;
else
return Math.round(
((value - this.min_db) / (this.max_db - this.min_db)) * out_max
);
};
Spectrum.prototype.rowToImageData = function (bins) {
for (var i = 0; i < this.imagedata.data.length; i += 4) {
var cindex = this.squeeze(bins[i / 4], 0, 255);
var color = this.colormap[cindex];
this.imagedata.data[i + 0] = color[0];
this.imagedata.data[i + 1] = color[1];
this.imagedata.data[i + 2] = color[2];
this.imagedata.data[i + 3] = 255;
}
};
Spectrum.prototype.addWaterfallRow = function (bins) {
// Shift waterfall 1 row down
this.ctx_wf.drawImage(
this.ctx_wf.canvas,
0,
0,
this.wf_size,
this.wf_rows - 1,
0,
1,
this.wf_size,
this.wf_rows - 1
);
// Draw new line on waterfall canvas
this.rowToImageData(bins);
this.ctx_wf.putImageData(this.imagedata, 0, 0);
var width = this.ctx.canvas.width;
var height = this.ctx.canvas.height;
// Copy scaled FFT canvas to screen. Only copy the number of rows that will
// fit in waterfall area to avoid vertical scaling.
this.ctx.imageSmoothingEnabled = false;
var rows = Math.min(this.wf_rows, height - this.spectrumHeight);
this.ctx.drawImage(
this.ctx_wf.canvas,
0,
0,
this.wf_size,
rows,
0,
this.spectrumHeight,
width,
height - this.spectrumHeight
);
};
Spectrum.prototype.drawFFT = function (bins) {
this.ctx.beginPath();
this.ctx.moveTo(-1, this.spectrumHeight + 1);
for (var i = 0; i < bins.length; i++) {
var y = this.spectrumHeight - this.squeeze(bins[i], 0, this.spectrumHeight);
if (y > this.spectrumHeight - 1) y = this.spectrumHeight + 1; // Hide underflow
if (y < 0) y = 0;
if (i == 0) this.ctx.lineTo(-1, y);
this.ctx.lineTo(i, y);
if (i == bins.length - 1) this.ctx.lineTo(this.wf_size + 1, y);
}
this.ctx.lineTo(this.wf_size + 1, this.spectrumHeight + 1);
this.ctx.strokeStyle = "#fefefe";
this.ctx.stroke();
};
//Spectrum.prototype.drawSpectrum = function(bins) {
Spectrum.prototype.drawSpectrum = function () {
var width = this.ctx.canvas.width;
var height = this.ctx.canvas.height;
// Modification by DJ2LS
// Draw bandwidth lines
// TODO: Math not correct. But a first attempt
// it seems position is more or less equal to frequenzy by factor 10
// eg. position 150 == 1500Hz
/*
// CENTER LINE
this.ctx_wf.beginPath();
this.ctx_wf.moveTo(150,0);
this.ctx_wf.lineTo(150, height);
this.ctx_wf.lineWidth = 1;
this.ctx_wf.strokeStyle = '#8C8C8C';
this.ctx_wf.stroke()
*/
// 586Hz and 1700Hz LINES
var linePositionLow = 121.6; //150 - bandwith/20
var linePositionHigh = 178.4; //150 + bandwidth/20
var linePositionLow2 = 65; //150 - bandwith/20
var linePositionHigh2 = 235; //150 + bandwith/20
this.ctx_wf.beginPath();
this.ctx_wf.moveTo(linePositionLow, 0);
this.ctx_wf.lineTo(linePositionLow, height);
this.ctx_wf.moveTo(linePositionHigh, 0);
this.ctx_wf.lineTo(linePositionHigh, height);
this.ctx_wf.moveTo(linePositionLow2, 0);
this.ctx_wf.lineTo(linePositionLow2, height);
this.ctx_wf.moveTo(linePositionHigh2, 0);
this.ctx_wf.lineTo(linePositionHigh2, height);
this.ctx_wf.lineWidth = 1;
this.ctx_wf.strokeStyle = "#C3C3C3";
this.ctx_wf.stroke();
// ---- END OF MODIFICATION ------
// Fill with black
this.ctx.fillStyle = "white";
this.ctx.fillRect(0, 0, width, height);
//Commenting out the remainder of this code, it's not needed and unused as of 6.9.11 and saves three if statements
return;
/*
// FFT averaging
if (this.averaging > 0) {
if (!this.binsAverage || this.binsAverage.length != bins.length) {
this.binsAverage = Array.from(bins);
} else {
for (var i = 0; i < bins.length; i++) {
this.binsAverage[i] += this.alpha * (bins[i] - this.binsAverage[i]);
}
}
bins = this.binsAverage;
}
// Max hold
if (this.maxHold) {
if (!this.binsMax || this.binsMax.length != bins.length) {
this.binsMax = Array.from(bins);
} else {
for (var i = 0; i < bins.length; i++) {
if (bins[i] > this.binsMax[i]) {
this.binsMax[i] = bins[i];
} else {
// Decay
this.binsMax[i] = 1.0025 * this.binsMax[i];
}
}
}
}
// Do not draw anything if spectrum is not visible
if (this.ctx_axes.canvas.height < 1)
return;
// Scale for FFT
this.ctx.save();
this.ctx.scale(width / this.wf_size, 1);
// Draw maxhold
if (this.maxHold)
this.drawFFT(this.binsMax);
// Draw FFT bins
this.drawFFT(bins);
// Restore scale
this.ctx.restore();
// Fill scaled path
this.ctx.fillStyle = this.gradient;
this.ctx.fill();
// Copy axes from offscreen canvas
this.ctx.drawImage(this.ctx_axes.canvas, 0, 0);
*/
};
//Allow setting colormap
Spectrum.prototype.setColorMap = function (index) {
this.colormap = colormaps[index];
};
Spectrum.prototype.updateAxes = function () {
var width = this.ctx_axes.canvas.width;
var height = this.ctx_axes.canvas.height;
// Clear axes canvas
this.ctx_axes.clearRect(0, 0, width, height);
// Draw axes
this.ctx_axes.font = "12px sans-serif";
this.ctx_axes.fillStyle = "white";
this.ctx_axes.textBaseline = "middle";
this.ctx_axes.textAlign = "left";
var step = 10;
for (var i = this.min_db + 10; i <= this.max_db - 10; i += step) {
var y = height - this.squeeze(i, 0, height);
this.ctx_axes.fillText(i, 5, y);
this.ctx_axes.beginPath();
this.ctx_axes.moveTo(20, y);
this.ctx_axes.lineTo(width, y);
this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)";
this.ctx_axes.stroke();
}
this.ctx_axes.textBaseline = "bottom";
for (var i = 0; i < 11; i++) {
var x = Math.round(width / 10) * i;
if (this.spanHz > 0) {
var adjust = 0;
if (i == 0) {
this.ctx_axes.textAlign = "left";
adjust = 3;
} else if (i == 10) {
this.ctx_axes.textAlign = "right";
adjust = -3;
} else {
this.ctx_axes.textAlign = "center";
}
var freq = this.centerHz + (this.spanHz / 10) * (i - 5);
if (this.centerHz + this.spanHz > 1e6) freq = freq / 1e6 + "M";
else if (this.centerHz + this.spanHz > 1e3) freq = freq / 1e3 + "k";
this.ctx_axes.fillText(freq, x + adjust, height - 3);
}
this.ctx_axes.beginPath();
this.ctx_axes.moveTo(x, 0);
this.ctx_axes.lineTo(x, height);
this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.10)";
this.ctx_axes.stroke();
}
};
Spectrum.prototype.addData = function (data) {
if (!this.paused) {
if (data.length != this.wf_size) {
this.wf_size = data.length;
this.ctx_wf.canvas.width = data.length;
this.ctx_wf.fillStyle = "white";
this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height);
this.imagedata = this.ctx_wf.createImageData(data.length, 1);
}
//this.drawSpectrum(data);
this.drawSpectrum();
this.addWaterfallRow(data);
this.resize();
}
};
Spectrum.prototype.updateSpectrumRatio = function () {
this.spectrumHeight = Math.round(
(this.canvas.height * this.spectrumPercent) / 100.0
);
this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight);
for (var i = 0; i < this.colormap.length; i++) {
var c = this.colormap[this.colormap.length - 1 - i];
this.gradient.addColorStop(
i / this.colormap.length,
"rgba(" + c[0] + "," + c[1] + "," + c[2] + ", 1.0)"
);
}
};
Spectrum.prototype.resize = function () {
var width = this.canvas.clientWidth;
var height = this.canvas.clientHeight;
if (this.canvas.width != width || this.canvas.height != height) {
this.canvas.width = width;
this.canvas.height = height;
this.updateSpectrumRatio();
}
if (this.axes.width != width || this.axes.height != this.spectrumHeight) {
this.axes.width = width;
this.axes.height = this.spectrumHeight;
this.updateAxes();
}
};
Spectrum.prototype.setSpectrumPercent = function (percent) {
if (percent >= 0 && percent <= 100) {
this.spectrumPercent = percent;
this.updateSpectrumRatio();
}
};
Spectrum.prototype.incrementSpectrumPercent = function () {
if (this.spectrumPercent + this.spectrumPercentStep <= 100) {
this.setSpectrumPercent(this.spectrumPercent + this.spectrumPercentStep);
}
};
Spectrum.prototype.decrementSpectrumPercent = function () {
if (this.spectrumPercent - this.spectrumPercentStep >= 0) {
this.setSpectrumPercent(this.spectrumPercent - this.spectrumPercentStep);
}
};
Spectrum.prototype.toggleColor = function () {
this.colorindex++;
if (this.colorindex >= colormaps.length) this.colorindex = 0;
this.colormap = colormaps[this.colorindex];
this.updateSpectrumRatio();
};
Spectrum.prototype.setRange = function (min_db, max_db) {
this.min_db = min_db;
this.max_db = max_db;
this.updateAxes();
};
Spectrum.prototype.rangeUp = function () {
this.setRange(this.min_db - 5, this.max_db - 5);
};
Spectrum.prototype.rangeDown = function () {
this.setRange(this.min_db + 5, this.max_db + 5);
};
Spectrum.prototype.rangeIncrease = function () {
this.setRange(this.min_db - 5, this.max_db + 5);
};
Spectrum.prototype.rangeDecrease = function () {
if (this.max_db - this.min_db > 10)
this.setRange(this.min_db + 5, this.max_db - 5);
};
Spectrum.prototype.setCenterHz = function (hz) {
this.centerHz = hz;
this.updateAxes();
};
Spectrum.prototype.setSpanHz = function (hz) {
this.spanHz = hz;
this.updateAxes();
};
Spectrum.prototype.setAveraging = function (num) {
if (num >= 0) {
this.averaging = num;
this.alpha = 2 / (this.averaging + 1);
}
};
Spectrum.prototype.incrementAveraging = function () {
this.setAveraging(this.averaging + 1);
};
Spectrum.prototype.decrementAveraging = function () {
if (this.averaging > 0) {
this.setAveraging(this.averaging - 1);
}
};
Spectrum.prototype.setPaused = function (paused) {
this.paused = paused;
};
Spectrum.prototype.togglePaused = function () {
this.setPaused(!this.paused);
};
Spectrum.prototype.setMaxHold = function (maxhold) {
this.maxHold = maxhold;
this.binsMax = undefined;
};
Spectrum.prototype.toggleMaxHold = function () {
this.setMaxHold(!this.maxHold);
};
Spectrum.prototype.toggleFullscreen = function () {
if (!this.fullscreen) {
if (this.canvas.requestFullscreen) {
this.canvas.requestFullscreen();
} else if (this.canvas.mozRequestFullScreen) {
this.canvas.mozRequestFullScreen();
} else if (this.canvas.webkitRequestFullscreen) {
this.canvas.webkitRequestFullscreen();
} else if (this.canvas.msRequestFullscreen) {
this.canvas.msRequestFullscreen();
}
this.fullscreen = true;
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
this.fullscreen = false;
}
};
Spectrum.prototype.onKeypress = function (e) {
if (e.key == " ") {
this.togglePaused();
} else if (e.key == "f") {
this.toggleFullscreen();
} else if (e.key == "c") {
this.toggleColor();
} else if (e.key == "ArrowUp") {
this.rangeUp();
} else if (e.key == "ArrowDown") {
this.rangeDown();
} else if (e.key == "ArrowLeft") {
this.rangeDecrease();
} else if (e.key == "ArrowRight") {
this.rangeIncrease();
} else if (e.key == "s") {
this.incrementSpectrumPercent();
} else if (e.key == "w") {
this.decrementSpectrumPercent();
} else if (e.key == "+") {
this.incrementAveraging();
} else if (e.key == "-") {
this.decrementAveraging();
} else if (e.key == "m") {
this.toggleMaxHold();
}
};
function Spectrum(id, options) {
// Handle options
this.centerHz = options && options.centerHz ? options.centerHz : 1500;
this.spanHz = options && options.spanHz ? options.spanHz : 0;
this.wf_size = options && options.wf_size ? options.wf_size : 0;
this.wf_rows = options && options.wf_rows ? options.wf_rows : 1024;
this.spectrumPercent =
options && options.spectrumPercent ? options.spectrumPercent : 0;
this.spectrumPercentStep =
options && options.spectrumPercentStep ? options.spectrumPercentStep : 0;
this.averaging = options && options.averaging ? options.averaging : 0;
this.maxHold = options && options.maxHold ? options.maxHold : false;
// Setup state
this.paused = false;
this.fullscreen = false;
this.min_db = 0;
this.max_db = 70;
this.spectrumHeight = 0;
// Colors
this.colorindex = 0;
this.colormap = colormaps[2];
// Create main canvas and adjust dimensions to match actual
this.canvas = document.getElementById(id);
this.canvas.height = this.canvas.clientHeight;
this.canvas.width = this.canvas.clientWidth;
this.ctx = this.canvas.getContext("2d");
this.ctx.fillStyle = "white";
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Create offscreen canvas for axes
this.axes = document.createElement("canvas");
this.axes.height = 1; // Updated later
this.axes.width = this.canvas.width;
this.ctx_axes = this.axes.getContext("2d");
// Create offscreen canvas for waterfall
this.wf = document.createElement("canvas");
this.wf.height = this.wf_rows;
this.wf.width = this.wf_size;
this.ctx_wf = this.wf.getContext("2d");
// Trigger first render
this.setAveraging(this.averaging);
this.updateSpectrumRatio();
this.resize();
}

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

View File

@ -1,82 +0,0 @@
{
"name": "FreeDATA",
"description": "FreeDATA ",
"private": true,
"version": "0.11.0-alpha",
"main": "dist-electron/main/index.js",
"scripts": {
"start": "git pull && npm i && vite",
"dev": "vite",
"check" : "vue-tsc --noEmit",
"build": "vue-tsc --noEmit && vite build && electron-builder",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue src",
"lint-fix": "eslint --ext .js,.vue --fix src"
},
"repository": {
"type": "git",
"url": "https://github.com/DJ2LS/FreeDATA.git"
},
"keywords": [
"TNC",
"GUI",
"FreeDATA",
"codec2"
],
"author": "DJ2LS",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/DJ2LS/FreeDATA/issues"
},
"homepage": "https://freedata.app",
"dependencies": {
"@electron/notarize": "^2.1.0",
"@vueuse/electron": "^10.4.1",
"blob-util": "^2.0.2",
"bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5",
"bootswatch": "^5.3.1",
"browser-image-compression": "^2.0.2",
"chart.js": "^4.3.3",
"chartjs-plugin-annotation": "^3.0.1",
"electron-builder-notarize": "^1.5.1",
"electron-log": "^4.4.8",
"electron-updater": "^6.1.4",
"emoji-picker-element": "^1.18.3",
"emoji-picker-element-data": "^1.4.0",
"file-saver": "^2.0.5",
"mime": "^3.0.0",
"pinia": "^2.1.6",
"pouchdb": "^8.0.1",
"pouchdb-browser": "^8.0.1",
"pouchdb-find": "^8.0.1",
"pouchdb-upsert": "^2.2.0",
"qth-locator": "^2.1.0",
"sass": "^1.66.1",
"socket.io": "^4.7.2",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-chartjs": "^5.2.0",
"vuemoji-picker": "^0.2.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@vitejs/plugin-vue": "^4.4.0",
"electron": "^27.0.0",
"electron-builder": "^24.6.3",
"eslint": "^8.50.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard-with-typescript": "^39.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-n": "^16.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.17.0",
"typescript": "^5.2.2",
"vite": "^4.3.2",
"vite-plugin-electron": "^0.14.0",
"vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4",
"vue-tsc": "^1.4.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Jeppe Ledet-Pedersen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,13 +0,0 @@
********************************
HTML Canvas/WebSockets Waterfall
********************************
This is a small experiment to create a waterfall plot with HTML Canvas and WebSockets to stream live FFT data from an SDR:
.. image:: img/waterfall.png
``spectrum.js`` contains the main JavaScript source code for the plot, while ``colormap.js`` contains colormaps generated using ``make_colormap.py``.
``index.html``, ``style.css``, ``script.js`` contain an example page that receives FFT data on a WebSocket and plots it on the waterfall plot.
``server.py`` contains a example `Bottle <https://bottlepy.org/docs/dev/>`_ and `gevent-websocket <https://pypi.org/project/gevent-websocket/>`_ server that broadcasts FFT data to connected clients. The FFT data is generated using `GNU radio <https://www.gnuradio.org/>`_ using a USRP but it should be fairly easy to change it to a different SDR.

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="author" content="Jeppe Ledet-Pedersen" />
<title>Spectrum Plot</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<canvas id="waterfall"></canvas>
<script src="colormap.js"></script>
<script src="spectrum.js"></script>
<script src="script.js"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More