diff --git a/datahandler.js b/datahandler.js index 2fd553c..2561cb1 100644 --- a/datahandler.js +++ b/datahandler.js @@ -379,5 +379,5 @@ function showOnlineUsers(currentUsers) { } module.exports = { - handleData, showOnlineUsers, dataToSend + handleData, showOnlineUsers, dataToSend, initialData }; diff --git a/fmdx_list.js b/fmdx_list.js index f726794..c3f8409 100644 --- a/fmdx_list.js +++ b/fmdx_list.js @@ -25,13 +25,13 @@ function send(request) { { if (!serverConfig.identification.token) { - logInfo("Succesfully registered in FM-DX Server Map."); + logInfo("Registered to FM-DX Server Map successfully."); serverConfig.identification.token = data.token; configSave(); } else { - logInfo("Succesfully updated FM-DX Server Map."); + logInfo("FM-DX Server Map update successful."); } } else diff --git a/index.js b/index.js index 3e1d0b5..492f3c4 100644 --- a/index.js +++ b/index.js @@ -27,8 +27,7 @@ const fmdxList = require('./fmdx_list'); const consoleCmd = require('./console'); const audioStream = require('./stream/index.js'); const { parseAudioDevice } = require('./stream/parser.js'); -const configPath = path.join(__dirname, 'config.json'); -const { serverConfig, configUpdate, configSave } = require('./server_config') +const { configName, serverConfig, configUpdate, configSave } = require('./server_config'); const { logDebug, logError, logInfo, logWarn } = consoleCmd; @@ -109,6 +108,33 @@ function connectToXdrd() { } else if (line.startsWith('OK')) { authFlags.authMsg = true; logInfo('Authentication with xdrd successful.'); + } else if (line.startsWith('G')) { + switch (line) { + case 'G11': + dataHandler.initialData.eq = 1; + dataHandler.dataToSend.eq = 1; + dataHandler.initialData.ims = 1; + dataHandler.dataToSend.ims = 1; + break; + case 'G01': + dataHandler.initialData.eq = 0; + dataHandler.dataToSend.eq = 0; + dataHandler.initialData.ims = 1; + dataHandler.dataToSend.ims = 1; + break; + case 'G10': + dataHandler.initialData.eq = 1; + dataHandler.dataToSend.eq = 1; + dataHandler.initialData.ims = 0; + dataHandler.dataToSend.ims = 0; + break; + case 'G00': + dataHandler.initialData.eq = 0; + dataHandler.initialData.ims = 0; + dataHandler.dataToSend.eq = 0; + dataHandler.dataToSend.ims = 0; + break; + } } if (authFlags.authMsg && authFlags.firstClient) { @@ -155,9 +181,11 @@ function connectToXdrd() { client.on('close', () => { logWarn('Disconnected from xdrd. Attempting to reconnect.'); - setTimeout(function () { - connectToXdrd(); - }, 2000) + if(serverConfig.autoShutdown === false) { + setTimeout(function () { + connectToXdrd(); + }, 2000) + } }); client.on('error', (err) => { @@ -235,7 +263,7 @@ app.set('view engine', 'ejs'); // Set EJS as the template engine app.set('views', path.join(__dirname, '/web')) app.get('/', (req, res) => { - if (!fs.existsSync("config.json")) { + if (!fs.existsSync(configName + '.json')) { parseAudioDevice((result) => { res.render('setup', { isAdminAuthenticated: true, @@ -282,17 +310,17 @@ app.get('/logout', (req, res) => { app.post('/saveData', (req, res) => { const data = req.body; let firstSetup; - if(req.session.isAdminAuthenticated || !fs.existsSync('config.json')) { + if(req.session.isAdminAuthenticated || !fs.existsSync(configName + '.json')) { configUpdate(data); fmdxList.update(); - if(!fs.existsSync('config.json')) { + if(!fs.existsSync(configName + '.json')) { firstSetup = true; } /* TODO: Refactor to server_config.js */ // Save data to a JSON file - fs.writeFile('config.json', JSON.stringify(serverConfig, null, 2), (err) => { + fs.writeFile(configName + '.json', JSON.stringify(serverConfig, null, 2), (err) => { if (err) { logError(err); res.status(500).send('Internal Server Error'); @@ -312,20 +340,20 @@ app.post('/saveData', (req, res) => { app.get('/getData', (req, res) => { if(req.session.isAdminAuthenticated) { // Check if the file exists - fs.access(configPath, fs.constants.F_OK, (err) => { + fs.access(configName + '.json', fs.constants.F_OK, (err) => { if (err) { // File does not exist res.status(404).send('Data not found'); } else { // File exists, send it as the response - res.sendFile(configPath); + res.sendFile(path.join(__dirname) + '/' + configName + '.json'); } }); } }); app.get('/getDevices', (req, res) => { - if (req.session.isAdminAuthenticated || !fs.existsSync('config.json')) { + if (req.session.isAdminAuthenticated || !fs.existsSync(configName + '.json')) { parseAudioDevice((result) => { res.json(result); }); @@ -342,6 +370,9 @@ wss.on('connection', (ws, request) => { const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress; currentUsers++; dataHandler.showOnlineUsers(currentUsers); + if(currentUsers > 0 && serverConfig.autoShutdown === true) { + client.write('x\n'); + } // Use ipinfo.io API to get geolocation information https.get(`https://ipinfo.io/${clientIp}/json`, (response) => { @@ -391,6 +422,9 @@ wss.on('connection', (ws, request) => { ws.on('close', (code, reason) => { currentUsers--; dataHandler.showOnlineUsers(currentUsers); + if(currentUsers === 0 && serverConfig.autoShutdown === true) { + client.write('X\n'); + } logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`); }); @@ -410,7 +444,9 @@ httpServer.on('upgrade', (request, socket, head) => { app.use(express.static(path.join(__dirname, 'web'))); httpServer.listen(serverConfig.webserver.webserverPort, serverConfig.webserver.webserverIp, () => { - logInfo(`Web server is running at \x1b[34mhttp://${serverConfig.webserver.webserverIp}:${serverConfig.webserver.webserverPort}\x1b[0m.`); + let currentAddress = serverConfig.webserver.webserverIp; + currentAddress == '0.0.0.0' ? currentAddress = 'localhost' : currentAddress = serverConfig.webserver.webserverIp; + logInfo(`Web server is running at \x1b[34mhttp://${currentAddress}:${serverConfig.webserver.webserverPort}\x1b[0m.`); }); fmdxList.update(); diff --git a/package-lock.json b/package-lock.json index b0562ca..33877c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "xdrd-client", - "version": "1.0.0", + "name": "fm-dx-webserver", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "xdrd-client", - "version": "1.0.0", + "name": "fm-dx-webserver", + "version": "1.0.6", "license": "ISC", "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", @@ -18,7 +18,6 @@ "http": "^0.0.1-security", "https": "1.0.0", "koffi": "2.7.2", - "lamejs": "^1.2.1", "net": "1.0.2", "websocket": "1.0.34", "wrtc": "^0.4.7", @@ -921,14 +920,6 @@ "integrity": "sha512-AWcsEKETQuELxK0Wq/aXDkDiNFFY41TxZQSrKm2Nd6HO/KTHeohPOOIlh2OfQnBXJbRjx5etpWt8cbqMUZo2sg==", "hasInstallScript": true }, - "node_modules/lamejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/lamejs/-/lamejs-1.2.1.tgz", - "integrity": "sha512-s7bxvjvYthw6oPLCm5pFxvA84wUROODB8jEO2+CE1adhKgrIvVOlmMgY8zyugxGrvRaDHNJanOiS21/emty6dQ==", - "dependencies": { - "use-strict": "1.0.1" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1536,11 +1527,6 @@ "node": ">= 0.8" } }, - "node_modules/use-strict": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/use-strict/-/use-strict-1.0.1.tgz", - "integrity": "sha512-IeiWvvEXfW5ltKVMkxq6FvNf2LojMKvB2OCeja6+ct24S1XOmQw2dGr2JyndwACWAGJva9B7yPHwAmeA9QCqAQ==" - }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", diff --git a/package.json b/package.json index 08d0ff6..6e8de41 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { "name": "fm-dx-webserver", - "version": "1.0.2", + "version": "1.0.6", "description": "", "main": "index.js", "scripts": { "debug": "node index.js --debug", + "debug_ffmpeg": "node index.js --ffmpegdebug", + "debug_full": "node index.js --debug --ffmpegdebug", "webserver": "node index.js" }, "author": "", @@ -19,7 +21,6 @@ "http": "^0.0.1-security", "https": "1.0.0", "koffi": "2.7.2", - "lamejs": "^1.2.1", "net": "1.0.2", "websocket": "1.0.34", "wrtc": "^0.4.7", diff --git a/server_config.js b/server_config.js index ca828f8..b80dac7 100644 --- a/server_config.js +++ b/server_config.js @@ -2,6 +2,14 @@ const fs = require('fs'); const { logDebug, logError, logInfo, logWarn } = require('./console'); +let configName = 'config'; + +const index = process.argv.indexOf('--config'); +if (index !== -1 && index + 1 < process.argv.length) { + configName = process.argv[index + 1]; + logInfo('Loading with a custom config file:', configName + '.json') +} + let serverConfig = { webserver: { webserverIp: "0.0.0.0", @@ -13,6 +21,11 @@ let serverConfig = { xdrdPort: "7373", xdrdPassword: "" }, + audio: { + audioDevice: "Microphone (High Definition Audio Device)", + audioChannels: 2, + audioBitrate: "128k" + }, identification: { token: null, tunerName: "", @@ -27,7 +40,8 @@ let serverConfig = { adminPass: "" }, publicTuner: true, - lockToAdmin: false + lockToAdmin: false, + autoShutdown: false, }; function deepMerge(target, source) @@ -46,7 +60,7 @@ function configUpdate(newConfig) { } function configSave() { - fs.writeFile('config.json', JSON.stringify(serverConfig, null, 2), (err) => { + fs.writeFile(configName + '.json', JSON.stringify(serverConfig, null, 2), (err) => { if (err) { logError(err); } else { @@ -55,11 +69,11 @@ function configSave() { }); } -if (fs.existsSync('config.json')) { - const configFileContents = fs.readFileSync('config.json', 'utf8'); +if (fs.existsSync(configName + '.json')) { + const configFileContents = fs.readFileSync(configName + '.json', 'utf8'); serverConfig = JSON.parse(configFileContents); } module.exports = { - serverConfig, configUpdate, configSave + configName, serverConfig, configUpdate, configSave }; diff --git a/stream/index.js b/stream/index.js index 504801e..b76521e 100644 --- a/stream/index.js +++ b/stream/index.js @@ -1,22 +1,7 @@ const { spawn } = require('child_process'); const fs = require('fs'); const consoleCmd = require('../console.js'); - -let serverConfig = { - webserver: { - audioPort: "8081" - }, - audio: { - audioDevice: "Microphone (High Definition Audio Device)", - audioChannels: 2, - audioBitrate: "128k" - }, -}; - -if(fs.existsSync('./config.json')) { - const configFileContents = fs.readFileSync('./config.json', 'utf8'); - serverConfig = JSON.parse(configFileContents); -} +const { configName, serverConfig, configUpdate, configSave } = require('../server_config'); function enableAudioStream() { var ffmpegCommand; diff --git a/tx_search.js b/tx_search.js index 7e28f37..bf9eb25 100644 --- a/tx_search.js +++ b/tx_search.js @@ -1,21 +1,9 @@ const fetch = require('node-fetch'); var fs = require('fs'); +const { serverConfig } = require('./server_config') 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; diff --git a/web/css/helpers.css b/web/css/helpers.css index 918d9e8..a159a7a 100644 --- a/web/css/helpers.css +++ b/web/css/helpers.css @@ -117,10 +117,18 @@ font-size: 11px; } +.text-light { + font-weight: 300; +} + .text-bold { font-weight: bold; } +.text-monospace { + font-family: "Roboto Mono", monospace; +} + .text-gray { color: #666; } diff --git a/web/js/main.js b/web/js/main.js index 75fda8e..387cd5d 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -5,6 +5,7 @@ var socket = new WebSocket(socketAddress); var parsedData; var data = []; let signalChart; +let updateCounter = 0; const europe_programmes = [ "No PTY", "News", "Current Affairs", "Info", @@ -176,15 +177,12 @@ function getLocalizedTime(serverTime) { // Convert server time to a Date object const serverDate = new Date(serverTime); - // Get local time zone offset - const localOffset = serverDate.getTimezoneOffset() * 60000; // Convert minutes to milliseconds - // Calculate local time by adding the offset const localTime = new Date(serverDate.getTime()); // Format local time const options = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }; - const formattedLocalTime = localTime.toLocaleString('en-US', options); + const formattedLocalTime = localTime.toLocaleString(navigator.language ? navigator.language : 'en-US', options); return formattedLocalTime; } @@ -603,12 +601,17 @@ function updateDataElements(parsedData) { } else { $('#data-station-container').removeAttr('style'); } + + updateCounter++; + if (updateCounter % 30 === 0) { + $('#data-ps').attr('aria-label', parsedData.ps); + $('#data-rt0').attr('aria-label', parsedData.rt0); + $('#data-rt1').attr('aria-label', parsedData.rt1); + } } let isEventListenerAdded = false; -let updateCounter = 0; - function updatePanels(parsedData) { updateCounter++; diff --git a/web/js/setup.js b/web/js/setup.js index 99ee25a..14dece7 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -160,6 +160,7 @@ function fetchData() { $("#tuner-public").prop("checked", data.publicTuner); $("#tuner-lock").prop("checked", data.lockToAdmin); + $("#shutdown-tuner").prop("checked", data.autoShutdown); // Check if latitude and longitude are present in the data if (data.identification.lat && data.identification.lon) { @@ -214,6 +215,7 @@ function submitData() { const publicTuner = $("#tuner-public").is(":checked"); const lockToAdmin = $("#tuner-lock").is(":checked"); + const autoShutdown = $("#shutdown-tuner").is(":checked"); const data = { webserver: { @@ -244,7 +246,8 @@ function submitData() { adminPass, }, publicTuner, - lockToAdmin + lockToAdmin, + autoShutdown }; diff --git a/web/setup.ejs b/web/setup.ejs index 30429b0..4e1573f 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -16,7 +16,7 @@ <% if (isAdminAuthenticated) { %>
This web setup allows you to set up your entire tuner.
Some settings will only change after a server restart.
In case you are setting up the webserver for the first time, we already filled fail-safe defaults for you.
You are currently not logged in as an administrator and therefore can't change the settings.
Please login below.