const { SerialPort } = require('serialport'); const tunnel = require('./tunnel'); const helpers = require('./helpers'); const dataHandler = require('./datahandler'); const { logError, logInfo, logWarn } = require('./console'); const { serverConfig, configExists } = require('./server_config'); const pluginsApi = require('./plugins_api'); const { startServer, wss } = require("./web"); const client = new (require('net')).Socket(); // Get all plugins from config and find corresponding server files const plugins = helpers.findServerFiles(serverConfig.plugins); // Start the first plugin after 3 seconds, then the rest with 3 seconds delay if (plugins.length > 0) { setTimeout(() => { helpers.startPluginsWithDelay(plugins, 3000); // Start plugins with 3 seconds interval }, 4000); // Initial delay of 4 seconds for the first plugin } const terminalWidth = require('readline').createInterface({input: process.stdin, output: process.stdout}).output.columns; console.log('\x1b[32m' + require('figlet').textSync("FM-DX Webserver")); console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org + KubaPro010\x1b[0m'); console.log("v" + require('../package.json').version) console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m'); require('./plugins'); let serialport; tunnel.download(); connectToXdrd(); connectToSerial(); // Serialport retry code when port is open but communication is lost (additional code in datahandler.js) dataHandler.state.isSerialportRetrying = false; setInterval(() => { if (!dataHandler.state.isSerialportAlive && serverConfig.xdrd.wirelessConnection === false) { dataHandler.state.isSerialportAlive = true; dataHandler.state.isSerialportRetrying = true; if (serialport && serialport.isOpen) { logWarn('Communication lost from ' + serverConfig.xdrd.comPort + ', force closing serialport.'); setTimeout(() => { serialport.close((err) => { if (err) logError('Error closing serialport: ', err.message); }); }, 1000); } else logWarn('Communication lost from ' + serverConfig.xdrd.comPort + '.'); } }, 2000); // Serial Connection function connectToSerial() { if (serverConfig.xdrd.wirelessConnection === true) return; serialport = new SerialPort({ path: serverConfig.xdrd.comPort, baudRate: 115200, autoOpen: false, dtr: false, rts: false }); serialport.open((err) => { if (err) { logError('Error opening port: ' + err.message); setTimeout(() => { connectToSerial(); }, 5000); return; } logInfo('Using serial port: ' + serverConfig.xdrd.comPort); dataHandler.state.isSerialportAlive = true; pluginsApi.setOutput(serialport); setTimeout(() => { serialport.write('x\n'); }, 1000); setTimeout(() => { serialport.write('Q0\n'); serialport.write('M0\n'); serialport.write(`Z${serverConfig.antennaStartup}\n`); // Antenna on startup if (serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true) { serialport.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n'); dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3); dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3); } else if (dataHandler.state.lastFrequencyAlive && dataHandler.state.isSerialportRetrying) { // Serialport retry code when port is open but communication is lost serialport.write('T' + (dataHandler.state.lastFrequencyAlive * 1000) + '\n'); } else serialport.write('T87500\n'); dataHandler.state.isSerialportRetrying = false; if (serverConfig.device === 'si47xx') serialport.write('A0\n'); serialport.write('F-1\n'); serialport.write('W0\n'); serverConfig.webserver.rdsMode ? serialport.write('D1\n') : serialport.write('D0\n'); serialport.write(`G${serverConfig.ceqStartup}${serverConfig.imsStartup}\n`); serialport.write(`B${serverConfig.stereoStartup}\n`); // Mono serverConfig.audio.startupVolume ? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n') : serialport.write('Y100\n'); }, 6000); serialport.on('data', (data) => { helpers.resolveDataBuffer(data, wss, rdsWss); }); serialport.on('error', (error) => { logError(error.message); }); }); // Handle port closure serialport.on('close', () => { pluginsApi.setOutput(null); logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.'); setTimeout(() => { dataHandler.state.isSerialportRetrying = true; connectToSerial(); }, 5000); }); return serialport; } // xdrd connection let authFlags = {}; function connectToXdrd() { const { xdrd } = serverConfig; if (xdrd.wirelessConnection && configExists()) { client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => { logInfo('Connection to xdrd established successfully.'); pluginsApi.setOutput(client); authFlags = { authMsg: false, firstClient: false, receivedSalt: '', receivedPassword: false, messageCount: 0, }; }); } } client.on('data', (data) => { const { xdrd } = serverConfig; helpers.resolveDataBuffer(data, wss, rdsWss); if (authFlags.authMsg == true && authFlags.messageCount > 1) return; authFlags.messageCount++; const receivedData = data.toString(); const lines = receivedData.split('\n'); for (const line of lines) { if (authFlags.receivedPassword === false) { authFlags.receivedSalt = line.trim(); authFlags.receivedPassword = true; helpers.authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword); } 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.'); } else if (line.startsWith('G')) { dataHandler.initialData.eq = line.charAt(1); dataHandler.dataToSend.eq = line.charAt(1); dataHandler.initialData.ims = line.charAt(2); dataHandler.dataToSend.ims = line.charAt(2); } else if (line.startsWith('Z')) { let modifiedLine = line.slice(1); dataHandler.initialData.ant = modifiedLine; dataHandler.dataToSend.ant = modifiedLine; } if (authFlags.authMsg === true && authFlags.firstClient === true) { client.write('x\n'); client.write(serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? 'T' + Math.round(serverConfig.defaultFreq * 1000) + '\n' : 'T87500\n'); dataHandler.initialData.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3); dataHandler.dataToSend.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3); if (serverConfig.device === 'si47xx') serialport.write('A0\n'); client.write(serverConfig.audio.startupVolume ? 'Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n' : 'Y100\n'); serverConfig.webserver.rdsMode ? client.write('D1\n') : client.write('D0\n'); return; } } } }); client.on('close', () => { pluginsApi.setOutput(null); if(serverConfig.autoShutdown === false) { logWarn('Disconnected from xdrd. Attempting to reconnect.'); setTimeout(function () { connectToXdrd(); }, 2000) } else logWarn('Disconnected from xdrd.'); }); client.on('error', (err) => { switch (true) { case err.message.includes("ECONNRESET"): logError("Connection to xdrd lost. Reconnecting..."); break; case err.message.includes("ETIMEDOUT"): logError("Connection to xdrd @ " + serverConfig.xdrd.xdrdIp + ":" + serverConfig.xdrd.xdrdPort + " timed out."); break; case err.message.includes("ECONNREFUSED"): logError("Connection to xdrd @ " + serverConfig.xdrd.xdrdIp + ":" + serverConfig.xdrd.xdrdPort + " failed. Is xdrd running?"); break; case err.message.includes("EINVAL"): logError("Attempts to reconnect are failing repeatedly. Consider checking your settings or restarting xdrd."); break; default: logError("Unhandled error: ", err.message); break; } }); require('./stream/index'); startServer(serverConfig.webserver.webserverIp === '0.0.0.0' ? 'localhost' : serverConfig.webserver.webserverIp); tunnel.connect(); require('./fmdx_list').update();