Reverted tinygs mqtt
This commit is contained in:
1
server/package-lock.json
generated
1
server/package-lock.json
generated
@@ -11,7 +11,6 @@
|
||||
"@scalar/express-api-reference": "^0.10.4",
|
||||
"@ztimson/utils": "^0.29.5",
|
||||
"adm-zip": "^0.5.17",
|
||||
"aedes": "^1.1.2",
|
||||
"better-sqlite3": "^12.11.1",
|
||||
"cheerio": "^1.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"@scalar/express-api-reference": "^0.10.4",
|
||||
"@ztimson/utils": "^0.29.5",
|
||||
"adm-zip": "^0.5.17",
|
||||
"aedes": "^1.1.2",
|
||||
"better-sqlite3": "^12.11.1",
|
||||
"cheerio": "^1.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
|
||||
@@ -1,86 +1,69 @@
|
||||
import {Aedes} from 'aedes';
|
||||
import {createServer} from 'net';
|
||||
import {cfg} from './config.mjs';
|
||||
import {isEqual} from '@ztimson/utils';
|
||||
|
||||
const HISTORY_LIMIT = 500;
|
||||
const POLL_MS = 5_000;
|
||||
const TTL_MS = 60_000;
|
||||
|
||||
function parseWm(raw) {
|
||||
const f = raw.replace(/<[^>]+>/g, '').split(',').map(s => s.trim());
|
||||
const [latitude, longitude] = f[15].split('/').map(s => parseFloat(s));
|
||||
const [az, el] = f[16].split('/').map(s => parseFloat(s));
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
freqMHz: parseFloat(f[3]),
|
||||
satellite: f[14],
|
||||
latitude, longitude,
|
||||
az, el,
|
||||
packetRssi: parseFloat(f[21]),
|
||||
packetSnr: parseFloat(f[22]),
|
||||
freqError: parseFloat(f[23]),
|
||||
crcOk: !/CRC ERROR/i.test(f[24]),
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
for (const [name, bucket] of satellites) {
|
||||
if (now - bucket.lastSeen > TTL_MS) satellites.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
export async function pollTinyGS() {
|
||||
const { TINYGS_URL, TINYGS_AUTH } = cfg();
|
||||
try {
|
||||
const raw = await fetch(TINYGS_URL + '/wm', {
|
||||
headers: TINYGS_AUTH ? {Authorization: `Basic ${TINYGS_AUTH}`} : {}
|
||||
}).then(r => r.text());
|
||||
const reading = parseWm(raw);
|
||||
if (!reading.satellite || reading.satellite === '-') return;
|
||||
|
||||
const key = reading.satellite + '-' + reading.freqMHz;
|
||||
let bucket = satellites.get(key);
|
||||
if (!bucket) {
|
||||
bucket = {history: [], lastSeen: 0};
|
||||
bucket = { history: [], lastSeen: 0 };
|
||||
satellites.set(key, bucket);
|
||||
}
|
||||
|
||||
bucket.lastSeen = reading.timestamp;
|
||||
if (!isEqual(bucket.history.at(-1), reading)) {
|
||||
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, '0.0.0.0', () => console.info(`🛜 MQTT broker - localhost:${TINYGS_MQTT_PORT}`));
|
||||
pruneStale();
|
||||
} catch (err) {
|
||||
console.error('[tinygs] poll failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(pollTinyGS, POLL_MS);
|
||||
|
||||
export const getTinyGSData = () => {
|
||||
pruneStale();
|
||||
return Array.from(satellites.values()).map(bucket => ({
|
||||
return Array.from(satellites.entries()).map(([name, bucket]) => ({
|
||||
...bucket.history.at(-1),
|
||||
history: bucket.history.slice(0, -1),
|
||||
}));
|
||||
|
||||
@@ -12,7 +12,7 @@ import {getADSBImage, getADSB, getADSBHistory, getADSBRange, initAircraftDb} fro
|
||||
import {fetchIcon} from './openweather.mjs';
|
||||
import {getForecast, getWeatherCondition, forecastTTL} from './forecast.mjs';
|
||||
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
|
||||
import {getTinyGSData, startTinyGSServer} from './sats.mjs';
|
||||
import {getTinyGSData} from './sats.mjs';
|
||||
|
||||
// ── Uncaught error handlers ───────────────────────────────────────────────────
|
||||
|
||||
@@ -209,7 +209,6 @@ const c = cfg();
|
||||
setTimeout(getForecast, 1)
|
||||
setInterval(getForecast, forecastTTL)
|
||||
await initAircraftDb();
|
||||
await startTinyGSServer();
|
||||
|
||||
app.listen(c.PORT, () => {
|
||||
console.log(`⛅ Weather API — http://localhost:${c.PORT}`)
|
||||
|
||||
Reference in New Issue
Block a user