You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 22:13:53 +01:00
Merge pull request #154 from kkonradpl/main
RDS bugfixes & improvements
This commit is contained in:
@@ -264,7 +264,27 @@ const resetToDefault = dataToSend => Object.assign(dataToSend, initialData);
|
|||||||
const ServerStartTime = process.hrtime();
|
const ServerStartTime = process.hrtime();
|
||||||
var serialportUpdateTime = process.hrtime();
|
var serialportUpdateTime = process.hrtime();
|
||||||
let checkSerialport = false;
|
let checkSerialport = false;
|
||||||
|
let rdsTimeoutTimer = null;
|
||||||
|
|
||||||
|
function rdsReceived() {
|
||||||
|
if (rdsTimeoutTimer) {
|
||||||
|
clearTimeout(rdsTimeoutTimer);
|
||||||
|
rdsTimeoutTimer = null;
|
||||||
|
}
|
||||||
|
if (serverConfig.webserver.rdsTimeout && serverConfig.webserver.rdsTimeout != 0) {
|
||||||
|
rdsTimeoutTimer = setInterval(rdsReset, serverConfig.webserver.rdsTimeout * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rdsReset() {
|
||||||
|
resetToDefault(dataToSend);
|
||||||
|
dataToSend.af.length = 0;
|
||||||
|
rdsparser.clear(rds);
|
||||||
|
if (rdsTimeoutTimer) {
|
||||||
|
clearTimeout(rdsTimeoutTimer);
|
||||||
|
rdsTimeoutTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleData(wss, receivedData, rdsWss) {
|
function handleData(wss, receivedData, rdsWss) {
|
||||||
// Retrieve the last update time for this client
|
// Retrieve the last update time for this client
|
||||||
@@ -280,6 +300,7 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
dataToSend.bw = receivedLine.substring(1);
|
dataToSend.bw = receivedLine.substring(1);
|
||||||
break;
|
break;
|
||||||
case receivedLine.startsWith('P'): // PI Code
|
case receivedLine.startsWith('P'): // PI Code
|
||||||
|
rdsReceived();
|
||||||
modifiedData = receivedLine.slice(1);
|
modifiedData = receivedLine.slice(1);
|
||||||
legacyRdsPiBuffer = modifiedData;
|
legacyRdsPiBuffer = modifiedData;
|
||||||
if (dataToSend.pi.length >= modifiedData.length || dataToSend.pi == '?') {
|
if (dataToSend.pi.length >= modifiedData.length || dataToSend.pi == '?') {
|
||||||
@@ -289,16 +310,11 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
case receivedLine.startsWith('T'): // Frequency
|
case receivedLine.startsWith('T'): // Frequency
|
||||||
modifiedData = receivedLine.substring(1).split(",")[0];
|
modifiedData = receivedLine.substring(1).split(",")[0];
|
||||||
|
|
||||||
if((modifiedData / 1000).toFixed(3) == dataToSend.freq) {
|
rdsReset();
|
||||||
resetToDefault(dataToSend);
|
if((modifiedData / 1000).toFixed(3) == dataToSend.freq) {
|
||||||
rdsparser.clear(rds);
|
|
||||||
dataToSend.af = [];
|
|
||||||
return; // Prevent tune spamming using scrollwheel
|
return; // Prevent tune spamming using scrollwheel
|
||||||
}
|
}
|
||||||
|
|
||||||
resetToDefault(dataToSend);
|
|
||||||
dataToSend.af.length = 0;
|
|
||||||
rdsparser.clear(rds);
|
|
||||||
parsedValue = parseFloat(modifiedData);
|
parsedValue = parseFloat(modifiedData);
|
||||||
|
|
||||||
if (!isNaN(parsedValue)) {
|
if (!isNaN(parsedValue)) {
|
||||||
@@ -315,6 +331,7 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
case receivedLine.startsWith('Z'): // Antenna
|
case receivedLine.startsWith('Z'): // Antenna
|
||||||
dataToSend.ant = receivedLine.substring(1);
|
dataToSend.ant = receivedLine.substring(1);
|
||||||
initialData.ant = receivedLine.substring(1);
|
initialData.ant = receivedLine.substring(1);
|
||||||
|
rdsReset();
|
||||||
break;
|
break;
|
||||||
case receivedLine.startsWith('G'): // EQ / iMS (RF+/IF+)
|
case receivedLine.startsWith('G'): // EQ / iMS (RF+/IF+)
|
||||||
const mapping = filterMappings[receivedLine];
|
const mapping = filterMappings[receivedLine];
|
||||||
@@ -342,6 +359,7 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
processSignal(receivedLine, false, true);
|
processSignal(receivedLine, false, true);
|
||||||
break;
|
break;
|
||||||
case receivedLine.startsWith('R'): // RDS HEX
|
case receivedLine.startsWith('R'): // RDS HEX
|
||||||
|
rdsReceived();
|
||||||
modifiedData = receivedLine.slice(1);
|
modifiedData = receivedLine.slice(1);
|
||||||
dataToSend.rds = true;
|
dataToSend.rds = true;
|
||||||
|
|
||||||
@@ -357,7 +375,7 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
// error correction, but this is a good substitute.
|
// error correction, but this is a good substitute.
|
||||||
errorsNew = (legacyRdsPiBuffer.length - 4) << 6;
|
errorsNew = (legacyRdsPiBuffer.length - 4) << 6;
|
||||||
} else {
|
} else {
|
||||||
pi = '----';
|
pi = '0000';
|
||||||
errorsNew = (0x03 << 6);
|
errorsNew = (0x03 << 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,28 +389,14 @@ function handleData(wss, receivedData, rdsWss) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rdsWss.clients.forEach((client) => {
|
rdsWss.clients.forEach((client) => {
|
||||||
let dataString = modifiedData.toString();
|
const errors = parseInt(modifiedData.slice(-2), 16);
|
||||||
let lastTwoChars = dataString.slice(-2);
|
let data = (((errors & 0xC0) == 0) ? modifiedData.slice(0, 4) : '----');
|
||||||
let lastByteValue = parseInt(lastTwoChars, 16);
|
data += (((errors & 0x30) == 0) ? modifiedData.slice(4, 8) : '----');
|
||||||
|
data += (((errors & 0x0C) == 0) ? modifiedData.slice(8, 12) : '----');
|
||||||
let truncatedString = dataString.slice(0, -2);
|
data += (((errors & 0x03) == 0) ? modifiedData.slice(12, 16) : '----');
|
||||||
|
|
||||||
if ((lastByteValue & 0x03) !== 0) {
|
|
||||||
truncatedString = truncatedString.slice(0, 4) + '----' + truncatedString.slice(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((lastByteValue & 0x30) !== 0) {
|
|
||||||
truncatedString = truncatedString.slice(0, 8) + '----' + truncatedString.slice(12);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((lastByteValue & 0x0C) !== 0) {
|
|
||||||
truncatedString = truncatedString.slice(0, 12) + '----';
|
|
||||||
}
|
|
||||||
|
|
||||||
let newDataString = "G:\r\n" + truncatedString + "\r\n\r\n";
|
|
||||||
|
|
||||||
let finalBuffer = Buffer.from(newDataString, 'utf-8');
|
|
||||||
|
|
||||||
|
const newDataString = "G:\r\n" + data + "\r\n\r\n";
|
||||||
|
const finalBuffer = Buffer.from(newDataString, 'utf-8');
|
||||||
client.send(finalBuffer);
|
client.send(finalBuffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ router.get('/rdsspy', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/rds', (req, res) => {
|
router.get('/rds', (req, res) => {
|
||||||
res.send('Please c onnect using a WebSocket compatible app to obtain RDS stream.');
|
res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/rdsspy', (req, res) => {
|
router.get('/rdsspy', (req, res) => {
|
||||||
@@ -344,6 +344,7 @@ router.get('/static_data', (req, res) => {
|
|||||||
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
|
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
|
||||||
bgImage: serverConfig.webserver.bgImage || '',
|
bgImage: serverConfig.webserver.bgImage || '',
|
||||||
rdsMode: serverConfig.webserver.rdsMode || false,
|
rdsMode: serverConfig.webserver.rdsMode || false,
|
||||||
|
rdsTimeout: serverConfig.webserver.rdsTimeout || 0,
|
||||||
tunerName: serverConfig.identification.tunerName || '',
|
tunerName: serverConfig.identification.tunerName || '',
|
||||||
tunerDesc: serverConfig.identification.tunerDesc || '',
|
tunerDesc: serverConfig.identification.tunerDesc || '',
|
||||||
ant: serverConfig.antennas || {}
|
ant: serverConfig.antennas || {}
|
||||||
|
|||||||
119
server/index.js
119
server/index.js
@@ -214,7 +214,10 @@ if (serverConfig.xdrd.wirelessConnection === false) {
|
|||||||
return serialport;
|
return serialport;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// xdrd connection
|
// xdrd connection
|
||||||
|
let authFlags = {};
|
||||||
|
|
||||||
function connectToXdrd() {
|
function connectToXdrd() {
|
||||||
const { xdrd } = serverConfig;
|
const { xdrd } = serverConfig;
|
||||||
|
|
||||||
@@ -222,76 +225,72 @@ function connectToXdrd() {
|
|||||||
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
|
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
|
||||||
logInfo('Connection to xdrd established successfully.');
|
logInfo('Connection to xdrd established successfully.');
|
||||||
|
|
||||||
let authFlags = {
|
authFlags = {
|
||||||
authMsg: false,
|
authMsg: false,
|
||||||
firstClient: false,
|
firstClient: false,
|
||||||
receivedSalt: '',
|
receivedSalt: '',
|
||||||
receivedPassword: false,
|
receivedPassword: false,
|
||||||
messageCount: 0,
|
messageCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const authDataHandler = (data) => {
|
|
||||||
authFlags.messageCount++
|
|
||||||
const receivedData = data.toString();
|
|
||||||
const lines = receivedData.split('\n');
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (authFlags.receivedPassword === false) {
|
|
||||||
authFlags.receivedSalt = line.trim();
|
|
||||||
authFlags.receivedPassword = true;
|
|
||||||
helpers.authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword);
|
|
||||||
} else {
|
|
||||||
if (line.startsWith('a')) {
|
|
||||||
authFlags.authMsg = true;
|
|
||||||
logWarn('Authentication with xdrd failed. Is your password set correctly?');
|
|
||||||
} else if (line.startsWith('o1,')) {
|
|
||||||
authFlags.firstClient = true;
|
|
||||||
} else if (line.startsWith('T') && line.length <= 7) {
|
|
||||||
const freq = line.slice(1) / 1000;
|
|
||||||
dataHandler.dataToSend.freq = freq.toFixed(3);
|
|
||||||
} else if (line.startsWith('OK')) {
|
|
||||||
authFlags.authMsg = true;
|
|
||||||
logInfo('Authentication with xdrd successful.');
|
|
||||||
} else if (line.startsWith('G')) {
|
|
||||||
const value = line.substring(1);
|
|
||||||
dataHandler.initialData.eq = value.charAt(0);
|
|
||||||
dataHandler.dataToSend.eq = value.charAt(0);
|
|
||||||
dataHandler.initialData.ims = value.charAt(1);
|
|
||||||
dataHandler.dataToSend.ims = value.charAt(1);
|
|
||||||
} else if (line.startsWith('Z')) {
|
|
||||||
let modifiedLine = line.slice(1);
|
|
||||||
dataHandler.initialData.ant = modifiedLine;
|
|
||||||
dataHandler.dataToSend.ant = modifiedLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authFlags.authMsg === true && authFlags.firstClient === true) {
|
|
||||||
client.write('x\n');
|
|
||||||
client.write(serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? 'T' + Math.round(serverConfig.defaultFreq * 1000) + '\n' : 'T87500\n');
|
|
||||||
dataHandler.initialData.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
|
|
||||||
dataHandler.dataToSend.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
|
|
||||||
client.write('A0\n');
|
|
||||||
client.write(serverConfig.audio.startupVolume ? 'Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n' : 'Y100\n');
|
|
||||||
serverConfig.webserver.rdsMode ? client.write('D1\n') : client.write('D0\n');
|
|
||||||
client.off('data', authDataHandler);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
client.on('data', (data) => {
|
|
||||||
helpers.resolveDataBuffer(data, wss, rdsWss);
|
|
||||||
if (authFlags.authMsg == true && authFlags.messageCount > 1) {
|
|
||||||
// If the limit is reached, remove the 'data' event listener
|
|
||||||
client.off('data', authDataHandler);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
authDataHandler(data);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.on('data', (data) => {
|
||||||
|
const { xdrd } = serverConfig;
|
||||||
|
|
||||||
|
helpers.resolveDataBuffer(data, wss, rdsWss);
|
||||||
|
if (authFlags.authMsg == true && authFlags.messageCount > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
authFlags.messageCount++;
|
||||||
|
const receivedData = data.toString();
|
||||||
|
const lines = receivedData.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (authFlags.receivedPassword === false) {
|
||||||
|
authFlags.receivedSalt = line.trim();
|
||||||
|
authFlags.receivedPassword = true;
|
||||||
|
helpers.authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword);
|
||||||
|
} else {
|
||||||
|
if (line.startsWith('a')) {
|
||||||
|
authFlags.authMsg = true;
|
||||||
|
logWarn('Authentication with xdrd failed. Is your password set correctly?');
|
||||||
|
} else if (line.startsWith('o1,')) {
|
||||||
|
authFlags.firstClient = true;
|
||||||
|
} else if (line.startsWith('T') && line.length <= 7) {
|
||||||
|
const freq = line.slice(1) / 1000;
|
||||||
|
dataHandler.dataToSend.freq = freq.toFixed(3);
|
||||||
|
} else if (line.startsWith('OK')) {
|
||||||
|
authFlags.authMsg = true;
|
||||||
|
logInfo('Authentication with xdrd successful.');
|
||||||
|
} else if (line.startsWith('G')) {
|
||||||
|
const value = line.substring(1);
|
||||||
|
dataHandler.initialData.eq = value.charAt(0);
|
||||||
|
dataHandler.dataToSend.eq = value.charAt(0);
|
||||||
|
dataHandler.initialData.ims = value.charAt(1);
|
||||||
|
dataHandler.dataToSend.ims = value.charAt(1);
|
||||||
|
} else if (line.startsWith('Z')) {
|
||||||
|
let modifiedLine = line.slice(1);
|
||||||
|
dataHandler.initialData.ant = modifiedLine;
|
||||||
|
dataHandler.dataToSend.ant = modifiedLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authFlags.authMsg === true && authFlags.firstClient === true) {
|
||||||
|
client.write('x\n');
|
||||||
|
client.write(serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? 'T' + Math.round(serverConfig.defaultFreq * 1000) + '\n' : 'T87500\n');
|
||||||
|
dataHandler.initialData.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
|
||||||
|
dataHandler.dataToSend.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
|
||||||
|
client.write('A0\n');
|
||||||
|
client.write(serverConfig.audio.startupVolume ? 'Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n' : 'Y100\n');
|
||||||
|
serverConfig.webserver.rdsMode ? client.write('D1\n') : client.write('D0\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
client.on('close', () => {
|
client.on('close', () => {
|
||||||
if(serverConfig.autoShutdown === false) {
|
if(serverConfig.autoShutdown === false) {
|
||||||
logWarn('Disconnected from xdrd. Attempting to reconnect.');
|
logWarn('Disconnected from xdrd. Attempting to reconnect.');
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ let serverConfig = {
|
|||||||
defaultTheme: "theme1",
|
defaultTheme: "theme1",
|
||||||
bgImage: "",
|
bgImage: "",
|
||||||
rdsMode: false,
|
rdsMode: false,
|
||||||
|
rdsTimeout: 0,
|
||||||
txIdAlgorithm: 0
|
txIdAlgorithm: 0
|
||||||
},
|
},
|
||||||
xdrd: {
|
xdrd: {
|
||||||
@@ -181,4 +182,4 @@ if (configExists()) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
configName, serverConfig, configUpdate, configSave, configExists, configPath
|
configName, serverConfig, configUpdate, configSave, configExists, configPath
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function getInitialSettings() {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
|
|
||||||
['qthLatitude', 'qthLongitude', 'defaultTheme', 'bgImage', 'rdsMode'].forEach(key => {
|
['qthLatitude', 'qthLongitude', 'defaultTheme', 'bgImage', 'rdsMode', 'rdsTimeout'].forEach(key => {
|
||||||
if (data[key] !== undefined) {
|
if (data[key] !== undefined) {
|
||||||
localStorage.setItem(key, data[key]);
|
localStorage.setItem(key, data[key]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,7 +323,12 @@
|
|||||||
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
||||||
<h3>RDS Mode</h3>
|
<h3>RDS Mode</h3>
|
||||||
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
|
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %><br>
|
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %>
|
||||||
|
<h3>RDS Timeout</h3>
|
||||||
|
<p>If no data is received, RDS will be automatically cleared after a timeout.<br>
|
||||||
|
<span class="text-gray">Enter timeout in seconds or 0 to disable.</span><br></p>
|
||||||
|
|
||||||
|
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '0', label: 'RDS Timeout', id: 'webserver-rdsTimeout'}) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
||||||
<h3>Transmitter Search Algorithm</h3>
|
<h3>Transmitter Search Algorithm</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user