Files
weather-station/server/src/sats.mjs
2026-09-11 11:08:38 -04:00

88 lines
2.7 KiB
JavaScript

import {Aedes} from 'aedes';
import {createServer} from 'net';
import {cfg} from './config.mjs';
const HISTORY_LIMIT = 500;
const TTL_MS = 60_000;
const satellites = new Map();
function pruneStale() {
const now = Date.now();
for (const [key, bucket] of satellites) {
if (now - bucket.lastSeen > TTL_MS) satellites.delete(key);
}
}
function safeJson(buf) {
try { return JSON.parse(buf.toString()); } catch { return null; }
}
/**
* Best-effort field mapping — adjust once real TinyGS payload shape is confirmed
* from the console logs. Unknown fields are kept under `raw` for inspection.
*/
function extractReading(topic, payload) {
const data = safeJson(payload) ?? {raw: payload.toString()};
return {
timestamp: Date.now(),
topic,
satellite: data.satellite ?? data.name ?? data.sat ?? 'unknown',
norad: data.norad ?? data.noradId ?? data.NORAD ?? null,
freqMHz: data.freq ?? data.frequency ?? null,
packetRssi: data.rssi ?? null,
packetSnr: data.snr ?? null,
freqError: data.frequency_error ?? data.freqError ?? null,
crcOk: data.crc_error !== undefined ? !data.crc_error : null,
raw: data,
};
}
function handlePacket(topic, payload) {
const reading = extractReading(topic, payload);
const key = reading.norad ? `${reading.satellite}-${reading.norad}` : reading.satellite;
let bucket = satellites.get(key);
if (!bucket) {
bucket = {history: [], lastSeen: 0};
satellites.set(key, bucket);
}
bucket.lastSeen = reading.timestamp;
bucket.history.push(reading);
if (bucket.history.length > HISTORY_LIMIT) bucket.history.shift();
pruneStale();
}
export async function startTinyGSServer() {
const {TINYGS_MQTT_PORT = 1883, TINYGS_MQTT_USER, TINYGS_MQTT_PASS} = cfg();
const broker = await Aedes.createBroker();
const server = createServer(broker.handle);
if (TINYGS_MQTT_USER) {
broker.authenticate = (client, username, password, cb) => {
const ok = username === TINYGS_MQTT_USER && password?.toString() === TINYGS_MQTT_PASS;
if (!ok) console.warn(`[tinygs] rejected auth from ${client.id}`);
cb(null, ok);
};
}
broker.on('publish', (packet, client) => {
if (!client || packet.topic.startsWith('$SYS')) return;
console.info(`[tinygs] ${packet.topic}`, packet.payload.toString().slice(0, 200));
handlePacket(packet.topic, packet.payload);
});
broker.on('client', client => console.info(`[tinygs] connected: ${client.id}`));
broker.on('clientDisconnect', client => console.info(`[tinygs] disconnected: ${client.id}`));
server.listen(TINYGS_MQTT_PORT, () => console.info(`🛜 MQTT broker - http://localhost:${TINYGS_MQTT_PORT}`));
}
export const getTinyGSData = () => {
pruneStale();
return Array.from(satellites.values()).map(bucket => ({
...bucket.history.at(-1),
history: bucket.history.slice(0, -1),
}));
}