radio: Opa's update - Haagse ronde on scanner 1-3, QO-100 net (weekend reminder), antenna tip
Deploy production / deploy (push) Successful in 1m24s
Deploy production / deploy (push) Successful in 1m24s
- data (scripts/radio-2026-10-27-opa.sql): 145.450 in bank 1 ch 3 (ISS APRS UHF moved to ch 7), NL/EN notes for the Haagse ronde (scanner, not the WebSDR: trolls), new QO-100 satellietronde (daily 09:30, 10489.700 USB) with its reminder on Sat/Sun only - migration: radio.nets.remind_days (null = every day of the net); scripts/radio.ts honours it, and `upcoming [hours]` prints the day too - links: BATC/Goonhilly QO-100 WebSDR (via Opa, 27 Oct 2026); PI1DFT note about trolls - Radioweetjes: Haagse ronde on the scanner, QO-100 net, magnetic antenna on a radiator
This commit is contained in:
1 parent
2c8d1bd881
commit
c175c25615
7 files changed
+45930
-12
No files matched your search
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "radio"."nets" ADD COLUMN "remind_days" smallint[];--> statement-breakpoint
|
||||
ALTER TABLE "radio"."nets" ADD CONSTRAINT "nets_remind_days_check" CHECK ("remind_days" <@ '{1,2,3,4,5,6,7}'::smallint[]);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,49 @@
|
||||
-- One-off data update from Opa (27 Oct 2026). Idempotent; run with psql -v ON_ERROR_STOP=1.
|
||||
BEGIN;
|
||||
|
||||
-- 1) Haagse ronde in scanner bank 1, channel 3 (Opa's own layout). The ISS APRS UHF memory that
|
||||
-- sat there moves to the first free bank-1 channel (7).
|
||||
UPDATE radio.channels SET channel = 7, updated_at = now()
|
||||
WHERE channel = 3
|
||||
AND frequency_id <> (SELECT id FROM radio.frequencies WHERE freq_hz = 145450000 AND name = 'Haagse ronde')
|
||||
AND NOT EXISTS (SELECT 1 FROM radio.channels WHERE channel = 7);
|
||||
INSERT INTO radio.channels (channel, frequency_id, alpha_tag)
|
||||
SELECT 3, id, 'HAAGSE RONDE' FROM radio.frequencies WHERE freq_hz = 145450000 AND name = 'Haagse ronde'
|
||||
ON CONFLICT (channel) DO UPDATE SET frequency_id = excluded.frequency_id, alpha_tag = excluded.alpha_tag, updated_at = now();
|
||||
|
||||
UPDATE radio.frequencies SET notes =
|
||||
'Haagse 2m-ronde, werkdagen 11:00. Luister op de scanner, bank 1 kanaal 3. Opa raadt de WebSDR voor deze ronde niet meer aan: trollen verstoren hem daar. / '
|
||||
|| 'The Hague 2 m net, weekdays 11:00. Listen on the scanner, bank 1 channel 3. Opa no longer recommends the WebSDR for this net: trolls disrupt it there.',
|
||||
updated_at = now()
|
||||
WHERE freq_hz = 145450000 AND name = 'Haagse ronde';
|
||||
|
||||
UPDATE radio.nets SET notes =
|
||||
'Luister op de scanner, bank 1 kanaal 3. Opa raadt de WebSDR voor deze ronde niet meer aan: trollen verstoren hem daar. Opa corrigeerde 145.350 naar 145.450. / '
|
||||
|| 'Listen on the scanner, bank 1 channel 3. Opa no longer recommends the WebSDR for this net: trolls disrupt it there. Opa corrected 145.350 to 145.450.',
|
||||
updated_at = now()
|
||||
WHERE name = 'Haagse ronde';
|
||||
|
||||
-- 2) QO-100 satellite net (Dutch hams), daily 09:30 Europe/Amsterdam. Inserted with the reminder
|
||||
-- off; section 3 turns it on for weekends only once the remind_days migration is in.
|
||||
INSERT INTO radio.frequencies (freq_hz, mode, band, name, location, notes, tags, source)
|
||||
VALUES (10489700000, 'USB', 'ham', 'QO-100 satellietronde', 'Satelliet QO-100 (Es''hail-2), smalband-transponder',
|
||||
'Nederlandse zendamateurs, dagelijks 09:30. Niet met de handscanner: luister via de BATC/Goonhilly QO-100 WebSDR https://eshail.batc.org.uk/nb/ / '
|
||||
|| 'Dutch hams, daily 09:30. Not on the handheld scanner: listen on the BATC/Goonhilly QO-100 WebSDR.',
|
||||
'{satellite,qo-100,websdr}', 'Opa, 27 Oct 2026')
|
||||
ON CONFLICT (freq_hz, name) DO NOTHING;
|
||||
|
||||
INSERT INTO radio.nets (name, frequency_id, days, times, kind, notes, remind, active, sort_order)
|
||||
SELECT 'QO-100 satellietronde', f.id, '{1,2,3,4,5,6,7}', '{09:30}', 'net',
|
||||
'Nederlandse zendamateurs via satelliet QO-100. Luister via de QO-100 WebSDR van BATC/Goonhilly: https://eshail.batc.org.uk/nb/ / '
|
||||
|| 'Dutch hams via the QO-100 satellite. Listen on the BATC/Goonhilly QO-100 WebSDR.',
|
||||
false, true, 15
|
||||
FROM radio.frequencies f
|
||||
WHERE f.freq_hz = 10489700000 AND f.name = 'QO-100 satellietronde'
|
||||
AND NOT EXISTS (SELECT 1 FROM radio.nets WHERE name = 'QO-100 satellietronde');
|
||||
|
||||
-- 3) Stan approved the QO-100 reminder for Saturday and Sunday only (ping 09:20). Needs the
|
||||
-- 20261027103329_radio_net_remind_days migration.
|
||||
UPDATE radio.nets SET remind = true, remind_days = '{6,7}', updated_at = now()
|
||||
WHERE name = 'QO-100 satellietronde';
|
||||
|
||||
COMMIT;
|
||||
+13
-5
@@ -5,7 +5,7 @@
|
||||
* passes over Gouda into radio.iss_passes (daily timer)
|
||||
* bun scripts/radio.ts remind ntfy reminder 10 minutes before each net (remind = true) and
|
||||
* each ISS pass with max elevation >= 10 deg (every-minute timer)
|
||||
* bun scripts/radio.ts upcoming print what the next 24 h of reminders would be (no sending)
|
||||
* bun scripts/radio.ts upcoming [hours] print the reminders due in the next 24 h (no sending)
|
||||
*
|
||||
* Reminders go to RADIO_NTFY_URL (an ntfy topic URL, optional RADIO_NTFY_TOKEN), passed in with
|
||||
* `docker exec --env-file ~/.config/radio.env`. radio.reminders makes every reminder fire once.
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
QUIET_HOURS,
|
||||
REMIND_MINUTES,
|
||||
compass,
|
||||
fmtDay,
|
||||
fmtTime,
|
||||
formatFreq,
|
||||
inQuiet,
|
||||
@@ -197,15 +198,21 @@ type Due = {
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
/** The net's days that get a reminder: remind_days (when set) narrows the schedule. */
|
||||
function remindDays(days: unknown[], only: unknown[] | null): number[] {
|
||||
const all = days.map(Number);
|
||||
return only ? all.filter((d) => only.map(Number).includes(d)) : all;
|
||||
}
|
||||
|
||||
async function due(from: Date, to: Date): Promise<Due[]> {
|
||||
const out: Due[] = [];
|
||||
const nets = await sql`SELECT n.id, n.name, n.days, n.times, n.active, n.notes,
|
||||
const nets = await sql`SELECT n.id, n.name, n.days, n.remind_days, n.times, n.active, n.notes,
|
||||
f.freq_hz::float8 AS hz, f.mode, f.marine_ch
|
||||
FROM radio.nets n LEFT JOIN radio.frequencies f ON f.id = n.frequency_id
|
||||
WHERE n.remind AND n.active`;
|
||||
for (const n of nets) {
|
||||
for (const at of occurrences(
|
||||
{ id: n.id, days: n.days.map(Number), times: n.times },
|
||||
{ id: n.id, days: remindDays(n.days, n.remind_days), times: n.times },
|
||||
from,
|
||||
to
|
||||
)) {
|
||||
@@ -264,10 +271,11 @@ try {
|
||||
else if (mode === 'remind') await remind();
|
||||
else if (mode === 'upcoming') {
|
||||
const now = new Date();
|
||||
const hours = Number(process.argv[3] ?? 24);
|
||||
console.log(`quiet hours: ${QUIET?.label ?? 'off'} (Europe/Amsterdam, by send time)`);
|
||||
for (const r of await due(now, new Date(now.getTime() + 86_400_000)))
|
||||
for (const r of await due(now, new Date(now.getTime() + hours * 3_600_000)))
|
||||
console.log(
|
||||
`${quiet(r.at) ? 'QUIET-SKIP' : 'SEND '} at ${fmtTime(sendAt(r.at))} ${r.kind}: ${r.title}`
|
||||
`${quiet(r.at) ? 'QUIET-SKIP' : 'SEND '} at ${fmtDay(sendAt(r.at))} ${fmtTime(sendAt(r.at))} ${r.kind}: ${r.title}`
|
||||
);
|
||||
} else throw new Error(`unknown mode ${mode} (iss | remind | upcoming)`);
|
||||
} finally {
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
archive: 'archief',
|
||||
viaEmail: 'via Opa, e-mail',
|
||||
viaWhatsapp: 'via Opa, WhatsApp',
|
||||
viaOpa: 'via Opa',
|
||||
added: 'zelf toegevoegd',
|
||||
checked: (d: string) => `Links gecontroleerd op ${d}.`,
|
||||
nLinks: (n: number) => `${n} links`,
|
||||
@@ -124,9 +125,17 @@
|
||||
],
|
||||
[
|
||||
'Haagse ronde',
|
||||
'145.450 MHz NFM, ma–vr 11:00. Via de WebSDR van PI1DFT: zet 145.000 en schuif de marker. Opa: \u201cop elke streep of draadgolf die je ziet is iemand aan het praten.\u201d'
|
||||
'145.450 MHz NFM, ma–vr 11:00. Luister op de scanner: bank 1, kanaal 3. Opa raadt de WebSDR voor deze ronde niet meer aan, want daar verstoren trollen hem.'
|
||||
],
|
||||
['KLM-ronde', '3785 kHz LSB (80 m), te horen via WebSDR Maasbree.'],
|
||||
[
|
||||
'QO-100-ronde',
|
||||
'Dagelijks 09:30 op 10489.700 MHz (USB), via satelliet QO-100. Te beluisteren via de QO-100 WebSDR van BATC/Goonhilly (eshail.batc.org.uk).'
|
||||
],
|
||||
[
|
||||
'Magneetantenne',
|
||||
'Zet de magneetantenne op een radiator of kachel: het metaal werkt als grondvlak (ground plane) en geeft betere ontvangst.'
|
||||
],
|
||||
['ISS', '145.800 MHz NFM spraak-downlink. Zie live.ariss.org voor schoolcontacten.'],
|
||||
[
|
||||
'ADS-B',
|
||||
@@ -205,6 +214,7 @@
|
||||
archive: 'archive',
|
||||
viaEmail: 'via Opa, email',
|
||||
viaWhatsapp: 'via Opa, WhatsApp',
|
||||
viaOpa: 'via Opa',
|
||||
added: 'added by us',
|
||||
checked: (d: string) => `Links checked on ${d}.`,
|
||||
nLinks: (n: number) => `${n} links`,
|
||||
@@ -219,9 +229,17 @@
|
||||
],
|
||||
[
|
||||
'Hague net',
|
||||
'145.450 MHz NFM, Mon–Fri 11:00. On the PI1DFT WebSDR: set 145.000 and move the marker. Opa: every line or wave you see is someone talking.'
|
||||
'145.450 MHz NFM, Mon–Fri 11:00. Listen on the scanner: bank 1, channel 3. Opa no longer recommends the WebSDR for this net, because trolls disrupt it there.'
|
||||
],
|
||||
['KLM net', '3785 kHz LSB (80 m), heard via WebSDR Maasbree.'],
|
||||
[
|
||||
'QO-100 net',
|
||||
'Daily 09:30 on 10489.700 MHz (USB), via the QO-100 satellite. Listen on the BATC/Goonhilly QO-100 WebSDR (eshail.batc.org.uk).'
|
||||
],
|
||||
[
|
||||
'Magnetic antenna',
|
||||
'Put the magnetic antenna on a radiator or heater: the metal acts as a ground plane and gives better reception.'
|
||||
],
|
||||
['ISS', '145.800 MHz NFM voice downlink. See live.ariss.org for school contacts.'],
|
||||
[
|
||||
'ADS-B',
|
||||
@@ -351,7 +369,7 @@
|
||||
const via = (l: RadioLink) =>
|
||||
l.via === 'added'
|
||||
? c.added
|
||||
: `${l.via === 'email' ? c.viaEmail : c.viaWhatsapp}${l.sent ? `, ${dateFmt(l.sent)}` : ''}`;
|
||||
: `${l.via === 'email' ? c.viaEmail : l.via === 'opa' ? c.viaOpa : c.viaWhatsapp}${l.sent ? `, ${dateFmt(l.sent)}` : ''}`;
|
||||
const external = (url: string) => /^https?:\/\//.test(url);
|
||||
/** Split text into plain parts and http(s) links, so URLs in notes are clickable. */
|
||||
const parts = (text: string) =>
|
||||
|
||||
+19
-4
@@ -9,8 +9,8 @@ export type RadioLink = {
|
||||
/** An http(s) URL, or "#anchor" for a section on this page. */
|
||||
url: string;
|
||||
about: Text;
|
||||
/** How it reached us: Opa by email or WhatsApp, or a supporting link we added. */
|
||||
via: 'email' | 'whatsapp' | 'added';
|
||||
/** How it reached us: Opa by email or WhatsApp, from Opa (channel not noted), or a link we added. */
|
||||
via: 'email' | 'whatsapp' | 'opa' | 'added';
|
||||
/** Date Opa sent it (YYYY-MM-DD), when known. */
|
||||
sent?: string;
|
||||
status: LinkStatus;
|
||||
@@ -19,7 +19,7 @@ export type RadioLink = {
|
||||
};
|
||||
export type LinkCategory = { id: string; title: Text; links: RadioLink[] };
|
||||
|
||||
export const LINKS_CHECKED = '2026-10-24';
|
||||
export const LINKS_CHECKED = '2026-10-27';
|
||||
|
||||
export const LINK_CATEGORIES: LinkCategory[] = [
|
||||
{
|
||||
@@ -69,7 +69,22 @@ export const LINK_CATEGORIES: LinkCategory[] = [
|
||||
},
|
||||
via: 'email',
|
||||
status: 'online',
|
||||
sent: '2025-03-07'
|
||||
sent: '2025-03-07',
|
||||
note: {
|
||||
nl: 'Opa raadt deze WebSDR niet meer aan voor de Haagse ronde: trollen verstoren hem daar. Luister op de scanner, bank 1 kanaal 3.',
|
||||
en: 'Opa no longer recommends this WebSDR for the Hague net: trolls disrupt it there. Listen on the scanner, bank 1 channel 3.'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'QO-100 WebSDR (BATC / Goonhilly)',
|
||||
url: 'https://eshail.batc.org.uk/nb/',
|
||||
about: {
|
||||
nl: 'Smalband-transponder van satelliet QO-100 (Es\u2019hail-2), voor de QO-100 satellietronde op 10489.700 MHz',
|
||||
en: 'Narrowband transponder of the QO-100 (Es\u2019hail-2) satellite, for the QO-100 satellite net on 10489.700 MHz'
|
||||
},
|
||||
via: 'opa',
|
||||
status: 'online',
|
||||
sent: '2026-10-27'
|
||||
},
|
||||
{
|
||||
title: 'WebSDR Utrecht (PI4UTR)',
|
||||
|
||||
@@ -96,6 +96,8 @@ export const radioNets = radioSchema.table(
|
||||
notes: text('notes'),
|
||||
/** ntfy reminder 10 minutes before each start. */
|
||||
remind: boolean('remind').notNull().default(true),
|
||||
/** ISO weekdays that get the reminder; null = every day of the net. */
|
||||
remindDays: smallint('remind_days').array(),
|
||||
active: boolean('active').notNull().default(true),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
...timestamps
|
||||
@@ -103,6 +105,7 @@ export const radioNets = radioSchema.table(
|
||||
(t) => [
|
||||
check('nets_kind_check', sql`${t.kind} IN ('net','broadcast','other')`),
|
||||
check('nets_days_check', sql`${t.days} <@ '{1,2,3,4,5,6,7}'::smallint[]`),
|
||||
check('nets_remind_days_check', sql`${t.remindDays} <@ '{1,2,3,4,5,6,7}'::smallint[]`),
|
||||
check(
|
||||
'nets_times_check',
|
||||
sql`array_to_string(${t.times}, ',') ~ '^(([01][0-9]|2[0-3]):[0-5][0-9](,|$))*$'`
|
||||
|
||||
Reference in new issue
Block a user