From fc306f277a3aa7c3e28cdf0fa4ef4d140ebd9417 Mon Sep 17 00:00:00 2001 From: ztimson Date: Fri, 11 Sep 2026 12:17:55 -0400 Subject: [PATCH] Reverted tinygs mqtt --- server/package-lock.json | 1 - server/package.json | 1 - server/src/sats.mjs | 107 ++++++++++++++++----------------------- server/src/server.mjs | 3 +- 4 files changed, 46 insertions(+), 66 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index d13cdf4..a9eedb4 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -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", diff --git a/server/package.json b/server/package.json index 53c8175..58ad682 100644 --- a/server/package.json +++ b/server/package.json @@ -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", diff --git a/server/src/sats.mjs b/server/src/sats.mjs index 20e1dc8..609f7bc 100644 --- a/server/src/sats.mjs +++ b/server/src/sats.mjs @@ -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; } -} +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; -/** - * 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, - }; -} + const key = reading.satellite + '-' + reading.freqMHz; + let bucket = satellites.get(key); + if (!bucket) { + bucket = { history: [], lastSeen: 0 }; + satellites.set(key, bucket); + } -function handlePacket(topic, payload) { - const reading = extractReading(topic, payload); - const key = reading.norad ? `${reading.satellite}-${reading.norad}` : reading.satellite; + bucket.lastSeen = reading.timestamp; + if (!isEqual(bucket.history.at(-1), reading)) { + bucket.history.push(reading); + if (bucket.history.length > HISTORY_LIMIT) bucket.history.shift(); + } - let bucket = satellites.get(key); - if (!bucket) { - bucket = {history: [], lastSeen: 0}; - satellites.set(key, bucket); + pruneStale(); + } catch (err) { + console.error('[tinygs] poll failed', err); } - 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, '0.0.0.0', () => console.info(`🛜 MQTT broker - localhost:${TINYGS_MQTT_PORT}`)); -} +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), })); diff --git a/server/src/server.mjs b/server/src/server.mjs index 40d7e50..bac05ff 100644 --- a/server/src/server.mjs +++ b/server/src/server.mjs @@ -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}`)