diff --git a/datahandler.js b/datahandler.js
index 4095237..5d9880a 100644
--- a/datahandler.js
+++ b/datahandler.js
@@ -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) {
diff --git a/index.js b/index.js
index df8e2d5..5707fe2 100644
--- a/index.js
+++ b/index.js
@@ -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,78 +101,80 @@ function authenticateWithXdrd(client, salt, password) {
}
// xdrd connection
-client.connect(serverConfig.xdrd.xdrdPort, serverConfig.xdrd.xdrdIp, () => {
- logInfo('Connection to xdrd established successfully.');
-
- const authFlags = {
- authMsg: false,
- firstClient: false,
- receivedPassword: false
- };
-
- const authDataHandler = (data) => {
- const receivedData = data.toString();
- const lines = receivedData.split('\n');
-
- for (const line of lines) {
-
- if (!authFlags.receivedPassword) {
- authFlags.receivedSalt = line.trim();
- authenticateWithXdrd(client, authFlags.receivedSalt, serverConfig.xdrd.xdrdPassword);
- authFlags.receivedPassword = true;
- } 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.');
- }
-
- if (authFlags.authMsg && authFlags.firstClient) {
- client.write('T87500\n');
- client.write('A0\n');
- client.write('G11\n');
- client.off('data', authDataHandler);
- return;
+if (serverConfig.xdrd.xdrdPassword.length > 1) {
+ client.connect(serverConfig.xdrd.xdrdPort, serverConfig.xdrd.xdrdIp, () => {
+ logInfo('Connection to xdrd established successfully.');
+
+ const authFlags = {
+ authMsg: false,
+ firstClient: false,
+ receivedPassword: false
+ };
+
+ const authDataHandler = (data) => {
+ const receivedData = data.toString();
+ const lines = receivedData.split('\n');
+
+ for (const line of lines) {
+
+ if (!authFlags.receivedPassword) {
+ authFlags.receivedSalt = line.trim();
+ authenticateWithXdrd(client, authFlags.receivedSalt, serverConfig.xdrd.xdrdPassword);
+ authFlags.receivedPassword = true;
+ } 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.');
+ }
+
+ if (authFlags.authMsg && authFlags.firstClient) {
+ client.write('T87500\n');
+ client.write('A0\n');
+ client.write('G11\n');
+ client.off('data', authDataHandler);
+ return;
+ }
}
}
- }
- };
-
- client.on('data', (data) => {
- var receivedData = incompleteDataBuffer + data.toString();
- const isIncomplete = (receivedData.slice(-1) != '\n');
-
- if (isIncomplete) {
- const position = receivedData.lastIndexOf('\n');
- if (position < 0) {
- incompleteDataBuffer = receivedData;
- receivedData = '';
- } else {
- incompleteDataBuffer = receivedData.slice(position + 1);
- receivedData = receivedData.slice(0, position + 1);
- }
- } else {
- incompleteDataBuffer = '';
- }
-
- if (receivedData.length) {
- wss.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN) {
- dataHandler.handleData(client, receivedData);
+ };
+
+ client.on('data', (data) => {
+ var receivedData = incompleteDataBuffer + data.toString();
+ const isIncomplete = (receivedData.slice(-1) != '\n');
+
+ if (isIncomplete) {
+ const position = receivedData.lastIndexOf('\n');
+ if (position < 0) {
+ incompleteDataBuffer = receivedData;
+ receivedData = '';
+ } else {
+ incompleteDataBuffer = receivedData.slice(position + 1);
+ receivedData = receivedData.slice(0, position + 1);
}
- });
- }
+ } else {
+ incompleteDataBuffer = '';
+ }
+
+ if (receivedData.length) {
+ wss.clients.forEach((client) => {
+ if (client.readyState === WebSocket.OPEN) {
+ dataHandler.handleData(client, receivedData);
+ }
+ });
+ }
+ });
+
+ client.on('data', authDataHandler);
});
-
- client.on('data', authDataHandler);
-});
+}
client.on('close', () => {
logWarn('Disconnected from xdrd.');
diff --git a/stream/index.js b/stream/index.js
index 6032a4c..bd796e2 100644
--- a/stream/index.js
+++ b/stream/index.js
@@ -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);
diff --git a/tx_search.js b/tx_search.js
new file mode 100644
index 0000000..42143cf
--- /dev/null
+++ b/tx_search.js
@@ -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
+};
diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css
index af2405e..396ede7 100644
--- a/web/css/breadcrumbs.css
+++ b/web/css/breadcrumbs.css
@@ -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;
diff --git a/web/css/helpers.css b/web/css/helpers.css
index 2afe077..19b82ae 100644
--- a/web/css/helpers.css
+++ b/web/css/helpers.css
@@ -58,6 +58,14 @@
background-color: transparent;
}
+.opacity-full {
+ opacity: 1;
+}
+
+.opacity-half {
+ opacity: 0.5;
+}
+
.flex-container {
display: flex;
}
diff --git a/web/index.ejs b/web/index.ejs
index 49dcbed..0f61624 100644
--- a/web/index.ejs
+++ b/web/index.ejs
@@ -9,10 +9,10 @@
-
+
-
+
@@ -27,18 +27,6 @@