From 092c4622ec571ce59c0ce6b7e55d4e1e18b5bc7b Mon Sep 17 00:00:00 2001 From: DJ2LS Date: Tue, 2 Jun 2026 18:49:23 +0000 Subject: [PATCH] Prettified Code! --- CONTRIBUTING.md | 13 +- freedata_gui/src/js/api.js | 17 +- freedata_gui/src/js/audioStreamHandler.js | 8 +- freedata_gui/src/js/broadcastsHandler.js | 16 +- freedata_gui/src/js/eventHandler.js | 10 +- freedata_gui/src/js/event_sock.js | 2 +- freedata_gui/src/js/metar.js | 208 +++++++++++++--------- freedata_gui/src/store/audioStore.js | 8 +- freedata_gui/src/store/broadcastStore.js | 13 +- freedata_gui/src/store/settingsStore.js | 2 +- 10 files changed, 166 insertions(+), 131 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94335f10..924f2f9f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,11 +7,12 @@ in C/C++, and the frontend is build with VueJS, so you will need Python (from 3.10 to 3.13) to work on the backend, and node+npm to work on the frontend. System dependencies required: - - python3 (with virtual environment support) - - portaudio - - hamlib (optional, there is a vendored version bundled with the freedata - server, or you can build it from source) - - nodejs + npm + +- python3 (with virtual environment support) +- portaudio +- hamlib (optional, there is a vendored version bundled with the freedata + server, or you can build it from source) +- nodejs + npm Example for debian trixie: `sudo apt update && sudo apt install python3 python3-venv nodejs npm portaudio19-dev` @@ -37,6 +38,7 @@ To fetch the `npm` dependencies go inside `./freedata_gui` and run `npm install`. See `./freedata_gui/README.md` for additional informations. ## Linting and Formatting (python-only) + The python sources must be formatted and checked using `ruff`. There is a CI workflow that block all PRs that do not pass the linter/formatter checks. @@ -54,5 +56,6 @@ usefull in the CI. In the future it will no longer be necessary fix automatically, you must fix them manually. ## Testing backend + To run the backend tests run inside the virtual environment `python -m unittest discover tests` diff --git a/freedata_gui/src/js/api.js b/freedata_gui/src/js/api.js index c5ef458a..8931866d 100644 --- a/freedata_gui/src/js/api.js +++ b/freedata_gui/src/js/api.js @@ -9,7 +9,10 @@ import { validateCallsignWithoutSSID, } from "./freedata"; import { processFreedataMessages } from "./messagesHandler"; -import { processFreedataDomains, processFreedataBroadcastsPerDomain } from "./broadcastsHandler"; +import { + processFreedataDomains, + processFreedataBroadcastsPerDomain, +} from "./broadcastsHandler"; import { useStateStore } from "../store/stateStore.js"; const state = useStateStore(pinia); @@ -327,13 +330,12 @@ export async function setStationInfo(callsign, info) { return await apiPost(`/freedata/station/${callsign}`, info); } - export async function getFreedataBroadcasts() { broadcastStore.loading = true; try { const res = await apiGet("/freedata/broadcasts"); if (res) { - processFreedataBroadcasts(res) + processFreedataBroadcasts(res); } } catch (error) { console.error("Error fetching broadcasts:", error); @@ -347,7 +349,7 @@ export async function getFreedataBroadcastsPerDomain(domain) { try { const res = await apiGet(`/freedata/broadcasts/${domain}/`); if (res) { - processFreedataBroadcastsPerDomain(res) + processFreedataBroadcastsPerDomain(res); } } catch (error) { console.error("Error fetching broadcasts:", error); @@ -356,13 +358,12 @@ export async function getFreedataBroadcastsPerDomain(domain) { } } - export async function getFreedataDomains() { broadcastStore.loading = true; try { const res = await apiGet("/freedata/broadcasts/domains"); if (res) { - processFreedataDomains(res) + processFreedataDomains(res); } } catch (error) { console.error("Error fetching broadcasts:", error); @@ -389,12 +390,8 @@ export async function deleteFreedataBroadcastDomain(id) { return await apiDelete(`/freedata/broadcasts/${id}`); } - - export async function postFreedataBroadcastADIF(id) { return await apiPost(`/freedata/broadcasts/${id}/adif`, { action: "retransmit", }); } - - diff --git a/freedata_gui/src/js/audioStreamHandler.js b/freedata_gui/src/js/audioStreamHandler.js index dea67dca..99c1841c 100644 --- a/freedata_gui/src/js/audioStreamHandler.js +++ b/freedata_gui/src/js/audioStreamHandler.js @@ -9,8 +9,8 @@ const MAX_BLOCKS = 10; export function addDataToAudio(data) { const int16 = new Int16Array(data); - const copy = new Int16Array(int16); // Kopie für Sicherheit -/* + const copy = new Int16Array(int16); // Kopie für Sicherheit + /* const stream = audio.rxStream; if (stream.length >= MAX_BLOCKS) { @@ -19,7 +19,5 @@ export function addDataToAudio(data) { stream.push(copy); */ - audio.addBlock(copy); - - + audio.addBlock(copy); } diff --git a/freedata_gui/src/js/broadcastsHandler.js b/freedata_gui/src/js/broadcastsHandler.js index f1358441..5a6b068d 100644 --- a/freedata_gui/src/js/broadcastsHandler.js +++ b/freedata_gui/src/js/broadcastsHandler.js @@ -1,19 +1,19 @@ -import { setActivePinia } from 'pinia'; -import pinia from '../store/index'; +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; setActivePinia(pinia); -import { useBroadcastStore } from '../store/broadcastStore.js'; +import { useBroadcastStore } from "../store/broadcastStore.js"; import { deleteFreedataBroadcastDomain, deleteFreedataBroadcastMessage, postFreedataBroadcastADIF, retransmitFreedataBroadcast, - sendFreedataBroadcastMessage + sendFreedataBroadcastMessage, } from "@/js/api"; const broadcast = useBroadcastStore(pinia); export async function processFreedataBroadcastsPerDomain(data) { - console.log(data) + console.log(data); broadcast.setBroadcastsForDomain(data); } @@ -25,12 +25,10 @@ export async function processFreedataDomains(data) { // sum of unread_count broadcast.totalUnreadMessages = Object.values(data).reduce( (sum, domain) => sum + (domain.unread_count || 0), - 0 + 0, ); } - - export function newBroadcastMessage(params) { sendFreedataBroadcastMessage(params); broadcast.triggerScrollToBottom(); @@ -42,7 +40,7 @@ export function repeatBroadcastTransmission(id) { export function deleteBroadcastDomainFromDB(domain) { deleteFreedataBroadcastDomain(domain); - broadcast.selectedDomain = ''; + broadcast.selectedDomain = ""; } export function sendBroadcastADIFviaUDP(domain) { diff --git a/freedata_gui/src/js/eventHandler.js b/freedata_gui/src/js/eventHandler.js index e28936e1..922bc06a 100644 --- a/freedata_gui/src/js/eventHandler.js +++ b/freedata_gui/src/js/eventHandler.js @@ -5,8 +5,8 @@ import { getModemState, getRadioStatus, getSysInfo, - getFreedataDomains, - getFreedataBroadcastsPerDomain + getFreedataDomains, + getFreedataBroadcastsPerDomain, } from "./api"; import { processFreedataMessages } from "./messagesHandler"; import { processRadioStatus } from "./radioHandler"; @@ -45,7 +45,7 @@ export async function loadAllData() { getOverallHealth(); getFreedataMessages(); getFreedataDomains(); - getFreedataBroadcastsPerDomain(broadcast.selectedDomain) + getFreedataBroadcastsPerDomain(broadcast.selectedDomain); processFreedataMessages(); processRadioStatus(); } @@ -111,8 +111,8 @@ export function eventDispatcher(data) { switch (data["message-db"]) { case "changed": console.log("fetching new messages..."); - getFreedataDomains(); - getFreedataBroadcastsPerDomain(broadcast.selectedDomain) + getFreedataDomains(); + getFreedataBroadcastsPerDomain(broadcast.selectedDomain); var messages = getFreedataMessages(); processFreedataMessages(messages); diff --git a/freedata_gui/src/js/event_sock.js b/freedata_gui/src/js/event_sock.js index 87d80040..4cfb5954 100644 --- a/freedata_gui/src/js/event_sock.js +++ b/freedata_gui/src/js/event_sock.js @@ -23,7 +23,7 @@ function connect(endpoint, dispatcher) { `${wsProtocol}//${hostname}:${adjustedPort}/${endpoint}`, ); - if (endpoint.includes("audio")){ + if (endpoint.includes("audio")) { socket.binaryType = "arraybuffer"; } diff --git a/freedata_gui/src/js/metar.js b/freedata_gui/src/js/metar.js index d3ac3c29..83292a6c 100644 --- a/freedata_gui/src/js/metar.js +++ b/freedata_gui/src/js/metar.js @@ -1,9 +1,16 @@ - export function parseMetar(raw) { const out = { iata: null, - day: null, hh: null, mm: null, - wind: { dir_deg: null, spd_kt: null, gust_kt: null, var_from: null, var_to: null }, + day: null, + hh: null, + mm: null, + wind: { + dir_deg: null, + spd_kt: null, + gust_kt: null, + var_from: null, + var_to: null, + }, visibility: { code: null, meters: null }, temp_c: null, dew_c: null, @@ -11,66 +18,72 @@ export function parseMetar(raw) { auto: false, cor: false, clouds: [], - raw: raw?.trim() || '' + raw: raw?.trim() || "", }; if (!raw) return out; - const tokens = raw.replace('=', '').trim().split(/\s+/); + const tokens = raw.replace("=", "").trim().split(/\s+/); - out.auto = tokens.includes('AUTO'); - out.cor = tokens.includes('COR'); + out.auto = tokens.includes("AUTO"); + out.cor = tokens.includes("COR"); for (const t of tokens) { - if (/^[A-Z]{3}$/.test(t)) { out.iata = t; break; } - if (/^[A-Z]{4}$/.test(t)) { out.iata = t; break; } // IACA override + if (/^[A-Z]{3}$/.test(t)) { + out.iata = t; + break; + } + if (/^[A-Z]{4}$/.test(t)) { + out.iata = t; + break; + } // IACA override } - - - const tTok = tokens.find(t => /^\d{6}Z$/.test(t)); + const tTok = tokens.find((t) => /^\d{6}Z$/.test(t)); if (tTok) { - out.day = tTok.slice(0,2); - out.hh = tTok.slice(2,4); - out.mm = tTok.slice(4,6); + out.day = tTok.slice(0, 2); + out.hh = tTok.slice(2, 4); + out.mm = tTok.slice(4, 6); } - const wTok = tokens.find(t => /^(?:\d{3}|VRB)\d{2,3}(G\d{2,3})?KT$/.test(t)); + const wTok = tokens.find((t) => + /^(?:\d{3}|VRB)\d{2,3}(G\d{2,3})?KT$/.test(t), + ); if (wTok) { const m = wTok.match(/^(\d{3}|VRB)(\d{2,3})(?:G(\d{2,3}))?KT$/); if (m) { - out.wind.dir_deg = m[1] === 'VRB' ? null : parseInt(m[1], 10); - out.wind.spd_kt = parseInt(m[2], 10); + out.wind.dir_deg = m[1] === "VRB" ? null : parseInt(m[1], 10); + out.wind.spd_kt = parseInt(m[2], 10); out.wind.gust_kt = m[3] ? parseInt(m[3], 10) : null; } } - const vTok = tokens.find(t => /^\d{3}V\d{3}$/.test(t)); + const vTok = tokens.find((t) => /^\d{3}V\d{3}$/.test(t)); if (vTok) { const m = vTok.match(/^(\d{3})V(\d{3})$/); out.wind.var_from = parseInt(m[1], 10); - out.wind.var_to = parseInt(m[2], 10); + out.wind.var_to = parseInt(m[2], 10); } - if (tokens.includes('CAVOK')) { - out.visibility.code = 'CAVOK'; + if (tokens.includes("CAVOK")) { + out.visibility.code = "CAVOK"; out.visibility.meters = 10000; } else { - const vis = tokens.find(t => /^\d{4}$/.test(t)); + const vis = tokens.find((t) => /^\d{4}$/.test(t)); if (vis) { out.visibility.code = vis; out.visibility.meters = parseInt(vis, 10); } } - const tdTok = tokens.find(t => /^(M?\d{1,2})\/(M?\d{1,2})$/.test(t)); + const tdTok = tokens.find((t) => /^(M?\d{1,2})\/(M?\d{1,2})$/.test(t)); if (tdTok) { const m = tdTok.match(/^(M?\d{1,2})\/(M?\d{1,2})$/); out.temp_c = _signed(m[1]); - out.dew_c = _signed(m[2]); + out.dew_c = _signed(m[2]); } - const qTok = tokens.find(t => /^(Q\d{4}|A\d{4})$/.test(t)); + const qTok = tokens.find((t) => /^(Q\d{4}|A\d{4})$/.test(t)); if (qTok) { - if (qTok.startsWith('Q')) out.qnh_hpa = parseInt(qTok.slice(1), 10); + if (qTok.startsWith("Q")) out.qnh_hpa = parseInt(qTok.slice(1), 10); else { const inHg = parseInt(qTok.slice(1), 10) / 100; out.qnh_hpa = Math.round(inHg * 33.8639); @@ -83,7 +96,7 @@ export function parseMetar(raw) { out.clouds.push({ cover: m[1], base_ft: parseInt(m[2], 10) * 100, - type: m[3] || null + type: m[3] || null, }); } } @@ -91,93 +104,117 @@ export function parseMetar(raw) { return out; } -function _signed(s) { return s?.startsWith('M') ? -parseInt(s.slice(1),10) : parseInt(s,10); } +function _signed(s) { + return s?.startsWith("M") ? -parseInt(s.slice(1), 10) : parseInt(s, 10); +} // ---------------- Flight Category ---------------- export function determineFlightCategory(parsed) { - const cavok = parsed.visibility.code === 'CAVOK'; + const cavok = parsed.visibility.code === "CAVOK"; let ceiling = null; for (const c of parsed.clouds || []) { - if (c.cover === 'BKN' || c.cover === 'OVC') { + if (c.cover === "BKN" || c.cover === "OVC") { if (ceiling === null || c.base_ft < ceiling) ceiling = c.base_ft; } } const v = parsed.visibility.meters ?? 0; - if (cavok) return 'VFR'; - if (v >= 5000 && (ceiling === null || ceiling > 3000)) return 'VFR'; - if ((v >= 3000 && v < 5000) || (ceiling !== null && ceiling >= 1000 && ceiling <= 3000)) return 'MVFR'; - if ((v >= 1500 && v < 3000) || (ceiling !== null && ceiling >= 500 && ceiling < 1000)) return 'IFR'; - return 'LIFR'; + if (cavok) return "VFR"; + if (v >= 5000 && (ceiling === null || ceiling > 3000)) return "VFR"; + if ( + (v >= 3000 && v < 5000) || + (ceiling !== null && ceiling >= 1000 && ceiling <= 3000) + ) + return "MVFR"; + if ( + (v >= 1500 && v < 3000) || + (ceiling !== null && ceiling >= 500 && ceiling < 1000) + ) + return "IFR"; + return "LIFR"; } export function categoryClass(cat) { switch (cat) { - case 'VFR': return 'text-bg-success'; - case 'MVFR': return 'text-bg-primary'; - case 'IFR': return 'text-bg-danger'; - case 'LIFR': return 'text-bg-magenta'; - default: return 'text-bg-secondary'; + case "VFR": + return "text-bg-success"; + case "MVFR": + return "text-bg-primary"; + case "IFR": + return "text-bg-danger"; + case "LIFR": + return "text-bg-magenta"; + default: + return "text-bg-secondary"; } } // ---------------- Formatters (labels shown in Vue) ---------------- export function windToString(w) { - if (!w) return '—'; - const base = (w.dir_deg == null) - ? `VRB/${w.spd_kt ?? 0}kt` - : `${w.dir_deg}°/${w.spd_kt ?? 0}kt`; - const gust = w.gust_kt ? ` G${w.gust_kt}` : ''; - const varrng = (w.var_from != null && w.var_to != null) ? ` V${w.var_from}-${w.var_to}` : ''; + if (!w) return "—"; + const base = + w.dir_deg == null + ? `VRB/${w.spd_kt ?? 0}kt` + : `${w.dir_deg}°/${w.spd_kt ?? 0}kt`; + const gust = w.gust_kt ? ` G${w.gust_kt}` : ""; + const varrng = + w.var_from != null && w.var_to != null ? ` V${w.var_from}-${w.var_to}` : ""; return base + gust + varrng; } export function visibilityToString(v) { - return v?.code || '—'; + return v?.code || "—"; } export function cloudsToString(clouds) { - if (!clouds || clouds.length === 0) return '—'; - return clouds.map(c => `${c.cover}${String(Math.round(c.base_ft/100)).padStart(3,'0')}${c.type || ''}`).join(' '); + if (!clouds || clouds.length === 0) return "—"; + return clouds + .map( + (c) => + `${c.cover}${String(Math.round(c.base_ft / 100)).padStart(3, "0")}${c.type || ""}`, + ) + .join(" "); } export function tempDewToString(t, d) { - const tStr = (typeof t === 'number') ? `${t}` : '—'; - const dStr = (typeof d === 'number') ? `${d}` : '—'; + const tStr = typeof t === "number" ? `${t}` : "—"; + const dStr = typeof d === "number" ? `${d}` : "—"; return `${tStr}/${dStr}°C`; } export function qnhToString(q) { - return (typeof q === 'number') ? `${q} hPa` : '—'; + return typeof q === "number" ? `${q} hPa` : "—"; } export function timeToString(day, hh, mm) { - return (day && hh && mm) ? `${day}.${hh}:${mm}Z` : '—'; + return day && hh && mm ? `${day}.${hh}:${mm}Z` : "—"; } // ---------------- SVG Compass + Windbarb (WMO-like) ---------------- export function buildCompassSVG(wind, opts = {}) { const size = opts.size ?? 160; - const cx = size/2, cy = size/2; + const cx = size / 2, + cy = size / 2; const dirDeg = wind?.dir_deg; const spd = wind?.spd_kt ?? 0; const gust = wind?.gust_kt; - const showDir = (dirDeg == null) ? null : (dirDeg + 180) % 360; - const transform = (showDir == null) ? '' : `transform="rotate(${showDir} ${cx} ${cy})"`; + const showDir = dirDeg == null ? null : (dirDeg + 180) % 360; + const transform = + showDir == null ? "" : `transform="rotate(${showDir} ${cx} ${cy})"`; const svgGrid = ` - - - - - - N - E - S - W + + + + + + N + E + S + W `; let windGroup = ``; @@ -186,9 +223,10 @@ export function buildCompassSVG(wind, opts = {}) { windGroup = `${_wmoBarb(cx, cy, spd, size)}`; } - const gustRing = (gust && gust > spd) - ? `` - : ''; + const gustRing = + gust && gust > spd + ? `` + : ""; return ` @@ -199,32 +237,42 @@ export function buildCompassSVG(wind, opts = {}) { } function _wmoBarb(cx, cy, spdKt, size) { - const stroke = '#0d6efd'; + const stroke = "#0d6efd"; const lw = 3; const staffLen = size * 0.45; const step = Math.max(6, size * 0.05); - const barbLong = Math.max(12, size * 0.10); - const barbHalf = barbLong * 0.6; + const barbLong = Math.max(12, size * 0.1); + const barbHalf = barbLong * 0.6; let s = Math.round((spdKt || 0) / 5) * 5; - const tri = Math.floor(s / 50); s -= tri * 50; - const ten = Math.floor(s / 10); s -= ten * 10; + const tri = Math.floor(s / 50); + s -= tri * 50; + const ten = Math.floor(s / 10); + s -= ten * 10; const five = Math.floor(s / 5); const elems = []; - elems.push(``); + elems.push( + ``, + ); - let y = cy - staffLen + step*1.2; + let y = cy - staffLen + step * 1.2; for (let i = 0; i < tri; i++) { - elems.push(``); + elems.push( + ``, + ); y += step; } for (let i = 0; i < ten; i++) { - elems.push(``); + elems.push( + ``, + ); y += step; } if (five) { - elems.push(``); + elems.push( + ``, + ); } - return elems.join('\n'); + return elems.join("\n"); } diff --git a/freedata_gui/src/store/audioStore.js b/freedata_gui/src/store/audioStore.js index 6a94f80e..8ca43daa 100644 --- a/freedata_gui/src/store/audioStore.js +++ b/freedata_gui/src/store/audioStore.js @@ -53,11 +53,6 @@ export const useAudioStore = defineStore("audioStore", () => { } } - - - - - const loadAudioDevices = async () => { try { const devices = await getAudioDevices(); @@ -83,7 +78,6 @@ export const useAudioStore = defineStore("audioStore", () => { resetBuffer, get bufferedBlockCount() { return readyBlocks; - }, - + }, }; }); diff --git a/freedata_gui/src/store/broadcastStore.js b/freedata_gui/src/store/broadcastStore.js index ddb8927b..301fb14c 100644 --- a/freedata_gui/src/store/broadcastStore.js +++ b/freedata_gui/src/store/broadcastStore.js @@ -1,9 +1,7 @@ import { defineStore } from "pinia"; import { ref } from "vue"; - export const useBroadcastStore = defineStore("broadcastStore", () => { - // Indicator if we are loading data var loading = ref(false); @@ -29,21 +27,20 @@ export const useBroadcastStore = defineStore("broadcastStore", () => { const newPriority = ref(""); // selectedMessage - const selectedMessage = ref() + const selectedMessage = ref(); // unread message counter var totalUnreadMessages = ref(0); - function triggerScrollToBottom() { scrollTrigger.value++; } - function setDomains(data){ + function setDomains(data) { domains.value = data; } - function setBroadcastsForDomain(data){ + function setBroadcastsForDomain(data) { domainBroadcasts.value = data; } @@ -61,6 +58,6 @@ export const useBroadcastStore = defineStore("broadcastStore", () => { newPriority, newMessageType, selectedMessage, - totalUnreadMessages + totalUnreadMessages, }; -}) +}); diff --git a/freedata_gui/src/store/settingsStore.js b/freedata_gui/src/store/settingsStore.js index a89e0b68..a3ee0398 100644 --- a/freedata_gui/src/store/settingsStore.js +++ b/freedata_gui/src/store/settingsStore.js @@ -96,7 +96,7 @@ const defaultConfig = { EXP: { enable_ring_buffer: false, enable_vhf: false, - enable_groupchat: false + enable_groupchat: false, }, }, };