Reverted tinygs mqtt

This commit is contained in:
2026-09-11 12:17:55 -04:00
parent 396a1f4cab
commit fc306f277a
4 changed files with 46 additions and 66 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 };
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();
} catch (err) {
console.error('[tinygs] poll failed', err);
}
}
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),
}));

View File

@@ -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}`)