1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

TX ID, UI fixes

This commit is contained in:
NoobishSVK
2024-02-05 21:16:32 +01:00
parent e482c29e00
commit f3a99e6adc
9 changed files with 312 additions and 118 deletions

View File

@@ -7,6 +7,7 @@ const os = require('os');
const win32 = (os.platform() == "win32");
const unicode_type = (win32 ? 'int16_t' : 'int32_t');
const lib = koffi.load(path.join(__dirname, "librdsparser." + (win32 ? "dll" : "so")));
const { fetchTx } = require('./tx_search.js');
koffi.proto('void callback_pi(void *rds, void *user_data)');
koffi.proto('void callback_pty(void *rds, void *user_data)');
@@ -206,6 +207,15 @@ var dataToSend = {
ims: 0,
eq: 0,
ant: 0,
txInfo: {
station: '',
pol: '',
erp: '',
city: '',
itu: '',
distance: '',
azimuth: ''
},
country_name: '',
country_iso: 'UN',
users: '',
@@ -215,6 +225,7 @@ var legacyRdsPiBuffer = null;
const initialData = { ...dataToSend };
const resetToDefault = dataToSend => Object.assign(dataToSend, initialData);
function handleData(ws, receivedData) {
// Retrieve the last update time for this client
let lastUpdateTime = clientUpdateIntervals.get(ws) || 0;
@@ -329,6 +340,20 @@ function handleData(ws, receivedData) {
}
}
// Get the received TX info
const currentTx = fetchTx(dataToSend.freq, dataToSend.pi, dataToSend.ps);
if(currentTx.station !== undefined) {
dataToSend.txInfo = {
station: currentTx.station,
pol: currentTx.pol,
erp: currentTx.erp,
city: currentTx.city,
itu: currentTx.itu,
distance: currentTx.distance,
azimuth: currentTx.azimuth
}
}
// Send the updated data to the client
const dataToSendJSON = JSON.stringify(dataToSend);
if (currentTime - lastUpdateTime >= updateInterval) {

View File

@@ -10,7 +10,6 @@ const http = require('http');
const https = require('https');
const app = express();
const httpServer = http.createServer(app);
const ejs = require('ejs');
// Websocket handling
const WebSocket = require('ws');
@@ -43,13 +42,13 @@ let serverConfig = {
xdrd: {
xdrdIp: "127.0.0.1",
xdrdPort: "7373",
xdrdPassword: "password"
xdrdPassword: ""
},
identification: {
tunerName: "",
tunerDesc: "",
lat: "",
lon: ""
lat: "0",
lon: "0"
},
password: {
tunePass: "",
@@ -102,6 +101,7 @@ function authenticateWithXdrd(client, salt, password) {
}
// xdrd connection
if (serverConfig.xdrd.xdrdPassword.length > 1) {
client.connect(serverConfig.xdrd.xdrdPort, serverConfig.xdrd.xdrdIp, () => {
logInfo('Connection to xdrd established successfully.');
@@ -174,6 +174,7 @@ client.connect(serverConfig.xdrd.xdrdPort, serverConfig.xdrd.xdrdIp, () => {
client.on('data', authDataHandler);
});
}
client.on('close', () => {
logWarn('Disconnected from xdrd.');

View File

@@ -23,15 +23,15 @@ function enableAudioStream() {
// Specify the command and its arguments
const command = 'ffmpeg';
const flags = `-fflags +nobuffer+flush_packets -flags low_delay -rtbufsize 6192 -probesize 32`;
const codec = `-acodec pcm_s16le -ar 32000 -ac ${serverConfig.audio.audioChannels}`;
const codec = `-acodec pcm_s16le -ar 48000 -ac ${serverConfig.audio.audioChannels}`;
const output = `-f s16le -fflags +nobuffer+flush_packets -packetsize 384 -flush_packets 1 -bufsize 960`;
// Combine all the settings for the ffmpeg command
if (process.platform === 'win32') {
// Windows
ffmpegCommand = `${flags} -f dshow -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 32000 -channels ${serverConfig.audio.audioChannels}`;
ffmpegCommand = `${flags} -f dshow -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
} else {
// Linux
ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 32000 -channels ${serverConfig.audio.audioChannels}`;
ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
}
consoleCmd.logInfo("Using audio device: " + serverConfig.audio.audioDevice);

127
tx_search.js Normal file
View File

@@ -0,0 +1,127 @@
const fetch = require('node-fetch');
var fs = require('fs');
let cachedData = {};
let serverConfig = {
identification: {
lat: 0,
lon: 0
},
};
if(fs.existsSync('config.json')) {
const configFileContents = fs.readFileSync('config.json', 'utf8');
serverConfig = JSON.parse(configFileContents);
}
let lastFetchTime = 0;
const fetchInterval = 3000;
// Fetch data from maps
function fetchTx(freq, piCode, rdsPs) {
const now = Date.now();
// Check if it's been at least 3 seconds since the last fetch and if the QTH is correct
if (now - lastFetchTime < fetchInterval || serverConfig.identification.lat.length < 2) {
return Promise.resolve();
}
lastFetchTime = now;
// Check if data for the given frequency is already cached
if (cachedData[freq]) {
return processData(cachedData[freq], piCode, rdsPs);
}
const url = "https://maps.fmdx.pl/controller.php?freq=" + freq;
return fetch(url)
.then(response => response.json())
.then(data => {
// Cache the fetched data for the specific frequency
cachedData[freq] = data;
return processData(data, piCode, rdsPs);
})
.catch(error => {
console.error("Error fetching data:", error);
});
}
function processData(data, piCode, rdsPs) {
let matchingStation = null;
let matchingCity = null;
let minDistance = Infinity;
let txAzimuth;
for (const cityId in data.locations) {
const city = data.locations[cityId];
if (city.stations) {
for (const station of city.stations) {
if (station.pi === piCode && station.ps.includes(rdsPs.replace(/ /g, '_'))) {
const distance = haversine(serverConfig.identification.lat, serverConfig.identification.lon, city.lat, city.lon);
if (distance.distanceKm < minDistance) {
minDistance = distance.distanceKm;
txAzimuth = distance.azimuth;
matchingStation = station;
matchingCity = city;
}
}
}
}
}
if (matchingStation) {
return {
station: matchingStation.station.replace("R.", "Radio "),
pol: matchingStation.pol.toUpperCase(),
erp: matchingStation.erp,
city: matchingCity.name,
itu: matchingCity.itu,
distance: minDistance.toFixed(0),
azimuth: txAzimuth.toFixed(0),
foundStation: true
};
} else {
return;
}
}
function haversine(lat1, lon1, lat2, lon2) {
const R = 6371; // Earth radius in kilometers
const dLat = deg2rad(lat2 - lat1);
const dLon = deg2rad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
// Distance in kilometers
const distance = R * c;
// Azimuth calculation
const y = Math.sin(dLon) * Math.cos(deg2rad(lat2));
const x = Math.cos(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) -
Math.sin(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(dLon);
const azimuth = Math.atan2(y, x);
// Convert azimuth from radians to degrees
const azimuthDegrees = (azimuth * 180 / Math.PI + 360) % 360;
return {
distanceKm: distance,
azimuth: azimuthDegrees
};
}
function deg2rad(deg) {
return deg * (Math.PI / 180);
}
module.exports = {
fetchTx
};

View File

@@ -20,6 +20,12 @@ h3 {
font-size: 22px;
}
h4 {
margin: 0;
font-weight: 400;
font-size: 20px;
}
p#tuner-desc {
margin: 0;
}
@@ -54,6 +60,10 @@ label {
font-weight: 500;
}
#data-station-container {
display: none;
}
.form-group {
float: left;
margin-bottom: 10px;

View File

@@ -58,6 +58,14 @@
background-color: transparent;
}
.opacity-full {
opacity: 1;
}
.opacity-half {
opacity: 0.5;
}
.flex-container {
display: flex;
}

View File

@@ -9,10 +9,10 @@
<link rel="icon" type="image/png" href="favicon2.png" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="FM-DX WebServer">
<meta property="og:title" content="FM-DX WebServer [<%= tunerName %>]">
<meta property="og:type" content="website">
<meta property="og:image" content="favicon2.png">
<meta property="og:description" content="This server us running the FM-DX Webserver software by Noobish.">
<meta property="og:description" content="Server description: <%= tunerDesc %>.">
<!-- 3LAS Scripts for Audio streaming -->
<script src="js/3las/util/3las.helpers.js"></script>
@@ -27,18 +27,6 @@
<script src="js/3las/3las.js"></script>
<script src="js/3las/main.js"></script>
<script type="text/javascript">
var RtcConfig = {
iceServers: [
{
urls: "turns:turnserver.example.org",
},
{
urls: "stun.l.google.com:19302"
}
]
};
var AudioTagId = "audioTag";
window.addEventListener('load', Init, false);
document.ontouchmove = function(e){
@@ -74,14 +62,14 @@
<h2 class="show-phone">
<div class="data-pty" style="color:white;"></div>
</h2>
<h3 style="margin-top:0;margin-bottom:0;" class="flex-center">
<span class="data-tp color-4">TP</span>
<span style="margin-left: 15px;" class="data-ta color-4">TA</span>
<h3 style="margin-top:0;margin-bottom:0;" class="color-4 flex-center">
<span class="data-tp">TP</span>
<span style="margin-left: 15px;" class="data-ta">TA</span>
<div style="display:inline-block">
<span style="margin-left: 20px;display: block;margin-top: 2px;" class="data-flag"></span>
</div>
<span style="margin-left: 20px; color: #ff5776;" class="data-st"></span>
<span style="margin-left: 15px;" class="data-ms"><span style="color: #ff5776">M</span><span class="text-gray">S</span></span>
<span style="margin-left: 20px;" class="data-st">ST</span>
<span style="margin-left: 15px;" class="data-ms">MS</span>
</h3>
</div>
</div>
@@ -144,18 +132,22 @@
<div class="flex-container">
<div class="panel-75 hover-brighten" id="rt-container" style="height: 110px;">
<h2 style="margin: 0;">RADIOTEXT</h2>
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
<div id="data-rt0"></div>
<div id="data-rt1"></div>
<div id="data-container" style="display: none;"></div>
</div>
<div class="panel-33">
<h2>
<div style="color:white;"></div>
<div class="panel-33 hover-brighten">
<div id="data-station-container">
<h2 style="margin-top: 4px;">
<span id="data-station-name"></span>
</h2>
<h3 style="margin-top:0;" class="flex-center">
</h3>
<h4 class="m-0">
<span id="data-station-city"></span>, <span id="data-station-itu"></span>
</h4>
<span id="data-station-erp"></span> kW [<span id="data-station-pol"></span>] <span class="text-gray">•</span> <span id="data-station-distance"></span> km <span class="text-gray">•</span> <span id="data-station-azimuth"></span>°
</div>
</div>
</div>
@@ -176,14 +168,14 @@
<h2 class="show-phone">
<div class="data-pty" style="color:white;"></div>
</h2>
<h3 style="margin-top:0;margin-bottom:0;" class="flex-center">
<span class="data-tp color-4">TP</span>
<span style="margin-left: 15px;" class="data-ta color-4">TA</span>
<h3 style="margin-top:0;margin-bottom:0;" class="color-4 flex-center">
<span class="data-tp">TP</span>
<span style="margin-left: 15px;" class="data-ta">TA</span>
<div style="display:inline-block">
<span style="margin-left: 20px;display: block;margin-top: 2px;" class="data-flag"></span>
</div>
<span style="margin-left: 20px; color: #ff5776;" class="data-st"></span>
<span style="margin-left: 15px;" class="data-ms"><span style="color: #ff5776">M</span><span class="text-gray">S</span></span>
<span style="margin-left: 20px;" class="data-st"></span>
<span style="margin-left: 15px;" class="data-ms">MS</span>
</h3>
</div>
</div>
@@ -248,9 +240,9 @@
<div class="flex-container flex-left text-left bottom-20 hover-brighten p-10 br-5" onclick="window.open('https://buymeacoffee.com/noobish')">
<i class="fa-solid fa-hand-holding-medical"></i>&nbsp;<span><strong>Support</strong> the developer!</span>
</div>
<p class="text-small">FM-DX WebServer <span style="color: var(--color-3);">v1.0.0 [4/2/2024]</span> by <a href="https://noobish.eu" target="_blank">Noobish</a> & the OpenRadio community.</p>
<p class="text-small">FM-DX WebServer <span style="color: var(--color-3);">v1.0.1 [5/2/2024]</span> by <a href="https://noobish.eu" target="_blank">Noobish</a> & the OpenRadio community.</p>
<p class="text-small bottom-50">This app works thanks to these amazing projects: <br>
<span class="text-smaller">- librdsparser by <a href="https://fmdx.pl" target="_blank">Konrad Kosmatka</a></span><br>
<span class="text-smaller">- librdsparser & maps.fmdx.pl by <a href="https://fmdx.pl" target="_blank">Konrad Kosmatka</a></span><br>
<span class="text-smaller">- 3LAS by <a href="https://github.com/JoJoBond/3LAS" target="_blank">JoJoBond</a></span><br>
<span class="text-smaller">- flat-flags by <a href="https://github.com/luishdez/flat-flags/tree/master" target="_blank">luishdez</a></span><br></p>
<button class="button-close" id="closeModalButton">Close</button>

View File

@@ -99,6 +99,7 @@ $(document).ready(function() {
var rtContainer = $('#rt-container')[0];
var piCodeContainer = $('#pi-code-container')[0];
var freqContainer = $('#freq-container')[0];
var txContainer = $('#data-station-container')[0];
$("#data-eq").click(function () {
toggleButtonState("eq");
@@ -112,6 +113,7 @@ $(document).ready(function() {
$(freqDownButton).on("click", tuneDown);
$(psContainer).on("click", copyPs);
$(rtContainer).on("click", copyRt);
$(txContainer).on("click", copyTx);
$(piCodeContainer).on("click", findOnMaps);
$(freqContainer).on("click", function() {
textInput.focus();
@@ -353,6 +355,22 @@ async function copyPs() {
}
}
async function copyTx() {
const frequency = $('#data-frequency').text();
const pi = $('#data-pi').text();
const stationName = $('#data-station-name').text();
const stationCity = $('#data-station-city').text();
const stationItu = $('#data-station-itu').text();
const stationDistance = $('#data-station-distance').text();
const stationErp = $('#data-station-erp').text();
try {
await copyToClipboard(frequency + " - " + pi + " | " + stationName + " [" + stationCity + ", " + stationItu + "] - " + stationDistance + " km | " + stationErp + " kW");
} catch(error) {
console.error(error);
}
}
async function copyRt() {
var rt0 = $('#data-rt0').text();
var rt1 = $('#data-rt1').text();
@@ -434,15 +452,15 @@ function updateSignalUnits(parsedData) {
function updateDataElements(parsedData) {
$('#data-frequency').text(parsedData.freq);
$('#data-pi').html(parsedData.pi === '?' ? "<span class='text-gray'>?</span>" : parsedData.pi);
$('#data-ps').html(parsedData.ps === '?' ? "<span class='text-gray'>?</span>" : processString(parsedData.ps, parsedData.ps_errors));
$('.data-tp').html(parsedData.tp === false ? "<span class='text-gray'>TP</span>" : "TP");
$('.data-ta').html(parsedData.ta === 0 ? "<span class='text-gray'>TA</span>" : "TA");
$('#data-pi').html(parsedData.pi === '?' ? "<span class='opacity-half'>?</span>" : parsedData.pi);
$('#data-ps').html(parsedData.ps === '?' ? "<span class='opacity-half'>?</span>" : processString(parsedData.ps, parsedData.ps_errors));
$('.data-tp').html(parsedData.tp === false ? "<span class='opacity-half'>TP</span>" : "TP");
$('.data-ta').html(parsedData.ta === 0 ? "<span class='opacity-half'>TA</span>" : "TA");
$('.data-ms').html(parsedData.ms === 0
? "<span class='text-gray'>M</span><span class='text-red'>S</span>"
? "<span class='opacity-half'>M</span><span class='opacity-full'>S</span>"
: (parsedData.ms === -1
? "<span class='text-gray'>M</span><span class='text-gray'>S</span>"
: "<span class='text-red'>M</span><span class='text-gray'>S</span>"
? "<span class='opacity-half'>M</span><span class='opacity-half'>S</span>"
: "<span class='opacity-full'>M</span><span class='opacity-half'>S</span>"
)
);
$('.data-pty').html(europe_programmes[parsedData.pty]);
@@ -451,6 +469,19 @@ function updateDataElements(parsedData) {
$('#data-rt1').html(processString(parsedData.rt1, parsedData.rt1_errors));
$('.data-flag').html(`<i title="${parsedData.country_name}" class="flag-sm flag-sm-${parsedData.country_iso}"></i>`);
$('#data-ant input').val($('#data-ant li[data-value="' + parsedData.ant + '"]').text());
if(parsedData.txInfo.station.length > 1) {
$('#data-station-name').text(decodeURIComponent(parsedData.txInfo.station.replace(/\u009e/g, '\u017E')));
$('#data-station-erp').text(parsedData.txInfo.erp);
$('#data-station-city').text(parsedData.txInfo.city);
$('#data-station-itu').text(parsedData.txInfo.itu);
$('#data-station-pol').text(parsedData.txInfo.pol);
$('#data-station-distance').text(parsedData.txInfo.distance);
$('#data-station-azimuth').text(parsedData.txInfo.azimuth);
$('#data-station-container').css('display', 'block');
} else {
$('#data-station-container').removeAttr('style');
}
}
let isEventListenerAdded = false;

View File

@@ -34,7 +34,7 @@
</div>
<div class="form-group">
<label for="xdrd-password">xdrd server password:</label>
<input class="input-text w-150" type="text" name="xdrd-password" id="xdrd-password">
<input class="input-text w-150" type="password" name="xdrd-password" id="xdrd-password">
</div>
<br>
<h3>Webserver connection:</h3>
@@ -136,11 +136,11 @@
</div><br>
<div class="form-group">
<label for="tune-pass">Tune password:</label>
<input class="input-text w-150" type="text" name="tune-pass" id="tune-pass">
<input class="input-text w-150" type="password" name="tune-pass" id="tune-pass">
</div>
<div class="form-group" style="margin-bottom: 40px;">
<label for="admin-pass">Admin setup password:</label>
<input class="input-text w-150" type="text" name="admin-pass" id="admin-pass">
<input class="input-text w-150" type="password" name="admin-pass" id="admin-pass">
</div><br>
<button style="height:48px; width: 200px;margin-bottom:20px;" onclick="submitData();">Save settings</button>
<button style="height: 48px; width: 200px;background:var(--color-3)" class="logout-link">Logout</button>