diff --git a/datahandler.js b/datahandler.js index 1358902..7d5e543 100644 --- a/datahandler.js +++ b/datahandler.js @@ -6,6 +6,7 @@ const path = require('path'); const os = require('os'); const platform = os.platform(); const cpuArchitecture = os.arch(); +const { configName, serverConfig, configUpdate, configSave } = require('./server_config'); let unicode_type; let shared_Library; @@ -207,8 +208,10 @@ var dataToSend = { freq: 87.500.toFixed(3), previousFreq: 87.500.toFixed(3), signal: 0, + highestSignal: -Infinity, st: false, st_forced: false, + rds: false, ps: '', tp: 0, ta: 0, @@ -234,6 +237,14 @@ var dataToSend = { 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; const initialData = { ...dataToSend }; const resetToDefault = dataToSend => Object.assign(dataToSend, initialData); @@ -243,8 +254,10 @@ function handleData(ws, receivedData) { // Retrieve the last update time for this client let lastUpdateTime = clientUpdateIntervals.get(ws) || 0; const currentTime = Date.now(); + let modifiedData, parsedValue; const receivedLines = receivedData.split('\n'); + for (const receivedLine of receivedLines) { switch (true) { case receivedLine.startsWith('P'): @@ -272,86 +285,29 @@ function handleData(ws, receivedData) { initialData.ant = receivedLine.substring(1); break; case receivedLine.startsWith('G'): - switch (receivedLine) { - case 'G11': - initialData.eq = 1; - dataToSend.eq = 1; - initialData.ims = 1; - dataToSend.ims = 1; - 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); + const mapping = filterMappings[receivedLine]; + if (mapping) { + initialData.eq = mapping.eq; + initialData.ims = mapping.ims; + dataToSend.eq = mapping.eq; + dataToSend.ims = mapping.ims; } break; + case receivedData.startsWith('Sm'): + processSignal(receivedData, false, false); + break; case receivedData.startsWith('Ss'): - modifiedData = receivedData.substring(2); - 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); - } + processSignal(receivedData, true, false); break; case receivedData.startsWith('SS'): - modifiedData = receivedData.substring(2); - 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); - } + processSignal(receivedData, true, true); break; case receivedData.startsWith('SM'): - modifiedData = receivedData.substring(2); - parsedValue = parseFloat(modifiedData); - 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; + processSignal(receivedData, false, true); + break; case receivedLine.startsWith('R'): modifiedData = receivedLine.slice(1); + dataToSend.rds = true; if (modifiedData.length == 14) { // Handle legacy RDS message @@ -411,6 +367,42 @@ function showOnlineUsers(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 = { handleData, showOnlineUsers, dataToSend, initialData, resetToDefault }; diff --git a/fmdx_list.js b/fmdx_list.js index e73d467..e910cc8 100644 --- a/fmdx_list.js +++ b/fmdx_list.js @@ -2,7 +2,8 @@ const fs = require('fs'); const fetch = require('node-fetch'); 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; @@ -59,6 +60,12 @@ function sendKeepalive() { } function sendUpdate() { + + let bwLimit = ''; + if (serverConfig.webserver.tuningLimit === true) { + bwLimit = serverConfig.webserver.tuningLowerLimit + ' - ' + serverConfig.webserver.tuningUpperLimit + ' Mhz'; + } + const request = { status: (serverConfig.lockToAdmin ? 2 : 1), coords: [serverConfig.identification.lat, serverConfig.identification.lon], @@ -66,7 +73,10 @@ function sendUpdate() { desc: serverConfig.identification.tunerDesc, audioChannels: serverConfig.audio.audioChannels, audioQuality: serverConfig.audio.audioBitrate, - contact: serverConfig.identification.contact || '' + contact: serverConfig.identification.contact || '', + device: serverConfig.deviceName || '', + bwLimit: bwLimit, + version: pjson.version }; if (serverConfig.identification.token) diff --git a/index.js b/index.js index bc01cc9..b0e3bfc 100644 --- a/index.js +++ b/index.js @@ -108,6 +108,8 @@ function connectToSerial() { serialport.on('open', () => { logInfo('Using COM device: ' + serverConfig.xdrd.comPort); + + serialport.write('x\n'); serialport.write('M0\n'); serialport.write('Y100\n'); serialport.write('D0\n'); @@ -128,8 +130,6 @@ function connectToSerial() { serialport.write('T87500\n'); } - serialport.write('x\n'); - serialport.on('data', (data) => { resolveDataBuffer(data); }); @@ -422,6 +422,7 @@ app.get('/', (req, res) => { tuningLowerLimit: serverConfig.webserver.tuningLowerLimit, tuningUpperLimit: serverConfig.webserver.tuningUpperLimit, 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(request.session && request.session.isAdminAuthenticated === true) { diff --git a/package.json b/package.json index 41e24cb..56d7b7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.1.3", + "version": "1.1.4", "description": "", "main": "index.js", "scripts": { diff --git a/tx_search.js b/tx_search.js index b9800ea..60e08b7 100644 --- a/tx_search.js +++ b/tx_search.js @@ -10,6 +10,10 @@ const fetchInterval = 3000; function fetchTx(freq, piCode, rdsPs) { const now = Date.now(); 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 if (now - lastFetchTime < fetchInterval || serverConfig.identification.lat.length < 2 || freq < 87) { return Promise.resolve(); diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index a1ff0b6..64557df 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -36,6 +36,24 @@ h4 { 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 { margin: 0; } @@ -78,6 +96,10 @@ label { font-size: 20px; } +.highest-signal-container { + margin-bottom: -20px !important; +} + .form-group { float: left; margin-bottom: 10px; @@ -215,6 +237,17 @@ label { #tuner-wireless { 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) { canvas, #flags-container { display: none; @@ -234,6 +267,10 @@ label { .form-group { float: none; } + .highest-signal-container { + margin-top: -20px !important; + margin-bottom: 15px !important; + } #data-pi { font-size: 24px; margin-top: 20px; diff --git a/web/css/helpers.css b/web/css/helpers.css index d4bf2ff..4d7a153 100644 --- a/web/css/helpers.css +++ b/web/css/helpers.css @@ -202,6 +202,10 @@ display: none; } +.user-select-none { + user-select: none; +} + @media only screen and (max-width: 960px) { .text-medium-big { font-size: 32px; diff --git a/web/index.ejs b/web/index.ejs index 64da018..e600a30 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -6,6 +6,7 @@ + @@ -73,12 +74,12 @@
-
+
-
+

@@ -88,14 +89,17 @@
- ST + + ST + + MS
-
+

PI CODE

@@ -107,11 +111,16 @@

SIGNAL

- +
+ + + +
+
- dBf - + dBf +
@@ -135,10 +144,12 @@ <% } %>
- + <% if (device == 'tef') { %><% } %> + <% if (device == 'xdr') { %><% } %>
- + <% if (device == 'tef') { %><% } %> + <% if (device == 'xdr') { %><% } %>
@@ -162,7 +173,7 @@
-
+

@@ -277,7 +288,7 @@ SetupLogout

<% } else if (isTuneAuthenticated) { %> -

You are logged in and can control the receiver. Logout

+

You are logged in and can control the receiver.
Logout

<% } else { %>
@@ -349,5 +360,9 @@

+ + + + diff --git a/web/js/confighandler.js b/web/js/confighandler.js index 2e7de51..794aee8 100644 --- a/web/js/confighandler.js +++ b/web/js/confighandler.js @@ -41,6 +41,10 @@ function submitData() { return $(this).text() === $('#audio-quality').val(); }).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 tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description'; const broadcastTuner = $("#broadcast-tuner").is(":checked"); @@ -94,6 +98,7 @@ function submitData() { tunePass, adminPass, }, + device, publicTuner, lockToAdmin, autoShutdown, @@ -179,6 +184,12 @@ function submitData() { $("#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-channels').val(data.audio.audioChannels); var selectedChannels = $(".option[data-value='" + data.audio.audioChannels + "']"); diff --git a/web/js/main.js b/web/js/main.js index 9c84ac9..1f25225 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -17,11 +17,17 @@ const europe_programmes = [ "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 () { var canvas = $('#signal-canvas')[0]; - var signalToggle = $("#signal-units-toggle"); - canvas.width = canvas.parentElement.clientWidth; canvas.height = canvas.parentElement.clientHeight; @@ -135,10 +141,11 @@ $(document).ready(function () { $(rtContainer).on("click", copyRt); $(txContainer).on("click", copyTx); $(piCodeContainer).on("click", findOnMaps); - $(stereoContainer).on("click", toggleForcedStereo); + $(document).on("click", "#stereo-container", toggleForcedStereo); $(freqContainer).on("click", function () { textInput.focus(); }); + initTooltips(); }); function getServerTime() { @@ -377,6 +384,27 @@ function checkKey(e) { case 82: // RDS Reset (R key) tuneTo(Number(currentFreq)); break; + case 83: // Screenshot (S key) + screenshotCapture.capture().then(function (dataUrl) { + // Create an input element to hold the data URL temporarily + var aux = $('').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: socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1))); break; @@ -465,7 +493,7 @@ async function copyPs() { var ps = $('#data-ps').text(); var signal = $('#data-signal').text(); var signalDecimal = $('#data-signal-decimal').text(); - var signalUnit = $('#signal-units').text(); + var signalUnit = $('.signal-units').text(); try { await copyToClipboard(frequency + " - " + pi + " | " + ps + " [" + signal + signalDecimal + " " + signalUnit + "]"); @@ -543,13 +571,14 @@ function findOnMaps() { function updateSignalUnits(parsedData, averageSignal) { const signalUnit = localStorage.getItem('signalUnit'); let currentSignal; + let highestSignal = parsedData.highestSignal; if(localStorage.getItem("smoothSignal") == 'true') { currentSignal = averageSignal } else { currentSignal = parsedData.signal; } - let signalText = $('#signal-units'); + let signalText = $('.signal-units'); let signalValue; switch (signalUnit) { @@ -572,6 +601,7 @@ function updateSignalUnits(parsedData, averageSignal) { const formatted = (Math.round(signalValue * 10) / 10).toFixed(1); const [integerPart, decimalPart] = formatted.split('.'); + $('#data-signal-highest').text(Number(highestSignal).toFixed(1)); $('#data-signal').text(integerPart); $('#data-signal-decimal').text('.' + decimalPart); } @@ -589,6 +619,7 @@ function updateDataElements(parsedData) { const $dataTp = $('.data-tp'); const $dataTa = $('.data-ta'); const $dataMs = $('.data-ms'); + const $flagDesktopCointainer = $('#flags-container-desktop'); const $dataPty = $('.data-pty'); $dataFrequency.text(parsedData.freq); @@ -604,6 +635,12 @@ function updateDataElements(parsedData) { $dataRt1.html(processString(parsedData.rt1, parsedData.rt1_errors)); $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(``); $('.data-flag-big').html(``); @@ -719,4 +756,28 @@ function toggleForcedStereo() { var message = "B"; message += parsedData.st_forced = (parsedData.st_forced == "1") ? "0" : "1"; socket.send(message); +} + +function initTooltips() { + $('[data-tooltip]').hover(function(e){ + var tooltipText = $(this).data('tooltip'); + var tooltip = $('
').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 }); + }); } \ No newline at end of file diff --git a/web/js/settings.js b/web/js/settings.js index 0fce5be..92c74c1 100644 --- a/web/js/settings.js +++ b/web/js/settings.js @@ -3,7 +3,7 @@ var day = currentDate.getDate(); var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1 var year = currentDate.getFullYear(); var formattedDate = day + '/' + month + '/' + year; -var currentVersion = 'v1.1.3 [' + formattedDate + ']'; +var currentVersion = 'v1.1.4 [' + formattedDate + ']'; /** diff --git a/web/js/wizard.js b/web/js/wizard.js index 07da06f..1b7c986 100644 --- a/web/js/wizard.js +++ b/web/js/wizard.js @@ -48,13 +48,13 @@ function updateWizardContent() { $('.btn-prev').show(); } - if($('.step:visible').index() == 2) { + if($('.step:visible').index() == 3) { setTimeout(function () { map.invalidateSize(); }, 200); } - if($('.step:visible').index() == 3) { + if($('.step:visible').index() == 4) { $('.btn-next').text('Save'); } else { $('.btn-next').text('Next') diff --git a/web/setup.ejs b/web/setup.ejs index e36f1a0..8b782bc 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -1,7 +1,7 @@ - FM-DX Webserver + Setup - FM-DX Webserver @@ -21,6 +21,7 @@
+ +
+

Tuner Specific Settings

+
+ + +
+

Identification & Map

diff --git a/web/wizard.ejs b/web/wizard.ejs index d2bb89e..df8e672 100644 --- a/web/wizard.ejs +++ b/web/wizard.ejs @@ -1,7 +1,7 @@ - FM-DX Webserver + Wizard - FM-DX Webserver @@ -16,7 +16,6 @@ <% if (isAdminAuthenticated) { %>
-

FM-DX WebServer

[SETUP WIZARD]

@@ -24,14 +23,48 @@
2
3
4
+
5
-

BASIC SETTINGS

+

Basic settings

Welcome to the setup wizard! Let's set up some basic things.

+ +

Webserver connection:

+

Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.
Don't enter your public IP here.

+
+
+ + +
+
+ + +
+
+
+ + +
- -
-

Webserver connection:

-

Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.
Don't enter your public IP here.

-
-
- - -
-
- - -
-
- + -