sotlas-api/rbn.js

159 wiersze
4.0 KiB
JavaScript

const reconnect = require('reconnect-net');
const wsManager = require('./ws-manager');
const carrier = require('carrier');
const config = require('./config');
const db = require('./db');
const utils = require('./utils');
const rbnSpotRegex = /^DX de (\S+):\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+dB\s+(\S+)\s+\S+\s+(CQ|DX)\s+(\d+)Z$/;
class RbnReceiver {
start() {
this.restartConnection();
wsManager.on('message', (ws, message) => {
if (message.rbnFilter !== undefined) {
//console.log("Set RBN filter to " + JSON.stringify(message.rbnFilter));
ws.rbnFilter = message.rbnFilter;
this.sendSpotHistory(ws)
}
});
}
restartConnection() {
if (this.re)
this.re.disconnect();
this.resetTimer();
this.re = reconnect((stream) => {
console.log("Connected to RBN");
stream.write(config.rbn.login + "\r\n");
if (config.rbn.server.commands) {
config.rbn.server.commands.forEach(command => {
stream.write(command + "\r\n");
});
}
carrier.carry(stream, (line) => {
this.handleLine(line);
});
});
this.re.on('error', (err) => {
console.error(`RBN connection error: ${err}`);
});
this.re.connect(config.rbn.server);
}
resetTimer() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
console.error("RBN: timeout, reconnecting");
this.restartConnection();
}, config.rbn.timeout);
}
handleLine(line) {
this.resetTimer();
let matches = rbnSpotRegex.exec(line);
if (matches) {
let spot = {
timeStamp: new Date(),
callsign: matches[3],
homeCallsign: this.homeCallsign(matches[3]),
spotter: matches[1].replace("-#", ""),
frequency: parseFloat((matches[2]/1000).toFixed(4)),
mode: matches[4],
snr: parseInt(matches[5]),
speed: parseInt(matches[6])
};
// Check if this is a known SOTA activator
db.getDb().collection('activators').countDocuments({callsign: { $in: utils.makeCallsignVariations(spot.homeCallsign) }}, (error, result) => {
if (result > 0) {
spot.isActivator = true;
}
db.getDb().collection('rbnspots').insertOne(spot, (error, result) => {
// _id has now been added, but not in our preferred format
spot._id = spot._id.toHexString()
wsManager.broadcast({'rbnSpot': spot}, (ws) => {
if (!ws.rbnFilter) {
return false;
}
if (ws.rbnFilter.homeCallsign && ws.rbnFilter.homeCallsign.includes(spot.homeCallsign)) {
return true;
}
if (ws.rbnFilter.isActivator && spot.isActivator) {
return true;
}
return false;
});
});
});
}
}
sendSpotHistory(ws) {
// Send the spot history for the currently defined RBN filter
if (!ws.rbnFilter.homeCallsign && !ws.rbnFilter.isActivator) {
return;
}
let query = {};
if (ws.rbnFilter.homeCallsign) {
query.homeCallsign = ws.rbnFilter.homeCallsign;
}
if (ws.rbnFilter.isActivator) {
query.isActivator = true;
}
let maxAge = parseInt(ws.rbnFilter.maxAge) || 3600000;
query.timeStamp = {$gte: new Date(new Date().getTime() - maxAge)};
db.getDb().collection('rbnspots').find(query).sort({timeStamp: -1}).limit(config.rbn.maxSpotHistory).toArray((err, rbnSpots) => {
if (err) {
console.error(err);
return;
}
rbnSpots.forEach(spot => {
spot._id = spot._id.toHexString();
});
let response = {rbnSpotHistory: rbnSpots};
if (ws.rbnFilter.viewId) {
response.viewId = ws.rbnFilter.viewId;
}
wsManager.unicast(response, ws);
});
}
homeCallsign(callsign) {
let parts = callsign.split('/');
let longestPart = '';
parts.forEach(part => {
if (part.length > longestPart.length) {
longestPart = part;
}
})
// For UK callsigns, normalize them all to 2E/G/M for the sake of comparison
let matches = longestPart.match(/^(2[DEIJMUW]|G[DIJMUW]?|M[DIJMUW]?)(\d[A-Z]{2,3})$/)
if (matches) {
longestPart = matches[1].replace(/^2./, '2E').replace(/^G[DIJMUW]/, 'G').replace(/^M[DIJMUW]/, 'M') + matches[2]
}
return longestPart;
}
}
module.exports = RbnReceiver;