1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 14:11:59 +01:00
Files
fm-dx-webserver/server/datahandler.js

309 lines
9.3 KiB
JavaScript

/* Libraries / Imports */
const RDSDecoder = require("./rds.js");
const { serverConfig } = require('./server_config');
const fetchTx = require('./tx_search.js');
const updateInterval = 75;
// Initialize the data object
var dataToSend = {
pi: '?',
freq: (87.500).toFixed(3),
sig: 0,
sigRaw: '',
sigTop: -Infinity,
bw: 0,
st: false,
stForced: false,
rds: false,
ps: '',
tp: 0,
ta: 0,
ms: -1,
pty: 0,
ecc: null,
af: [],
rt0: '',
rt1: '',
rt_flag: '',
ims: 0,
eq: 0,
agc: 0,
ant: 0,
txInfo: {
tx: '',
pol: '',
erp: '',
city: '',
itu: '',
dist: '',
azi: '',
id: '',
reg: false,
pi: '',
},
country_name: '',
country_iso: 'UN',
users: 0,
};
const rdsdec = new RDSDecoder(dataToSend);
const filterMappings = {
'G11': { eq: 1, ims: 1 },
'G01': { eq: 0, ims: 1 },
'G10': { eq: 1, ims: 0 },
'G00': { eq: 0, ims: 0 }
};
var legacyRdsPiBuffer = null;
var lastUpdateTime = Date.now();
const initialData = { ...dataToSend };
const resetToDefault = dataToSend => Object.assign(dataToSend, initialData);
var serialportUpdateTime = process.hrtime();
let checkSerialport = false;
let rdsTimeoutTimer = null;
function rdsReceived() {
if (rdsTimeoutTimer) {
clearTimeout(rdsTimeoutTimer);
rdsTimeoutTimer = null;
}
if (serverConfig.webserver.rdsTimeout && serverConfig.webserver.rdsTimeout != 0) rdsTimeoutTimer = setTimeout(rdsReset, serverConfig.webserver.rdsTimeout * 1000);
}
function rdsReset() {
resetToDefault(dataToSend);
dataToSend.af.length = 0;
rdsdec.clear();
if (rdsTimeoutTimer) {
clearTimeout(rdsTimeoutTimer);
rdsTimeoutTimer = null;
}
}
function handleData(wss, receivedData, rdsWss) {
// Retrieve the last update time for this client
const currentTime = Date.now();
let modifiedData, parsedValue;
const receivedLines = receivedData.split('\n');
for (const receivedLine of receivedLines) {
switch (true) {
case receivedLine.startsWith('F'): // Bandwidth
initialData.bw = receivedLine.substring(1);
dataToSend.bw = receivedLine.substring(1);
break;
case receivedLine.startsWith('P'): // PI Code
rdsReceived();
modifiedData = receivedLine.slice(1);
legacyRdsPiBuffer = modifiedData;
if (dataToSend.pi.length >= modifiedData.length || dataToSend.pi == '?') dataToSend.pi = modifiedData;
break;
case receivedLine.startsWith('T'): // Frequency
modifiedData = receivedLine.substring(1).split(",")[0];
rdsReset();
if((modifiedData / 1000).toFixed(3) == dataToSend.freq) return; // Prevent tune spamming using scrollwheel
parsedValue = parseFloat(modifiedData);
if (!isNaN(parsedValue)) {
initialData.freq = (parsedValue / 1000).toFixed(3);
dataToSend.freq = (parsedValue / 1000).toFixed(3);
dataToSend.pi = '?';
dataToSend.txInfo.reg = false;
rdsWss.clients.forEach((client) => {
client.send("G:\r\nRESET-------\r\n\r\n");
});
}
break;
case receivedLine.startsWith('Z'): // Antenna
dataToSend.ant = receivedLine.substring(1);
initialData.ant = receivedLine.substring(1);
rdsReset();
break;
case receivedLine.startsWith('A'): // AGC
dataToSend.agc = receivedLine.substring(1);
initialData.agc = receivedLine.substring(1);
break;
case receivedLine.startsWith('G'): // EQ / iMS (RF+/IF+)
const mapping = filterMappings[receivedLine];
if (mapping) {
initialData.eq = mapping.eq;
initialData.ims = mapping.ims;
dataToSend.eq = mapping.eq;
dataToSend.ims = mapping.ims;
}
break;
case receivedLine.startsWith('W'): // Bandwidth
initialData.bw = receivedLine.substring(1);
dataToSend.bw = receivedLine.substring(1);
break;
case receivedLine.startsWith('Sm'):
processSignal(receivedLine, false, false);
break;
case receivedLine.startsWith('Ss'):
processSignal(receivedLine, true, false);
break;
case receivedLine.startsWith('SS'):
processSignal(receivedLine, true, true);
break;
case receivedLine.startsWith('SM'):
processSignal(receivedLine, false, true);
break;
case receivedLine.startsWith('R'): // RDS HEX
rdsReceived();
modifiedData = receivedLine.slice(1);
dataToSend.rds = true;
if (modifiedData.length == 14) {
// Handle legacy RDS message
var errorsNew = 0;
var pi;
if(legacyRdsPiBuffer !== null && legacyRdsPiBuffer.length >= 4) {
pi = legacyRdsPiBuffer.slice(0, 4);
// PI message does not carry explicit information about
// error correction, but this is a good substitute.
errorsNew = (legacyRdsPiBuffer.length - 4) << 6;
} else {
pi = '0000';
errorsNew = (0x03 << 6);
}
let errorsOld = parseInt(modifiedData.slice(12), 16);
errorsNew |= (errorsOld & 0x03) << 4;
errorsNew |= (errorsOld & 0x0C);
errorsNew |= (errorsOld & 0x30) >> 4;
modifiedData = pi + modifiedData.slice(0, 12);
modifiedData += errorsNew.toString(16).padStart(2, '0');
}
const a = modifiedData.slice(0, 4);
const b = modifiedData.slice(4, 8);
const c = modifiedData.slice(8, 12);
const d = modifiedData.slice(12, 16);
const errors = parseInt(modifiedData.slice(-2), 16);
rdsWss.clients.forEach((client) => {
let data = ((((errors >> 6) & 3) < 3) ? a : '----');
data += ((((errors >> 4) & 3) < 3) ? b : '----');
data += ((((errors >> 2) & 3) < 3) ? c : '----');
data += (((errors & 3) < 3) ? d : '----');
client.send("G:\r\n" + data + "\r\n\r\n");
});
rdsdec.decodeGroup(parseInt(a, 16), parseInt(b, 16), parseInt(c, 16), parseInt(d, 16), errors);
legacyRdsPiBuffer = null;
break;
}
}
// Get the received TX info
fetchTx(parseFloat(dataToSend.freq).toFixed(1), dataToSend.pi, dataToSend.ps)
.then((currentTx) => {
if (currentTx && currentTx.station !== undefined && parseInt(currentTx.distance) < 4000) {
dataToSend.txInfo = {
tx: currentTx.station,
pol: currentTx.pol,
erp: currentTx.erp,
city: currentTx.city,
itu: currentTx.itu,
dist: currentTx.distance,
azi: currentTx.azimuth,
id: currentTx.id,
pi: currentTx.pi,
reg: currentTx.reg,
otherMatches: currentTx.others,
score: currentTx.score,
};
}
})
.catch((error) => {
console.log("Error fetching Tx info:", error);
});
// Send the updated data to the client
const dataToSendJSON = JSON.stringify(dataToSend);
if (currentTime - lastUpdateTime >= updateInterval) {
wss.clients.forEach((client) => {
client.send(dataToSendJSON);
});
lastUpdateTime = Date.now();
serialportUpdateTime = process.hrtime();
}
}
// Serialport retry code when port is open but communication is lost (additional code in index.js)
let state = {
isSerialportAlive: true,
isSerialportRetrying: false,
lastFrequencyAlive: '87.500'
};
setInterval(() => {
state.lastFrequencyAlive = initialData.freq;
const serialportElapsedTime = process.hrtime(serialportUpdateTime)[0];
// Activate serialport retry if handleData has not been executed for over 8 seconds
if (checkSerialport && (serialportElapsedTime > 8) && !state.isSerialportRetrying && serverConfig.xdrd.wirelessConnection === false) {
state.isSerialportAlive = false;
state.isSerialportRetrying = true;
}
}, 2000);
// Delay checking Serialport status on startup for 10 seconds
async function checkSerialPortStatus() {
const ServerStartTime = process.hrtime();
while (!checkSerialport) {
const ServerElapsedSeconds = process.hrtime(ServerStartTime)[0];
if (ServerElapsedSeconds > 10) checkSerialport = true;
await new Promise(resolve => setTimeout(resolve, 100));
}
}
checkSerialPortStatus();
function showOnlineUsers(currentUsers) {
dataToSend.users = currentUsers;
initialData.users = currentUsers;
}
let prevFreq = initialData.freq || '87.500';
function processSignal(receivedData, st, stForced) {
if (initialData.freq !== prevFreq) {
prevFreq = initialData.freq;
dataToSend.ps_errors = '';
}
const modifiedData = receivedData.substring(2);
const parsedValue = parseFloat(modifiedData);
dataToSend.st = st;
dataToSend.stForced = stForced;
initialData.st = st;
initialData.stForced = stForced;
if (!isNaN(parsedValue)) {
// Convert parsedValue to a number
var signal = parseFloat(parsedValue.toFixed(2));
dataToSend.sig = signal;
initialData.sig = signal;
dataToSend.sigRaw = receivedData;
initialData.sigRaw = receivedData;
// Convert highestSignal to a number for comparison
var highestSignal = parseFloat(dataToSend.sigTop);
if (signal > highestSignal) dataToSend.sigTop = signal.toString(); // Convert back to string for consistency
}
}
module.exports = {
handleData, showOnlineUsers, dataToSend, initialData, resetToDefault, state
};