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

device types, bugfixes

This commit is contained in:
NoobishSVK
2024-03-16 18:49:18 +01:00
parent aeb2361f77
commit 44e2927801
14 changed files with 288 additions and 118 deletions

View File

@@ -6,6 +6,7 @@ const path = require('path');
const os = require('os'); const os = require('os');
const platform = os.platform(); const platform = os.platform();
const cpuArchitecture = os.arch(); const cpuArchitecture = os.arch();
const { configName, serverConfig, configUpdate, configSave } = require('./server_config');
let unicode_type; let unicode_type;
let shared_Library; let shared_Library;
@@ -207,8 +208,10 @@ var dataToSend = {
freq: 87.500.toFixed(3), freq: 87.500.toFixed(3),
previousFreq: 87.500.toFixed(3), previousFreq: 87.500.toFixed(3),
signal: 0, signal: 0,
highestSignal: -Infinity,
st: false, st: false,
st_forced: false, st_forced: false,
rds: false,
ps: '', ps: '',
tp: 0, tp: 0,
ta: 0, ta: 0,
@@ -234,6 +237,14 @@ var dataToSend = {
users: 0, users: 0,
}; };
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 legacyRdsPiBuffer = null;
const initialData = { ...dataToSend }; const initialData = { ...dataToSend };
const resetToDefault = dataToSend => Object.assign(dataToSend, initialData); const resetToDefault = dataToSend => Object.assign(dataToSend, initialData);
@@ -243,8 +254,10 @@ function handleData(ws, receivedData) {
// Retrieve the last update time for this client // Retrieve the last update time for this client
let lastUpdateTime = clientUpdateIntervals.get(ws) || 0; let lastUpdateTime = clientUpdateIntervals.get(ws) || 0;
const currentTime = Date.now(); const currentTime = Date.now();
let modifiedData, parsedValue; let modifiedData, parsedValue;
const receivedLines = receivedData.split('\n'); const receivedLines = receivedData.split('\n');
for (const receivedLine of receivedLines) { for (const receivedLine of receivedLines) {
switch (true) { switch (true) {
case receivedLine.startsWith('P'): case receivedLine.startsWith('P'):
@@ -272,86 +285,29 @@ function handleData(ws, receivedData) {
initialData.ant = receivedLine.substring(1); initialData.ant = receivedLine.substring(1);
break; break;
case receivedLine.startsWith('G'): case receivedLine.startsWith('G'):
switch (receivedLine) { const mapping = filterMappings[receivedLine];
case 'G11': if (mapping) {
initialData.eq = 1; initialData.eq = mapping.eq;
dataToSend.eq = 1; initialData.ims = mapping.ims;
initialData.ims = 1; dataToSend.eq = mapping.eq;
dataToSend.ims = 1; dataToSend.ims = mapping.ims;
break;
case 'G01':
initialData.eq = 0;
dataToSend.eq = 0;
initialData.ims = 1;
dataToSend.ims = 1;
break;
case 'G10':
initialData.eq = 1;
dataToSend.eq = 1;
initialData.ims = 0;
dataToSend.ims = 0;
break;
case 'G00':
initialData.eq = 0;
initialData.ims = 0;
dataToSend.eq = 0;
dataToSend.ims = 0;
break;
}
case receivedLine.startsWith('Sm'):
modifiedData = receivedLine.substring(2);
parsedValue = parseFloat(modifiedData);
dataToSend.st = false;
dataToSend.st_forced = false;
initialData.st = false;
initialData.st_forced = false;
if (!isNaN(parsedValue)) {
dataToSend.signal = parsedValue.toFixed(2);
initialData.signal = parsedValue.toFixed(2);
} }
break; break;
case receivedData.startsWith('Sm'):
processSignal(receivedData, false, false);
break;
case receivedData.startsWith('Ss'): case receivedData.startsWith('Ss'):
modifiedData = receivedData.substring(2); processSignal(receivedData, true, false);
parsedValue = parseFloat(modifiedData);
dataToSend.st = true;
dataToSend.st_forced = false;
initialData.st = true;
initialData.st_forced = false;
if (!isNaN(parsedValue)) {
dataToSend.signal = parsedValue.toFixed(2);
initialData.signal = parsedValue.toFixed(2);
}
break; break;
case receivedData.startsWith('SS'): case receivedData.startsWith('SS'):
modifiedData = receivedData.substring(2); processSignal(receivedData, true, true);
parsedValue = parseFloat(modifiedData);
dataToSend.st = true;
dataToSend.st_forced = true;
initialData.st = true;
initialData.st_forced = true;
if (!isNaN(parsedValue)) {
dataToSend.signal = parsedValue.toFixed(2);
initialData.signal = parsedValue.toFixed(2);
}
break; break;
case receivedData.startsWith('SM'): case receivedData.startsWith('SM'):
modifiedData = receivedData.substring(2); processSignal(receivedData, false, true);
parsedValue = parseFloat(modifiedData); break;
dataToSend.st = false;
dataToSend.st_forced = true;
initialData.st = false;
initialData.st_forced = true;
if (!isNaN(parsedValue)) {
dataToSend.signal = parsedValue.toFixed(2);
initialData.signal = parsedValue.toFixed(2);
}
break;
case receivedLine.startsWith('R'): case receivedLine.startsWith('R'):
modifiedData = receivedLine.slice(1); modifiedData = receivedLine.slice(1);
dataToSend.rds = true;
if (modifiedData.length == 14) { if (modifiedData.length == 14) {
// Handle legacy RDS message // Handle legacy RDS message
@@ -411,6 +367,42 @@ function showOnlineUsers(currentUsers) {
initialData.users = currentUsers; initialData.users = currentUsers;
} }
function convertSignal(dBFS, fullScaleVoltage = 1, inputImpedance = 300) {
// Convert dBFS to voltage
let voltage = Math.pow(10, dBFS / 20) * fullScaleVoltage;
// Convert voltage to microvolts
let uV = voltage * 1e6;
// Convert microvolts to dBuV
let dBf = 20 * Math.log10(uV / Math.sqrt(2) / Math.sqrt(inputImpedance));
return dBf.toFixed(2);
}
function processSignal(receivedData, st, stForced) {
const modifiedData = receivedData.substring(2);
const parsedValue = parseFloat(modifiedData);
dataToSend.st = st;
dataToSend.st_forced = stForced;
initialData.st = st;
initialData.st_forced = stForced;
if (!isNaN(parsedValue)) {
/*if (serverConfig.device && serverConfig.device === 'sdr') {
dataToSend.signal = convertSignal(parsedValue);
initialData.signal = convertSignal(parsedValue);
} else {*/
dataToSend.signal = parsedValue.toFixed(2);
initialData.signal = parsedValue.toFixed(2);
//}
if(dataToSend.signal > dataToSend.highestSignal) {
dataToSend.highestSignal = dataToSend.signal;
}
}
}
module.exports = { module.exports = {
handleData, showOnlineUsers, dataToSend, initialData, resetToDefault handleData, showOnlineUsers, dataToSend, initialData, resetToDefault
}; };

View File

@@ -2,7 +2,8 @@
const fs = require('fs'); const fs = require('fs');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const { logDebug, logError, logInfo, logWarn } = require('./console'); const { logDebug, logError, logInfo, logWarn } = require('./console');
const { serverConfig, configUpdate, configSave } = require('./server_config') const { serverConfig, configUpdate, configSave } = require('./server_config');
var pjson = require('./package.json');
let timeoutID = null; let timeoutID = null;
@@ -59,6 +60,12 @@ function sendKeepalive() {
} }
function sendUpdate() { function sendUpdate() {
let bwLimit = '';
if (serverConfig.webserver.tuningLimit === true) {
bwLimit = serverConfig.webserver.tuningLowerLimit + ' - ' + serverConfig.webserver.tuningUpperLimit + ' Mhz';
}
const request = { const request = {
status: (serverConfig.lockToAdmin ? 2 : 1), status: (serverConfig.lockToAdmin ? 2 : 1),
coords: [serverConfig.identification.lat, serverConfig.identification.lon], coords: [serverConfig.identification.lat, serverConfig.identification.lon],
@@ -66,7 +73,10 @@ function sendUpdate() {
desc: serverConfig.identification.tunerDesc, desc: serverConfig.identification.tunerDesc,
audioChannels: serverConfig.audio.audioChannels, audioChannels: serverConfig.audio.audioChannels,
audioQuality: serverConfig.audio.audioBitrate, audioQuality: serverConfig.audio.audioBitrate,
contact: serverConfig.identification.contact || '' contact: serverConfig.identification.contact || '',
device: serverConfig.deviceName || '',
bwLimit: bwLimit,
version: pjson.version
}; };
if (serverConfig.identification.token) if (serverConfig.identification.token)

View File

@@ -108,6 +108,8 @@ function connectToSerial() {
serialport.on('open', () => { serialport.on('open', () => {
logInfo('Using COM device: ' + serverConfig.xdrd.comPort); logInfo('Using COM device: ' + serverConfig.xdrd.comPort);
serialport.write('x\n');
serialport.write('M0\n'); serialport.write('M0\n');
serialport.write('Y100\n'); serialport.write('Y100\n');
serialport.write('D0\n'); serialport.write('D0\n');
@@ -128,8 +130,6 @@ function connectToSerial() {
serialport.write('T87500\n'); serialport.write('T87500\n');
} }
serialport.write('x\n');
serialport.on('data', (data) => { serialport.on('data', (data) => {
resolveDataBuffer(data); resolveDataBuffer(data);
}); });
@@ -422,6 +422,7 @@ app.get('/', (req, res) => {
tuningLowerLimit: serverConfig.webserver.tuningLowerLimit, tuningLowerLimit: serverConfig.webserver.tuningLowerLimit,
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit, tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
chatEnabled: serverConfig.webserver.chatEnabled, chatEnabled: serverConfig.webserver.chatEnabled,
device: serverConfig.device
}) })
} }
}); });
@@ -637,7 +638,7 @@ wss.on('connection', (ws, request) => {
} }
} }
if((serverConfig.publicTuner === true) || (request.session && request.session.isTuneAuthenticated === true && serverConfig.xdrd.wirelessConnection)) { if((serverConfig.publicTuner === true) || (request.session && request.session.isTuneAuthenticated === true && serverConfig.xdrd.wirelessConnection)) {
if(serverConfig.lockToAdmin === true) { if(serverConfig.lockToAdmin === true) {
if(request.session && request.session.isAdminAuthenticated === true) { if(request.session && request.session.isAdminAuthenticated === true) {

View File

@@ -1,6 +1,6 @@
{ {
"name": "fm-dx-webserver", "name": "fm-dx-webserver",
"version": "1.1.3", "version": "1.1.4",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@@ -10,6 +10,10 @@ const fetchInterval = 3000;
function fetchTx(freq, piCode, rdsPs) { function fetchTx(freq, piCode, rdsPs) {
const now = Date.now(); const now = Date.now();
freq = parseFloat(freq); freq = parseFloat(freq);
if(isNaN(freq)) {
return;
}
// Check if it's been at least 3 seconds since the last fetch and if the QTH is correct // 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 || freq < 87) { if (now - lastFetchTime < fetchInterval || serverConfig.identification.lat.length < 2 || freq < 87) {
return Promise.resolve(); return Promise.resolve();

View File

@@ -36,6 +36,24 @@ h4 {
font-size: 20px; font-size: 20px;
} }
.tooltip {
display: inline-block;
cursor: pointer;
}
.tooltiptext {
position: absolute;
background-color: var(--color-3);
color: var(--color-text);
text-align: center;
font-size: 14px;
border-radius: 30px;
padding: 5px 25px;
z-index: 1000;
opacity:var(--color-main);
transition: opacity 0.3s ease-in-out;
}
p#tuner-desc { p#tuner-desc {
margin: 0; margin: 0;
} }
@@ -78,6 +96,10 @@ label {
font-size: 20px; font-size: 20px;
} }
.highest-signal-container {
margin-bottom: -20px !important;
}
.form-group { .form-group {
float: left; float: left;
margin-bottom: 10px; margin-bottom: 10px;
@@ -215,6 +237,17 @@ label {
#tuner-wireless { #tuner-wireless {
display: none; display: none;
} }
.overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: auto; /* Ensure that the overlay captures clicks */
opacity: 0; /* Make the overlay invisible */
}
@media (max-width: 768px) { @media (max-width: 768px) {
canvas, #flags-container { canvas, #flags-container {
display: none; display: none;
@@ -234,6 +267,10 @@ label {
.form-group { .form-group {
float: none; float: none;
} }
.highest-signal-container {
margin-top: -20px !important;
margin-bottom: 15px !important;
}
#data-pi { #data-pi {
font-size: 24px; font-size: 24px;
margin-top: 20px; margin-top: 20px;

View File

@@ -202,6 +202,10 @@
display: none; display: none;
} }
.user-select-none {
user-select: none;
}
@media only screen and (max-width: 960px) { @media only screen and (max-width: 960px) {
.text-medium-big { .text-medium-big {
font-size: 32px; font-size: 32px;

View File

@@ -6,6 +6,7 @@
<link href="css/flags.min.css" type="text/css" rel="stylesheet"> <link href="css/flags.min.css" type="text/css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="icon" type="image/png" href="favicon2.png" /> <link rel="icon" type="image/png" href="favicon2.png" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -73,12 +74,12 @@
<div class="panel-10 no-bg h-100 m-0 m-right-20 hide-phone" style="width: 100px;margin-right: 20px !important;"> <div class="panel-10 no-bg h-100 m-0 m-right-20 hide-phone" style="width: 100px;margin-right: 20px !important;">
<button class="playbutton" aria-label="Play / Stop Button"><i class="fa-solid fa-play fa-lg"></i></button> <button class="playbutton" aria-label="Play / Stop Button"><i class="fa-solid fa-play fa-lg"></i></button>
</div> </div>
<div class="panel-100 m-0 hover-brighten flex-center" id="ps-container" style="height: 90px;"> <div class="panel-100 m-0 hover-brighten flex-center" id="ps-container" style="height: 90px;" data-tooltip="Clicking on the RDS PS will copy the RDS info into the clipboard.">
<span class="text-big" id="data-ps"></span> <span class="text-big" id="data-ps"></span>
</div> </div>
</div> </div>
<div id="flags-container-desktop" class="panel-33"> <div id="flags-container-desktop" class="panel-33 user-select-none">
<h2 class="show-phone"> <h2 class="show-phone">
<div class="data-pty text-color-default"></div> <div class="data-pty text-color-default"></div>
</h2> </h2>
@@ -88,14 +89,17 @@
<div style="display:inline-block"> <div style="display:inline-block">
<span style="margin-left: 20px;display: block;margin-top: 2px;" class="data-flag"></span> <span style="margin-left: 20px;display: block;margin-top: 2px;" class="data-flag"></span>
</div> </div>
<span id="stereo-container" class="pointer"><span style="margin-left: 20px;" class="data-st">ST</span></span> <span id="stereo-container" class="pointer" style="position: relative;">
<span style="margin-left: 20px;" class="data-st">ST</span>
<span class="overlay" data-tooltip="Stereo / Mono toggle. <br><strong>Click to toggle."></span>
</span>
<span style="margin-left: 15px;" class="data-ms">MS</span> <span style="margin-left: 15px;" class="data-ms">MS</span>
</h3> </h3>
</div> </div>
</div> </div>
<div class="flex-container"> <div class="flex-container">
<div class="panel-33 hover-brighten" id="pi-code-container"> <div class="panel-33 hover-brighten" id="pi-code-container" data-tooltip="Clicking on the PI code will show the current station on a map.">
<h2>PI CODE</h2> <h2>PI CODE</h2>
<span id="data-pi" class="text-big text-uppercase"></span> <span id="data-pi" class="text-big text-uppercase"></span>
</div> </div>
@@ -107,11 +111,16 @@
<div class="panel-33"> <div class="panel-33">
<h2>SIGNAL</h2> <h2>SIGNAL</h2>
<span class="text-big"> <div class="text-small text-gray highest-signal-container">
<i class="fa-solid fa-arrow-up"></i>
<span id="data-signal-highest"></span>
<span class="signal-units"></span>
</div>
<div class="text-big">
<span id="data-signal"></span><!-- <span id="data-signal"></span><!--
--><span id="data-signal-decimal" class="text-medium-big" style="opacity:0.7;"></span> --><span id="data-signal-decimal" class="text-medium-big" style="opacity:0.7;"></span>
<span id="signal-units" class="text-medium">dBf</span> <span class="signal-units text-medium">dBf</span>
</span> </div>
</div> </div>
</div> </div>
@@ -135,10 +144,12 @@
<% } %> <% } %>
<div class="panel-100 no-bg h-100 m-0 button-eq"> <div class="panel-100 no-bg h-100 m-0 button-eq">
<button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="EQ / RF+ Filter"><span class="text-bold">EQ</span><br><span class="text-smaller">(RF+)</span></button> <% if (device == 'tef') { %><button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="EQ Filter" data-tooltip="<strong>The cEQ filter can reduce bandwidth below 56 KHz.</strong><br><br>Useful for weak stations next to strong ones,<br>although it may pick up more interference."><span class="text-bold">cEQ</span></button><% } %>
<% if (device == 'xdr') { %><button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="RF+ Filter" data-tooltip="<strong>The RF+ filter increases gain by 5dB</strong>"><span class="text-bold">RF+</span></button><% } %>
</div> </div>
<div class="panel-100 no-bg h-100 m-0 button-ims"> <div class="panel-100 no-bg h-100 m-0 button-ims">
<button id="data-ims" style="border-radius: 0px 30px 30px 0px;" aria-label="iMS / IF+ Filter"><span class="text-bold">iMS</span><br><span class="text-smaller">(IF+)</span></button> <% if (device == 'tef') { %><button id="data-ims" style="border-radius: 0px 30px 30px 0px;" aria-label="iMS + Filter" data-tooltip="<strong>The iMS filter reduces multipath audio artifacts.</strong><br><br>It's recommended to leave it on most of the time."><span class="text-bold">iMS</span></button><% } %>
<% if (device == 'xdr') { %><button id="data-ims" style="border-radius: 0px 30px 30px 0px;" aria-label="IF+ Filter" data-tooltip="<strong>The IF+ filter increases gain by 6dB</strong>"><span class="text-bold">IF+</span></button><% } %>
</div> </div>
</div> </div>
</div> </div>
@@ -162,7 +173,7 @@
<hr class="hide-desktop"> <hr class="hide-desktop">
</div> </div>
<div class="panel-33 hover-brighten"> <div class="panel-33 hover-brighten" data-tooltip="This panel contains the current TX info when RDS is loaded.<br><strong>Clicking on this panel copies the info into the clipboard.</strong>">
<div id="data-station-container"> <div id="data-station-container">
<h2 style="margin-top: 0;" class="mb-0"> <h2 style="margin-top: 0;" class="mb-0">
<span id="data-station-name"></span> <span id="data-station-name"></span>
@@ -277,7 +288,7 @@
<a href="./setup">Setup</a> • <a class="logout-link" href="#">Logout</a> <a href="./setup">Setup</a> • <a class="logout-link" href="#">Logout</a>
</p> </p>
<% } else if (isTuneAuthenticated) { %> <% } else if (isTuneAuthenticated) { %>
<p>You are logged in and can control the receiver. <a class="logout-link" href="#">Logout</a></p> <p>You are logged in and can control the receiver.<br><a class="logout-link" href="#">Logout</a></p>
<% } else { %> <% } else { %>
<form action="./login" method="post" id="login-form" class="top-25"> <form action="./login" method="post" id="login-form" class="top-25">
<label for="password">Password:</label> <label for="password">Password:</label>
@@ -349,5 +360,9 @@
</div> </div>
<script src="js/webserver.js"></script> <script src="js/webserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenshot-capture/2.0.0/screenshot-capture.min.js"></script>
</body> </body>
</html> </html>

View File

@@ -41,6 +41,10 @@ function submitData() {
return $(this).text() === $('#audio-quality').val(); return $(this).text() === $('#audio-quality').val();
}).data('value') || "192k"); }).data('value') || "192k");
const device = ($('.options .option').filter(function() {
return $(this).text() === $('#device-type').val();
}).data('value') || "tef");
const tunerName = $('#webserver-name').val() || 'FM Tuner'; const tunerName = $('#webserver-name').val() || 'FM Tuner';
const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description'; const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description';
const broadcastTuner = $("#broadcast-tuner").is(":checked"); const broadcastTuner = $("#broadcast-tuner").is(":checked");
@@ -94,6 +98,7 @@ function submitData() {
tunePass, tunePass,
adminPass, adminPass,
}, },
device,
publicTuner, publicTuner,
lockToAdmin, lockToAdmin,
autoShutdown, autoShutdown,
@@ -179,6 +184,12 @@ function submitData() {
$("#com-devices").val(selectedDevice.text()); $("#com-devices").val(selectedDevice.text());
} }
$('#device-type').val(data.device);
var selectedDevice = $(".option[data-value='" + data.device + "']");
if (selectedDevice.length > 0) {
$("#device-type").val(selectedDevice.text());
}
$('#audio-devices').val(data.audio.audioDevice); $('#audio-devices').val(data.audio.audioDevice);
$('#audio-channels').val(data.audio.audioChannels); $('#audio-channels').val(data.audio.audioChannels);
var selectedChannels = $(".option[data-value='" + data.audio.audioChannels + "']"); var selectedChannels = $(".option[data-value='" + data.audio.audioChannels + "']");

View File

@@ -17,11 +17,17 @@ const europe_programmes = [
"Oldies Music", "Folk Music", "Documentary", "Alarm Test" "Oldies Music", "Folk Music", "Documentary", "Alarm Test"
]; ];
const usa_programmes = [
"No PTY", "News", "Information", "Sports", "Talk", "Rock", "Classic Rock",
"Adults Hits", "Soft Rock", "Top 40", "Country", "Oldies", "Soft Music",
"Nostalgia", "Jazz", "Classical", "Rhythm and Blues", "Soft Rhythm and Blues",
"Language", "Religious Music", "Religious Talk", "Personality", "Public", "College",
"Spanish Talk", "Spanish Music", "Hip Hop", "", "", "Weather", "Emergency Test", "Emergency"
];
$(document).ready(function () { $(document).ready(function () {
var canvas = $('#signal-canvas')[0]; var canvas = $('#signal-canvas')[0];
var signalToggle = $("#signal-units-toggle");
canvas.width = canvas.parentElement.clientWidth; canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight; canvas.height = canvas.parentElement.clientHeight;
@@ -135,10 +141,11 @@ $(document).ready(function () {
$(rtContainer).on("click", copyRt); $(rtContainer).on("click", copyRt);
$(txContainer).on("click", copyTx); $(txContainer).on("click", copyTx);
$(piCodeContainer).on("click", findOnMaps); $(piCodeContainer).on("click", findOnMaps);
$(stereoContainer).on("click", toggleForcedStereo); $(document).on("click", "#stereo-container", toggleForcedStereo);
$(freqContainer).on("click", function () { $(freqContainer).on("click", function () {
textInput.focus(); textInput.focus();
}); });
initTooltips();
}); });
function getServerTime() { function getServerTime() {
@@ -377,6 +384,27 @@ function checkKey(e) {
case 82: // RDS Reset (R key) case 82: // RDS Reset (R key)
tuneTo(Number(currentFreq)); tuneTo(Number(currentFreq));
break; break;
case 83: // Screenshot (S key)
screenshotCapture.capture().then(function (dataUrl) {
// Create an input element to hold the data URL temporarily
var aux = $('<input>').attr({
type: 'text',
value: dataUrl
});
// Append the input element to the body, select its contents, and copy them to the clipboard
$('body').append(aux);
aux.select();
document.execCommand('copy');
aux.remove();
// Alert the user that the screenshot has been copied to the clipboard
alert('Screenshot copied to clipboard!');
}).catch(function (error) {
console.error('Error capturing screenshot:', error);
});
break;
case 38: case 38:
socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1))); socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1)));
break; break;
@@ -465,7 +493,7 @@ async function copyPs() {
var ps = $('#data-ps').text(); var ps = $('#data-ps').text();
var signal = $('#data-signal').text(); var signal = $('#data-signal').text();
var signalDecimal = $('#data-signal-decimal').text(); var signalDecimal = $('#data-signal-decimal').text();
var signalUnit = $('#signal-units').text(); var signalUnit = $('.signal-units').text();
try { try {
await copyToClipboard(frequency + " - " + pi + " | " + ps + " [" + signal + signalDecimal + " " + signalUnit + "]"); await copyToClipboard(frequency + " - " + pi + " | " + ps + " [" + signal + signalDecimal + " " + signalUnit + "]");
@@ -543,13 +571,14 @@ function findOnMaps() {
function updateSignalUnits(parsedData, averageSignal) { function updateSignalUnits(parsedData, averageSignal) {
const signalUnit = localStorage.getItem('signalUnit'); const signalUnit = localStorage.getItem('signalUnit');
let currentSignal; let currentSignal;
let highestSignal = parsedData.highestSignal;
if(localStorage.getItem("smoothSignal") == 'true') { if(localStorage.getItem("smoothSignal") == 'true') {
currentSignal = averageSignal currentSignal = averageSignal
} else { } else {
currentSignal = parsedData.signal; currentSignal = parsedData.signal;
} }
let signalText = $('#signal-units'); let signalText = $('.signal-units');
let signalValue; let signalValue;
switch (signalUnit) { switch (signalUnit) {
@@ -572,6 +601,7 @@ function updateSignalUnits(parsedData, averageSignal) {
const formatted = (Math.round(signalValue * 10) / 10).toFixed(1); const formatted = (Math.round(signalValue * 10) / 10).toFixed(1);
const [integerPart, decimalPart] = formatted.split('.'); const [integerPart, decimalPart] = formatted.split('.');
$('#data-signal-highest').text(Number(highestSignal).toFixed(1));
$('#data-signal').text(integerPart); $('#data-signal').text(integerPart);
$('#data-signal-decimal').text('.' + decimalPart); $('#data-signal-decimal').text('.' + decimalPart);
} }
@@ -589,6 +619,7 @@ function updateDataElements(parsedData) {
const $dataTp = $('.data-tp'); const $dataTp = $('.data-tp');
const $dataTa = $('.data-ta'); const $dataTa = $('.data-ta');
const $dataMs = $('.data-ms'); const $dataMs = $('.data-ms');
const $flagDesktopCointainer = $('#flags-container-desktop');
const $dataPty = $('.data-pty'); const $dataPty = $('.data-pty');
$dataFrequency.text(parsedData.freq); $dataFrequency.text(parsedData.freq);
@@ -604,6 +635,12 @@ function updateDataElements(parsedData) {
$dataRt1.html(processString(parsedData.rt1, parsedData.rt1_errors)); $dataRt1.html(processString(parsedData.rt1, parsedData.rt1_errors));
$dataPty.html(europe_programmes[parsedData.pty]); $dataPty.html(europe_programmes[parsedData.pty]);
if(parsedData.rds === true) {
$flagDesktopCointainer.css('background-color', 'var(--color-2');
} else {
$flagDesktopCointainer.css('background-color', 'var(--color-1');
}
$('.data-flag').html(`<i title="${parsedData.country_name}" class="flag-sm flag-sm-${parsedData.country_iso}"></i>`); $('.data-flag').html(`<i title="${parsedData.country_name}" class="flag-sm flag-sm-${parsedData.country_iso}"></i>`);
$('.data-flag-big').html(`<i title="${parsedData.country_name}" class="flag-md flag-md-${parsedData.country_iso}"></i>`); $('.data-flag-big').html(`<i title="${parsedData.country_name}" class="flag-md flag-md-${parsedData.country_iso}"></i>`);
@@ -719,4 +756,28 @@ function toggleForcedStereo() {
var message = "B"; var message = "B";
message += parsedData.st_forced = (parsedData.st_forced == "1") ? "0" : "1"; message += parsedData.st_forced = (parsedData.st_forced == "1") ? "0" : "1";
socket.send(message); socket.send(message);
}
function initTooltips() {
$('[data-tooltip]').hover(function(e){
var tooltipText = $(this).data('tooltip');
var tooltip = $('<div class="tooltiptext"></div>').html(tooltipText);
$('body').append(tooltip);
var tooltipWidth = tooltip.outerWidth();
var tooltipHeight = tooltip.outerHeight();
var posX = e.pageX - tooltipWidth / 2;
var posY = e.pageY - tooltipHeight - 10;
tooltip.css({ top: posY, left: posX, opacity: 0.9 });
}, function() {
$('.tooltiptext').remove();
}).mousemove(function(e){
var tooltipWidth = $('.tooltiptext').outerWidth();
var tooltipHeight = $('.tooltiptext').outerHeight();
var posX = e.pageX - tooltipWidth / 2;
var posY = e.pageY - tooltipHeight - 10;
$('.tooltiptext').css({ top: posY, left: posX });
});
} }

View File

@@ -3,7 +3,7 @@ var day = currentDate.getDate();
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1 var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
var year = currentDate.getFullYear(); var year = currentDate.getFullYear();
var formattedDate = day + '/' + month + '/' + year; var formattedDate = day + '/' + month + '/' + year;
var currentVersion = 'v1.1.3 [' + formattedDate + ']'; var currentVersion = 'v1.1.4 [' + formattedDate + ']';
/** /**

View File

@@ -48,13 +48,13 @@ function updateWizardContent() {
$('.btn-prev').show(); $('.btn-prev').show();
} }
if($('.step:visible').index() == 2) { if($('.step:visible').index() == 3) {
setTimeout(function () { setTimeout(function () {
map.invalidateSize(); map.invalidateSize();
}, 200); }, 200);
} }
if($('.step:visible').index() == 3) { if($('.step:visible').index() == 4) {
$('.btn-next').text('Save'); $('.btn-next').text('Save');
} else { } else {
$('.btn-next').text('Next') $('.btn-next').text('Next')

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>FM-DX Webserver</title> <title>Setup - FM-DX Webserver</title>
<link href="css/entry.css" type="text/css" rel="stylesheet"> <link href="css/entry.css" type="text/css" rel="stylesheet">
<link href="css/flags.min.css" type="text/css" rel="stylesheet"> <link href="css/flags.min.css" type="text/css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet">
@@ -21,6 +21,7 @@
<div class="panel-100"> <div class="panel-100">
<ul class="nav"> <ul class="nav">
<li data-panel="dashboard" class="active">Dashboard</li> <li data-panel="dashboard" class="active">Dashboard</li>
<li data-panel="tuner">Tuner</li>
<li data-panel="connection">Connection</li> <li data-panel="connection">Connection</li>
<li data-panel="audio">Audio</li> <li data-panel="audio">Audio</li>
<li data-panel="webserver">Webserver</li> <li data-panel="webserver">Webserver</li>
@@ -340,6 +341,21 @@
</div> </div>
</div> </div>
<div class="panel-100 tab-content" id="tuner">
<h2>Tuner Specific Settings</h2>
<div class="form-group">
<label for="themes"><i class="fa-solid fa-radio"></i> Device:</label>
<div class="dropdown" id="device-selector" style="margin-right: 0;">
<input type="text" placeholder="TEF6686 / TEA685x" id="device-type" readonly>
<ul class="options">
<li class="option" data-value="tef">TEF668x / TEA685x</li>
<li class="option" data-value="xdr">XDR (F1HD / S10HDiP)</li>
<li class="option" data-value="sdr">SDR (RTL-SDR / AirSpy)</li>
</ul>
</div>
</div>
</div>
<div class="panel-100 tab-content" id="identification"> <div class="panel-100 tab-content" id="identification">
<h2>Identification & Map</h2> <h2>Identification & Map</h2>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>FM-DX Webserver</title> <title>Wizard - FM-DX Webserver</title>
<link href="css/entry.css" type="text/css" rel="stylesheet"> <link href="css/entry.css" type="text/css" rel="stylesheet">
<link href="css/flags.min.css" type="text/css" rel="stylesheet"> <link href="css/flags.min.css" type="text/css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet">
@@ -16,7 +16,6 @@
<% if (isAdminAuthenticated) { %> <% if (isAdminAuthenticated) { %>
<div class="panel-100 no-bg"> <div class="panel-100 no-bg">
<img class="top-10" src="../images/openradio_logo_neutral.png" height="64px"> <img class="top-10" src="../images/openradio_logo_neutral.png" height="64px">
<h1 class="top-10">FM-DX WebServer</h1>
<h2 class="text-monospace text-light text-gray">[SETUP WIZARD]</h2> <h2 class="text-monospace text-light text-gray">[SETUP WIZARD]</h2>
</div> </div>
<div class="panel-100 no-bg flex-container flex-center flex-phone"> <div class="panel-100 no-bg flex-container flex-center flex-phone">
@@ -24,14 +23,48 @@
<div class="btn-rounded-cube">2</div> <div class="btn-rounded-cube">2</div>
<div class="btn-rounded-cube">3</div> <div class="btn-rounded-cube">3</div>
<div class="btn-rounded-cube">4</div> <div class="btn-rounded-cube">4</div>
<div class="btn-rounded-cube">5</div>
</div> </div>
<div class="panel-100"> <div class="panel-100">
<!-- BASIC SETTINGS --> <!-- BASIC SETTINGS -->
<div class="panel-100 step" id="step1"> <div class="panel-100 step" id="step1">
<h2 class="settings-heading">BASIC SETTINGS</h2> <h2 class="settings-heading">Basic settings</h2>
<p class="m-0">Welcome to the setup wizard! Let's set up some basic things.</p> <p class="m-0">Welcome to the setup wizard! Let's set up some basic things.</p>
<h3>Webserver connection:</h3>
<p class="m-0 text-gray">Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.<br>Don't enter your public IP here.</p>
<div class="flex-center top-25">
<div class="form-group">
<label for="webserver-ip">Webserver IP:</label>
<input class="input-text w-150" type="text" name="webserver-ip" id="webserver-ip" placeholder="0.0.0.0">
</div>
<div class="form-group">
<label for="webserver-port">Webserver port:</label>
<input class="input-text w-100" type="text" name="webserver-port" id="webserver-port" placeholder="8080">
</div>
</div>
</div>
<!-- BASIC SETTINGS END -->
<!-- TUNER SETTINGS -->
<div id="step2" class="step" style="display: none">
<h2>Tuner settings</h2>
<h3>Tuner type:</h3>
<p class="text-gray">Settings a proper device type ensures that the correct interface and settings will load.</p>
<div class="form-group">
<label for="themes"><i class="fa-solid fa-radio"></i> Device:</label>
<div class="dropdown" id="device-selector" style="margin-right: 0;">
<input type="text" placeholder="TEF6686 / TEA685x" id="device-type" readonly>
<ul class="options">
<li class="option" data-value="tef">TEF668x / TEA685x</li>
<li class="option" data-value="xdr">XDR (F1HD / S10HDiP)</li>
<li class="option" data-value="sdr">SDR (RTL-SDR / AirSpy)</li>
</ul>
</div>
</div>
<h3>Tuner connection:</h3> <h3>Tuner connection:</h3>
<div style="width: 300px;" class="auto top-10"> <div style="width: 300px;" class="auto top-10">
<label class="toggleSwitch nolabel" onclick=""> <label class="toggleSwitch nolabel" onclick="">
@@ -74,24 +107,10 @@
</div> </div>
</div> </div>
</div> </div>
<br class="top-25">
<h3>Webserver connection:</h3>
<p class="m-0 text-gray">Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.<br>Don't enter your public IP here.</p>
<div class="flex-center top-25">
<div class="form-group">
<label for="webserver-ip">Webserver IP:</label>
<input class="input-text w-150" type="text" name="webserver-ip" id="webserver-ip" placeholder="0.0.0.0">
</div>
<div class="form-group">
<label for="webserver-port">Webserver port:</label>
<input class="input-text w-100" type="text" name="webserver-port" id="webserver-port" placeholder="8080">
</div>
</div>
</div> </div>
<!-- BASIC SETTINGS END --> <!-- TUNER SETTINGS END -->
<!-- AUDIO SETTINGS --> <!-- AUDIO SETTINGS -->
<div id="step2" class="step" style="display: none;"> <div id="step3" class="step" style="display: none;">
<div class="panel-100" style="min-height: 120px;margin-bottom: 0;"> <div class="panel-100" style="min-height: 120px;margin-bottom: 0;">
<h2 class="settings-heading">AUDIO SETTINGS</h2> <h2 class="settings-heading">AUDIO SETTINGS</h2>
<p class="m-0 text-gray">In this section, we will set up the audio.<br> <p class="m-0 text-gray">In this section, we will set up the audio.<br>
@@ -143,7 +162,7 @@
</div> </div>
<!-- AUDIO SETTINGS END --> <!-- AUDIO SETTINGS END -->
<!-- IDENTIFICATION START --> <!-- IDENTIFICATION START -->
<div id="step3" class="step" style="display: none;"> <div id="step4" class="step" style="display: none;">
<div class="panel-100" style="padding-bottom: 20px;"> <div class="panel-100" style="padding-bottom: 20px;">
<h2 class="settings-heading">IDENTIFICATION INFO</h2> <h2 class="settings-heading">IDENTIFICATION INFO</h2>
<p class="text-gray">In this part, we will set up your indentification info, such as the server name, description and location.</p> <p class="text-gray">In this part, we will set up your indentification info, such as the server name, description and location.</p>
@@ -188,7 +207,7 @@
</div> </div>
<!-- IDENTIFICATION END --> <!-- IDENTIFICATION END -->
<!-- ADMIN SETTINGS START --> <!-- ADMIN SETTINGS START -->
<div id="step4" class="step" style="display: none;"> <div id="step5" class="step" style="display: none;">
<h2 class="settings-heading">Admin panel settings</h2> <h2 class="settings-heading">Admin panel settings</h2>
<p>We are at the last and final step of the settings.</p> <p>We are at the last and final step of the settings.</p>