node-wsjtx-companion – Blame information for rev 5

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env node
2 /*************************************************************************/
3 /* Copyright (C) 2023 Wizardry and Steamworks - License: GNU GPLv3 */
4 /*************************************************************************/
5  
6 // Import packages.
7 const https = require('https')
8 const http = require('http')
9 const path = require('path')
10 const fs = require('fs')
11 const url = require('url')
12 const moment = require('moment')
13 const winston = require('winston')
14 const yargs = require('yargs')
15 const dns = require('dns')
16 const dgram = require('dgram')
4 office 17 const udpServer = dgram.createSocket( { type: 'udp4', reuseAddr: true } )
1 office 18 const { WsjtxUdpParser } = require('@henriquegravina/wsjtxudpparser')
19 const { AdifReader } = require('tcadif')
20 const Readable = require('stream').Readable
21  
22 // Local imports.
23 const Handler = require(
24 path
25 .resolve(
26 path.dirname(require.main.filename),
27 'src',
28 'handler'
29 )
30 )
31  
32 const certs = require(
33 path
34 .resolve(
35 path.dirname(require.main.filename),
36 'src',
37 'certs'
38 )
39 )
40  
41 // Load configuration file.
42 const config = require(
43 path
44 .resolve(
45 path.dirname(require.main.filename),
46 'config'
47 )
48 )
49  
50 // Get command-line arguments.
51 const argv = yargs
52 .version()
53 .option('root', {
54 alias: 'd',
55 describe: 'Path to the document root',
56 demandOption: true
57 })
58 .help()
59 .argv
60  
61 // Create various logging mechanisms.
62 // RFC5424 - { emerg: 0, alert: 1, crit: 2, error: 3, warning: 4, notice: 5, info: 6, debug: 7 }
63 winston.setLevels(winston.config.syslog.levels)
64 const log = new winston.Logger({
65 transports: [
66 new winston.transports.File({
67 level: 'info',
68 filename: path.resolve(
69 path.dirname(require.main.filename),
70 config.log.file
71 ),
72 handleExceptions: true,
73 json: false,
74 maxsize: 1048576, // 1MiB.
75 maxFiles: 10, // Ten rotations.
76 colorize: false,
77 timestamp: () => moment()
78 .format('YYYYMMDDTHHmmss')
79 }),
80 new winston.transports.Console({
81 level: 'info',
82 handleExceptions: true,
83 json: false,
84 colorize: true,
85 timestamp: () => moment()
86 .format('YYYYMMDDTHHmmss')
87 })
88 ],
89 exitOnError: false
90 })
91  
92 fs.realpath(argv.root, (error, root) => {
93 if (error) {
94 log.error('Could not find document root: ' + argv.root)
95 process.exit(1)
96 }
97  
98 // Start HTTP server.
99 http.createServer(
100 // authentication,
101 (request, response) => {
102 // Configuration path requested, so send the server configuration if allowed.
103 if (config.configuration.enable === true &&
104 url.parse(request.url, true).path ===
105 config.configuration.path) {
106 const address = request.socket.address()
107 log.info('HTTP Server configuration requested by: ' +
108 address.address + ':' +
109 address.port
110 )
111 response.setHeader('Content-Type', 'application/json')
112 response.end(JSON.stringify(config))
113 return
114 }
115  
116 // Process the resource.
117 new Handler().process(config, request, response, root)
118 .on('log', (data) => {
119 log.log(data.severity, data.message)
120 })
121 .on('data', (result) => {
122 response.setHeader('Content-Type', result.type)
123 response.writeHead(result.status)
124 result.data.pipe(response)
125 })
126 }
127 ).listen(config.net.port, config.net.address, () => {
128 log.info('HTTP Server accessible at: http://' +
129 config.net.address +
130 ':' +
131 config.net.port +
132 ' and serving files from directory: ' +
133 root
134 )
135 })
136  
137 // Start HTTPs server if enabled.
138 if (config.ssl.enable) {
139 // Generate certificates for HTTPs.
140 certs.generate(
141 config.site.name,
142 config.net.address,
143 config.ssl.privateKeySize,
144 (certificates) => {
145 https.createServer(
146 // authentication,
147 {
148 key: certificates.privateKey,
149 cert: certificates.certificate,
150 },
151 (request, response) => {
152 // Configuration path requested, so send the server configuration if allowed.
153 if (config.configuration.enable === true &&
154 url.parse(request.url, true).path ===
155 config.configuration.path) {
156 const address = request.socket.address()
157 log.info('HTTP Server configuration requested by: ' +
158 address.address + ':' +
159 address.port
160 )
161 response.setHeader('Content-Type', 'application/json')
162 response.end(JSON.stringify(config))
163 return
164 }
165  
166  
167 new Handler().process(config, request, response, root)
168 .on('log', (data) => {
169 log.log(data.severity, data.message)
170 })
171 .on('data', (result) => {
172 response.setHeader('Content-Type', result.type)
173 response.writeHead(result.status)
174 result.data.pipe(response)
175 })
176 }
177 ).listen(config.ssl.port, config.ssl.address, () => {
178 log.info('HTTPs Server accessible at: https://' +
179 config.ssl.address +
180 ':' +
181 config.ssl.port +
182 ' and serving files from directory: ' +
183 root
184 )
185 })
186 }
187 )
188 }
189 })
190  
191 // start UDP server to listen to WSJT-X UDP messages
192 udpServer.on('error', (err) => {
193 log.error(`UDP server error:\n${err.stack}`)
194 udpServer.close()
195 })
196  
197 // when UDP server is listening
198 udpServer.on('listening', () => {
5 office 199 udpServer.addMembership(config.ham.udpAddress)
200  
1 office 201 const address = udpServer.address()
202 log.info(`UDP server listening on ${address.address}:${address.port}`)
203 })
204  
205 // process WSJT-X messages in order to generate QSL cards
206 udpServer.on('message', (buffer, rinfo) => { // A cada mensagem recebida passa aqui:
207 try {
208 const msg = new WsjtxUdpParser(buffer)
209 switch (msg.type) {
210 // Decode Adif message
211 case 12:
212 const stringReadable = new Readable()
213 const adifReader = new AdifReader()
214 adifReader.on(
215 'data',
216 record => {
217 // load the QSL template
218 const templateStream = fs.createReadStream(config.ham.qslTemplate)
219 const templateBuffer = []
220 templateStream.on('data', (chunk) => {
221 templateBuffer.push(chunk)
222 })
223 templateStream.on('end', () => {
224 // perform substitutions in order to generate the QSL card
225 let template = Buffer.concat(templateBuffer).toString()
226  
227 // our station
228 let x = config.ham.me
229 let o = config.ham.qslTemplateMap.this
230 x = x.padStart((x.length + o.length) / 2).padEnd(o.length)
231 template = template.replace(o, x)
232  
233 // their station
234 let call = record.CALL
235 var split = call.split('/')
236 x = split.reduce(
237 function (a, b) {
238 return a.length > b.length ? a : b;
239 }
240 )
241 o = config.ham.qslTemplateMap.call
242 x = x.padStart((x.length + o.length) / 2).padEnd(o.length)
243 template = template.replace(o, x)
244  
245 // the date of the QSO
246 x = record.QSO_DATE
247 o = config.ham.qslTemplateMap.date
248 x = x.padStart((x.length + o.length) / 2).padEnd(o.length)
249 template = template.replace(o, x)
250  
4 office 251 // the frequency, mode and band
1 office 252 x = `${parseInt(record.FREQ)} / ${record.MODE} / ${record.BAND}`
253 o = config.ham.qslTemplateMap.conn
254 x = x.padStart((x.length + o.length) / 2).padEnd(o.length)
255 template = template.replace(o, x)
256  
257 // the signal report
258 x = `${record.RST_SENT}/${record.RST_RCVD}`
259 o = config.ham.qslTemplateMap.sign
260 x = x.padStart((x.length + o.length) / 2).padEnd(o.length)
261 template = template.replace(o, x)
262  
263 // write the QSL card to storage
264 const qslStream = fs.createWriteStream(`www/qsl/${call}.txt`)
265 qslStream.write(template)
266 log.info(`Wrote QSL for ${call}`)
267 })
268 }
269 )
270 adifReader.on(
271 'error',
272 err => {
273 console.error('err', err)
274 }
275 )
276 // push the adif reported by WSJT-X to the stream to be processed
277 stringReadable.push(msg.adif)
278 stringReadable.push(null)
279 stringReadable.pipe(adifReader)
280 break;
5 office 281 default:
282 //log.info("Some activity detected...")
283 break;
1 office 284 }
285 } catch (exception) {
286 log.error(`Exception thrown while reading WSJT-X packet ${exception}`)
287 }
288 })
289  
290 // bind to the WSJT-X UDP port in order to process messages
5 office 291 udpServer.bind(config.ham.udpPort);