From 03155aff0afaf63ccd60580299e42b39fc2340fc Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 08:59:09 +0100 Subject: [PATCH 1/8] fix: preserve rig-config.json during updates - Created rig-config.json.example as tracked template - Added rig-config.json to .gitignore to prevent tracking user customizations - Updated update.sh to backup and restore rig-config.json during updates - Updated rig-daemon.js to auto-create config from example on first run - Updated README.md and UserGuide.md to document the new system User customizations to rig-config.json will now be preserved across updates. --- .gitignore | 3 +++ rig-control/README.md | 4 +++- rig-control/UserGuide.md | 2 +- rig-control/rig-config.json.example | 24 ++++++++++++++++++++++++ rig-control/rig-daemon.js | 12 ++++++++++++ scripts/update.sh | 16 ++++++++++++++++ 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 rig-control/rig-config.json.example diff --git a/.gitignore b/.gitignore index 253ea02b..fba7c559 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,9 @@ config.local.js config.local.json config.json +# Rig control daemon config (user-specific settings) +rig-control/rig-config.json + # Test files *.test.js.snap diff --git a/rig-control/README.md b/rig-control/README.md index 69381b77..fd2f8edd 100644 --- a/rig-control/README.md +++ b/rig-control/README.md @@ -27,7 +27,7 @@ npm install ## Configuration -Configuration is loaded from `rig-config.json`. A default file is provided: +Configuration is loaded from `rig-config.json`. On first run, this file is automatically created from `rig-config.json.example`: ```json { @@ -46,6 +46,8 @@ Configuration is loaded from `rig-config.json`. A default file is provided: } ``` +**Important:** Your `rig-config.json` customizations are preserved during updates. The file is excluded from git tracking, so your local changes won't be overwritten when pulling new versions. + ### Configuration Options - **server.host**: IP to bind to (default 0.0.0.0) diff --git a/rig-control/UserGuide.md b/rig-control/UserGuide.md index 6ab9dc93..45be1261 100644 --- a/rig-control/UserGuide.md +++ b/rig-control/UserGuide.md @@ -67,7 +67,7 @@ The "Rig Control Bridge" (daemon) is a separate small program that sits between Tell the bridge which radio software you use. -1. Find `rig-config.json` in the `rig-control` folder. +1. Find `rig-config.json` in the `rig-control` folder. If it doesn't exist, it will be created automatically from `rig-config.json.example` when you first start the daemon. 2. Edit it with any text editor. ### If using FLRIG (Easiest) diff --git a/rig-control/rig-config.json.example b/rig-control/rig-config.json.example new file mode 100644 index 00000000..73d6506b --- /dev/null +++ b/rig-control/rig-config.json.example @@ -0,0 +1,24 @@ +{ + "server": { + "host": "0.0.0.0", + "port": 5555, + "cors": "*" + }, + "radio": { + "type": "flrig", + "host": "127.0.0.1", + "port": 12345, + "pollInterval": 1000, + "timeout": 5000, + "tuneDelay": 3000, + "pttEnabled": false, + "_comment": "Set type to 'rigctld' (TCP), 'flrig' (XML-RPC), or 'mock' (Simulation)" + }, + "rotator": { + "enabled": false, + "type": "rotctld", + "host": "127.0.0.1", + "port": 4533, + "pollInterval": 1000 + } +} diff --git a/rig-control/rig-daemon.js b/rig-control/rig-daemon.js index 78498707..f2f7f1a8 100644 --- a/rig-control/rig-daemon.js +++ b/rig-control/rig-daemon.js @@ -32,6 +32,18 @@ let CONFIG = { // Load Config File const configPath = path.join(__dirname, "rig-config.json"); +const exampleConfigPath = path.join(__dirname, "rig-config.json.example"); + +// Auto-create config from example if it doesn't exist +if (!fs.existsSync(configPath) && fs.existsSync(exampleConfigPath)) { + try { + fs.copyFileSync(exampleConfigPath, configPath); + console.log(`[Config] Created ${configPath} from example template`); + } catch (e) { + console.warn(`[Config] Could not create config from example: ${e.message}`); + } +} + if (fs.existsSync(configPath)) { try { const fileConfig = JSON.parse(fs.readFileSync(configPath, "utf8")); diff --git a/scripts/update.sh b/scripts/update.sh index 937ed3ae..9d9bd18c 100644 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -140,6 +140,12 @@ if [ -f "config.json" ]; then echo " ✓ config.json → config.json.backup" fi +# Backup rig control daemon config +if [ -f "rig-control/rig-config.json" ]; then + cp rig-control/rig-config.json rig-control/rig-config.json.backup + echo " ✓ rig-control/rig-config.json → rig-config.json.backup" +fi + echo "" echo "⬇️ Pulling latest changes..." @@ -208,6 +214,16 @@ if [ -f "config.json.backup" ] && [ ! -f "config.json" ]; then echo " ✓ config.json restored from backup" fi +# Restore rig control daemon config +if [ -f "rig-control/rig-config.json.backup" ]; then + cp rig-control/rig-config.json.backup rig-control/rig-config.json + echo " ✓ rig-control/rig-config.json restored from backup" +elif [ ! -f "rig-control/rig-config.json" ] && [ -f "rig-control/rig-config.json.example" ]; then + # First-time setup: copy example to actual config + cp rig-control/rig-config.json.example rig-control/rig-config.json + echo " ✓ rig-control/rig-config.json created from example template" +fi + # Patch kiosk.sh if present — fix --incognito flag that wipes localStorage on reboot if [ -f "kiosk.sh" ]; then if grep -q "\-\-incognito" kiosk.sh; then From edbd73af9849626295b7bf081b8b58bb61e2c199 Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 09:20:46 +0100 Subject: [PATCH 2/8] refactor: standardize rig control across all panels - Refactored POTAPanel to use callback pattern instead of direct useRig hook - Added rig control to WWFFPanel (new feature) - Updated PotaSotaPanel to pass onSpotClick callbacks to child panels - Added handleParkSpotClick functions to ModernLayout and ClassicLayout - Updated DockableApp to pass onSpotClick to POTA and WWFF panels All panels now use consistent callback pattern for better maintainability and separation of concerns. Layouts handle frequency conversion and mode detection centrally. --- src/DockableApp.jsx | 4 +++- src/components/POTAPanel.jsx | 14 ++------------ src/components/PotaSotaPanel.jsx | 20 +++++++++++++------- src/components/WWFFPanel.jsx | 19 +++++++++++-------- src/layouts/ClassicLayout.jsx | 18 ++++++++++++++++++ src/layouts/ModernLayout.jsx | 21 +++++++++++++++++++++ 6 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/DockableApp.jsx b/src/DockableApp.jsx index 81395773..c916a8ff 100644 --- a/src/DockableApp.jsx +++ b/src/DockableApp.jsx @@ -561,10 +561,11 @@ export const DockableApp = ({ showLabelsOnMap={mapLayersEff.showPOTALabels} onToggleLabelsOnMap={togglePOTALabelsEff} + onSpotClick={handleSpotClick} /> ); break; - + case 'wwff': content = ( ); break; diff --git a/src/components/POTAPanel.jsx b/src/components/POTAPanel.jsx index 04d66c6f..c056f89f 100644 --- a/src/components/POTAPanel.jsx +++ b/src/components/POTAPanel.jsx @@ -3,8 +3,6 @@ * Displays Parks on the Air activations with ON/OFF toggle */ import React from 'react'; -import { detectMode } from '../utils/callsign.js'; -import { useRig } from '../contexts/RigContext.jsx'; import CallsignLink from './CallsignLink.jsx'; export const POTAPanel = ({ @@ -14,8 +12,8 @@ export const POTAPanel = ({ onToggleMap, showLabelsOnMap = true, onToggleLabelsOnMap, + onSpotClick, }) => { - const { tuneTo, tuneEnabled } = useRig(); return (
{ - if (spot.freq) { - const freqVal = parseFloat(spot.freq); - let freqHz = freqVal; - if (freqVal < 1000) freqHz = freqVal * 1000000; - else if (freqVal < 100000) freqHz = freqVal * 1000; - - const mode = spot.mode || detectMode(spot.locationDesc || spot.comment || ''); - tuneTo(freqHz, mode); - } + onSpotClick?.(spot); }} > diff --git a/src/components/PotaSotaPanel.jsx b/src/components/PotaSotaPanel.jsx index 6a830ba2..90aa9f7f 100644 --- a/src/components/PotaSotaPanel.jsx +++ b/src/components/PotaSotaPanel.jsx @@ -13,7 +13,10 @@ const TABS = ['pota', 'sota']; export const PotaSotaPanel = ({ potaData, potaLoading, showPOTA, onTogglePOTA, wwffData, wwffLoading, showWWFF, onToggleWWFF, - sotaData, sotaLoading, showSOTA, onToggleSOTA + sotaData, sotaLoading, showSOTA, onToggleSOTA, + onPOTASpotClick, + onWWFFSpotClick, + onSOTASpotClick }) => { const [activeTab, setActiveTab] = useState(() => { try { @@ -24,7 +27,7 @@ export const PotaSotaPanel = ({ const handleTabChange = (tab) => { setActiveTab(tab); - try { localStorage.setItem('openhamclock_potaSotaTab', tab); } catch {} + try { localStorage.setItem('openhamclock_potaSotaTab', tab); } catch { } }; const tabStyle = (tab) => ({ @@ -32,10 +35,10 @@ export const PotaSotaPanel = ({ padding: '3px 0', background: activeTab === tab ? 'rgba(255, 255, 255, 0.08)' : 'transparent', border: 'none', - borderBottom: activeTab === tab - ? `2px solid ${tab === 'pota' ? '#44cc44' : '#ff9632'}` + borderBottom: activeTab === tab + ? `2px solid ${tab === 'pota' ? '#44cc44' : '#ff9632'}` : '2px solid transparent', - color: activeTab === tab + color: activeTab === tab ? (tab === 'pota' ? '#44cc44' : '#ff9632') : '#666', fontSize: '10px', @@ -48,8 +51,8 @@ export const PotaSotaPanel = ({ return (
{/* Tab bar */} -
@@ -72,6 +75,7 @@ export const PotaSotaPanel = ({ loading={potaLoading} showOnMap={showPOTA} onToggleMap={onTogglePOTA} + onSpotClick={onPOTASpotClick} /> ) : activeTab === 'sota' ? ( ) : ( )}
diff --git a/src/components/WWFFPanel.jsx b/src/components/WWFFPanel.jsx index 964ed215..9e9cbd16 100644 --- a/src/components/WWFFPanel.jsx +++ b/src/components/WWFFPanel.jsx @@ -12,13 +12,14 @@ export const WWFFPanel = ({ onToggleMap, showLabelsOnMap = true, onToggleLabelsOnMap, + onSpotClick, }) => { return (
-
@@ -61,7 +62,7 @@ export const WWFFPanel = ({ )}
- +
{loading ? (
@@ -70,14 +71,16 @@ export const WWFFPanel = ({ ) : data && data.length > 0 ? (
{data.map((spot, i) => ( -
onSpotClick?.(spot)} + style={{ display: 'grid', gridTemplateColumns: '62px 62px 58px 1fr', gap: '4px', padding: '3px 0', - borderBottom: i < data.length - 1 ? '1px solid var(--border-color)' : 'none' + borderBottom: i < data.length - 1 ? '1px solid var(--border-color)' : 'none', + cursor: 'pointer' }} > diff --git a/src/layouts/ClassicLayout.jsx b/src/layouts/ClassicLayout.jsx index 73c3b986..b3bcaf2e 100644 --- a/src/layouts/ClassicLayout.jsx +++ b/src/layouts/ClassicLayout.jsx @@ -5,6 +5,7 @@ import { DXNewsTicker, WorldMap } from '../components'; import { getBandColor } from '../utils'; import CallsignLink from '../components/CallsignLink.jsx'; import { useRig } from '../contexts/RigContext.jsx'; +import { detectMode } from '../utils/callsign.js'; export default function ClassicLayout(props) { const { @@ -54,6 +55,23 @@ export default function ClassicLayout(props) { const { tuneTo } = useRig(); + // Handler for POTA/WWFF/SOTA spot clicks + const handleParkSpotClick = (spot) => { + if (!spot?.freq) return; + + const freqVal = parseFloat(spot.freq); + let freqHz = freqVal; + + // Convert to Hz + if (freqVal < 1000) freqHz = freqVal * 1000000; // MHz to Hz + else if (freqVal < 100000) freqHz = freqVal * 1000; // kHz to Hz + + // Detect mode from spot data + const mode = spot.mode || detectMode(spot.locationDesc || spot.comment || ''); + + tuneTo(freqHz, mode); + }; + return config.layout === 'classic' ? (
{ + if (!spot?.freq) return; + + const freqVal = parseFloat(spot.freq); + let freqHz = freqVal; + + // Convert to Hz + if (freqVal < 1000) freqHz = freqVal * 1000000; // MHz to Hz + else if (freqVal < 100000) freqHz = freqVal * 1000; // kHz to Hz + + // Detect mode from spot data + const mode = spot.mode || detectMode(spot.locationDesc || spot.comment || ''); + + tuneTo(freqHz, mode); + }; + return (
)} From fb9e6f42b2be0941460ab3f398dec8f01fb36ff8 Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 09:43:53 +0100 Subject: [PATCH 3/8] fix: standardize frequency display formatting across all panels All park panels (POTA, WWFF, SOTA) now format frequencies consistently with DX Cluster using .toFixed(3) for 3 decimal places. This ensures frequencies like 144.3 MHz display as 144.300 for consistency. --- src/components/POTAPanel.jsx | 12 +++++++++++- src/components/SOTAPanel.jsx | 12 +++++++++++- src/components/WWFFPanel.jsx | 12 +++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/components/POTAPanel.jsx b/src/components/POTAPanel.jsx index c056f89f..d12b0240 100644 --- a/src/components/POTAPanel.jsx +++ b/src/components/POTAPanel.jsx @@ -92,7 +92,17 @@ export const POTAPanel = ({ {spot.locationDesc || spot.ref} - {spot.freq} + {(() => { + if (!spot.freq) return '?'; + const freqVal = parseFloat(spot.freq); + if (freqVal > 1000) { + // It's in kHz, convert to MHz + return (freqVal / 1000).toFixed(3); + } else { + // Already in MHz + return freqVal.toFixed(3); + } + })()} {spot.time} diff --git a/src/components/SOTAPanel.jsx b/src/components/SOTAPanel.jsx index 2fd016be..7115e6c2 100644 --- a/src/components/SOTAPanel.jsx +++ b/src/components/SOTAPanel.jsx @@ -65,7 +65,17 @@ export const SOTAPanel = ({ data, loading, showOnMap, onToggleMap, onSpotClick } {spot.ref} - {spot.freq} + {(() => { + if (!spot.freq) return '?'; + const freqVal = parseFloat(spot.freq); + if (freqVal > 1000) { + // It's in kHz, convert to MHz + return (freqVal / 1000).toFixed(3); + } else { + // Already in MHz + return freqVal.toFixed(3); + } + })()} {spot.time} diff --git a/src/components/WWFFPanel.jsx b/src/components/WWFFPanel.jsx index 9e9cbd16..ed6fc48a 100644 --- a/src/components/WWFFPanel.jsx +++ b/src/components/WWFFPanel.jsx @@ -90,7 +90,17 @@ export const WWFFPanel = ({ {spot.ref} - {spot.freq} + {(() => { + if (!spot.freq) return '?'; + const freqVal = parseFloat(spot.freq); + if (freqVal > 1000) { + // It's in kHz, convert to MHz + return (freqVal / 1000).toFixed(3); + } else { + // Already in MHz + return freqVal.toFixed(3); + } + })()} {spot.time} From ecf852320923db59f19b14f747a92b060e7c6766 Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 10:06:17 +0100 Subject: [PATCH 4/8] fix: correct high frequency handling in park panels Fixed bug where high frequencies (>100 MHz) were incorrectly converted in handleParkSpotClick. The manual conversion logic was flawed - it treated values like 144.300 MHz as kHz, resulting in 144300 Hz instead of 144300000 Hz. Solution: Pass the spot object directly to tuneTo() which already has robust frequency conversion logic that handles all formats correctly. This matches how DX Cluster handles spot clicks. Also removed unused detectMode import since tuneTo handles mode detection. --- src/layouts/ClassicLayout.jsx | 16 ++-------------- src/layouts/ModernLayout.jsx | 16 ++-------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/layouts/ClassicLayout.jsx b/src/layouts/ClassicLayout.jsx index b3bcaf2e..b27eeb4d 100644 --- a/src/layouts/ClassicLayout.jsx +++ b/src/layouts/ClassicLayout.jsx @@ -5,7 +5,6 @@ import { DXNewsTicker, WorldMap } from '../components'; import { getBandColor } from '../utils'; import CallsignLink from '../components/CallsignLink.jsx'; import { useRig } from '../contexts/RigContext.jsx'; -import { detectMode } from '../utils/callsign.js'; export default function ClassicLayout(props) { const { @@ -57,19 +56,8 @@ export default function ClassicLayout(props) { // Handler for POTA/WWFF/SOTA spot clicks const handleParkSpotClick = (spot) => { - if (!spot?.freq) return; - - const freqVal = parseFloat(spot.freq); - let freqHz = freqVal; - - // Convert to Hz - if (freqVal < 1000) freqHz = freqVal * 1000000; // MHz to Hz - else if (freqVal < 100000) freqHz = freqVal * 1000; // kHz to Hz - - // Detect mode from spot data - const mode = spot.mode || detectMode(spot.locationDesc || spot.comment || ''); - - tuneTo(freqHz, mode); + // tuneTo() in RigContext handles spot objects and all frequency conversions + tuneTo(spot); }; return config.layout === 'classic' ? ( diff --git a/src/layouts/ModernLayout.jsx b/src/layouts/ModernLayout.jsx index 4502db88..53d3d690 100644 --- a/src/layouts/ModernLayout.jsx +++ b/src/layouts/ModernLayout.jsx @@ -15,7 +15,6 @@ import { AnalogClockPanel } from '../components'; import { useRig } from '../contexts/RigContext.jsx'; -import { detectMode } from '../utils/callsign.js'; export default function ModernLayout(props) { const { @@ -91,19 +90,8 @@ export default function ModernLayout(props) { // Handler for POTA/WWFF/SOTA spot clicks const handleParkSpotClick = (spot) => { - if (!spot?.freq) return; - - const freqVal = parseFloat(spot.freq); - let freqHz = freqVal; - - // Convert to Hz - if (freqVal < 1000) freqHz = freqVal * 1000000; // MHz to Hz - else if (freqVal < 100000) freqHz = freqVal * 1000; // kHz to Hz - - // Detect mode from spot data - const mode = spot.mode || detectMode(spot.locationDesc || spot.comment || ''); - - tuneTo(freqHz, mode); + // tuneTo() in RigContext handles spot objects and all frequency conversions + tuneTo(spot); }; return ( From 36590b4e0ca3ade3b3cdcfc32233a2d9e1912a07 Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 10:42:46 +0100 Subject: [PATCH 5/8] fix: convert POTA/WWFF frequencies from kHz to MHz --- src/hooks/usePOTASpots.js | 52 ++++++++++++++++++++++----------------- src/hooks/useWWFFSpots.js | 31 +++++++++++++---------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/hooks/usePOTASpots.js b/src/hooks/usePOTASpots.js index 3a2ec7bd..1ef10a3b 100644 --- a/src/hooks/usePOTASpots.js +++ b/src/hooks/usePOTASpots.js @@ -9,23 +9,23 @@ import { apiFetch } from '../utils/apiFetch'; // Convert grid square to lat/lon function gridToLatLon(grid) { if (!grid || grid.length < 4) return null; - + const g = grid.toUpperCase(); const lon = (g.charCodeAt(0) - 65) * 20 - 180; const lat = (g.charCodeAt(1) - 65) * 10 - 90; const lonMin = parseInt(g[2]) * 2; const latMin = parseInt(g[3]) * 1; - + let finalLon = lon + lonMin + 1; let finalLat = lat + latMin + 0.5; - + if (grid.length >= 6) { - const lonSec = (g.charCodeAt(4) - 65) * (2/24); - const latSec = (g.charCodeAt(5) - 65) * (1/24); - finalLon = lon + lonMin + lonSec + (1/24); - finalLat = lat + latMin + latSec + (0.5/24); + const lonSec = (g.charCodeAt(4) - 65) * (2 / 24); + const latSec = (g.charCodeAt(5) - 65) * (1 / 24); + finalLon = lon + lonMin + lonSec + (1 / 24); + finalLat = lat + latMin + latSec + (0.5 / 24); } - + return { lat: finalLat, lon: finalLon }; } @@ -41,17 +41,17 @@ export const usePOTASpots = () => { const res = await apiFetch('/api/pota/spots'); if (res?.ok) { const spots = await res.json(); - + // Filter out QRT spots and nearly-expired spots, then sort by most recent const validSpots = spots .filter(s => { // Filter out QRT (operator signed off) const comments = (s.comments || '').toUpperCase().trim(); if (comments === 'QRT' || comments.startsWith('QRT ') || comments.startsWith('QRT,')) return false; - + // Filter out spots expiring within 60 seconds if (typeof s.expire === 'number' && s.expire < 60) return false; - + return true; }) .sort((a, b) => { @@ -60,12 +60,12 @@ export const usePOTASpots = () => { const timeB = b.spotTime ? new Date(b.spotTime).getTime() : 0; return timeB - timeA; }); - + setData(validSpots.map(s => { // Use API coordinates, fall back to grid square let lat = s.latitude ? parseFloat(s.latitude) : null; let lon = s.longitude ? parseFloat(s.longitude) : null; - + if ((!lat || !lon) && s.grid6) { const loc = gridToLatLon(s.grid6); if (loc) { lat = loc.lat; lon = loc.lon; } @@ -74,28 +74,34 @@ export const usePOTASpots = () => { const loc = gridToLatLon(s.grid4); if (loc) { lat = loc.lat; lon = loc.lon; } } - + + + // POTA API returns frequency in kHz as a string (e.g., "7160" or "433240") + // Convert to MHz for consistency with SOTA and proper rig control + const freqKhz = parseFloat(s.frequency); + const freqMhz = !isNaN(freqKhz) ? freqKhz / 1000 : null; + return { - call: s.activator, - ref: s.reference, - freq: s.frequency, + call: s.activator, + ref: s.reference, + freq: freqMhz ? freqMhz.toString() : s.frequency, // Convert to MHz string mode: s.mode, name: s.name || s.locationDesc, locationDesc: s.locationDesc, lat, lon, - time: s.spotTime ? new Date(s.spotTime).toISOString().substr(11,5)+'z' : '', + time: s.spotTime ? new Date(s.spotTime).toISOString().substr(11, 5) + 'z' : '', expire: s.expire || 0 }; })); } - } catch (err) { - console.error('POTA error:', err); - } finally { - setLoading(false); + } catch (err) { + console.error('POTA error:', err); + } finally { + setLoading(false); } }; - + fetchPOTA(); const interval = setInterval(fetchPOTA, 120 * 1000); // 2 minutes fetchRefPOTA.current = fetchPOTA; diff --git a/src/hooks/useWWFFSpots.js b/src/hooks/useWWFFSpots.js index e0417c74..005e8d43 100644 --- a/src/hooks/useWWFFSpots.js +++ b/src/hooks/useWWFFSpots.js @@ -18,7 +18,7 @@ export const useWWFFSpots = () => { const res = await apiFetch('/api/wwff/spots'); if (res.ok) { const spots = await res.json(); - + // Filter out QRT spots and nearly-expired spots, then sort by most recent const validSpots = spots .filter(s => { @@ -28,7 +28,7 @@ export const useWWFFSpots = () => { // We should also time it out if it's more than 60 minutes old if (Math.floor(Date.now() / 1000) - s.spot_time > 3600) return false; - + return true; }) .sort((a, b) => { @@ -37,33 +37,38 @@ export const useWWFFSpots = () => { const timeB = b.spot_time ? b.spot_time : 0; return timeB - timeA; }); - + setData(validSpots.map(s => { // Use API coordinates let lat = s.latitude ? parseFloat(s.latitude) : null; let lon = s.longitude ? parseFloat(s.longitude) : null; - + + // WWFF API returns frequency_khz as a number (e.g., 7160 or 433240) + // Convert to MHz for consistency with POTA/SOTA and proper rig control + const freqKhz = parseFloat(s.frequency_khz); + const freqMhz = !isNaN(freqKhz) ? freqKhz / 1000 : null; + return { - call: s.activator, - ref: s.reference, - freq: s.frequency_khz, + call: s.activator, + ref: s.reference, + freq: freqMhz ? freqMhz.toString() : s.frequency_khz, // Convert to MHz string mode: s.mode, name: s.reference_name, remarks: s.remarks, lat, lon, - time: s.spot_time ? s.spot_time_formatted.substr(11,5)+'z' : '', + time: s.spot_time ? s.spot_time_formatted.substr(11, 5) + 'z' : '', expire: 0 }; })); } - } catch (err) { - console.error('WWFF error:', err); - } finally { - setLoading(false); + } catch (err) { + console.error('WWFF error:', err); + } finally { + setLoading(false); } }; - + fetchWWFF(); fetchRef.current = fetchWWFF; const interval = setInterval(fetchWWFF, 120 * 1000); // 2 minutes - reduced from 1 to save bandwidth From c4a3e373ace02ead7897812a0b2fc33ed7899cbf Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 16:40:43 +0100 Subject: [PATCH 6/8] docs: Add rig control comparison guide and HTTPS mixed content warnings - Created RIG_CONTROL_COMPARISON.md comparing all three rig control solutions - Includes quick decision guide based on use case - Documents USB port exclusivity limitation for direct USB solutions - Added Mixed Content (HTTPS/HTTP) troubleshooting to rig-control README - Highlights Safari's strict blocking vs Chrome/Firefox workarounds - Recommends internal proxy solution for HTTPS deployments --- RIG_CONTROL_COMPARISON.md | 292 ++++++++++++++++++++++++++++++++++++++ rig-control/README.md | 35 +++++ 2 files changed, 327 insertions(+) create mode 100644 RIG_CONTROL_COMPARISON.md diff --git a/RIG_CONTROL_COMPARISON.md b/RIG_CONTROL_COMPARISON.md new file mode 100644 index 00000000..c8c788d0 --- /dev/null +++ b/RIG_CONTROL_COMPARISON.md @@ -0,0 +1,292 @@ +# 📻 OpenHamClock Rig Control Solutions — Comparison Guide + +OpenHamClock offers **three different solutions** for connecting your radio to the web application. Each serves different use cases and technical requirements. + +--- + +## Quick Decision Guide + +**Choose based on your setup:** + +| Your Situation | Recommended Solution | +|----------------|---------------------| +| 🎯 **I want the simplest setup** | **Rig Listener** — One download, one click | +| 🔌 **I already use flrig or rigctld** | **Rig Control Daemon** — Works with existing setup | +| 🌐 **I need a web UI to configure my radio** | **Rig Bridge** — Browser-based configuration | +| 🏠 **Radio is on a different computer** | **Rig Bridge** or **Rig Control Daemon** — Network accessible | +| 🐧 **Running on Raspberry Pi** | **Rig Control Daemon** — Lightweight, proven | + +--- + +## Solution Comparison + +### 1️⃣ Rig Listener (Newest — Recommended for Most Users) + +**What it is:** A standalone executable that connects directly to your radio via USB. No dependencies, no configuration files, no web server. + +**Best for:** +- First-time users who want zero hassle +- Users who don't need flrig/rigctld for other apps +- Portable/field operation (single executable) + +**Pros:** +- ✅ **Easiest setup** — Interactive wizard on first run +- ✅ **Single executable** — No Node.js installation required +- ✅ **Direct USB connection** — No intermediate software needed +- ✅ **Remembers settings** — Config saved automatically +- ✅ **Small footprint** — ~30MB executable + +**Cons:** +- ❌ No web UI for configuration (CLI wizard only) +- ❌ **USB port exclusivity** — Cannot share radio with other apps simultaneously (WSJT-X, fldigi, etc.) +- ❌ Must run on same computer as radio USB connection + +> [!WARNING] +> **USB Port Limitation**: When using direct USB connection, only ONE application can access the serial port at a time. If you need to use WSJT-X, fldigi, or other CAT control software alongside OpenHamClock, use **Rig Control Daemon** with flrig/rigctld instead — they can share the radio among multiple applications. + +**Supported Radios:** +- Yaesu (FT-991A, FT-891, FT-710, FT-DX10, FT-817/818) +- Kenwood (TS-590, TS-890, TS-480, TS-2000) +- Elecraft (K3, K4, KX3, KX2) +- Icom (IC-7300, IC-7610, IC-705, IC-9700) + +**Setup:** +```bash +# Download executable, then: +./rig-listener-mac-arm64 # Mac +rig-listener-win-x64.exe # Windows +./rig-listener-linux-x64 # Linux + +# Follow wizard prompts +# Done! Runs on http://localhost:5555 +``` + +**When to use:** +- You want to get started in under 2 minutes +- You don't use flrig/rigctld for other applications +- You value simplicity over advanced features + +--- + +### 2️⃣ Rig Bridge (Feature-Rich) + +**What it is:** A web-based rig control server with a browser configuration UI. Connects directly to your radio via USB **or** can proxy to flrig/rigctld. + +**Best for:** +- Users who want a web UI to configure their radio +- Network setups (radio on one computer, OpenHamClock on another) +- Users who need to switch between radios frequently + +**Pros:** +- ✅ **Web-based configuration** — Configure via browser at http://localhost:5555 +- ✅ **Direct USB or proxy mode** — Works with or without flrig/rigctld +- ✅ **Network accessible** — Can run on a different computer +- ✅ **Visual port selection** — See all available COM ports in browser +- ✅ **Live status display** — Real-time frequency/mode display in web UI + +**Cons:** +- ❌ Requires Node.js (or download pre-built executable) +- ❌ More complex than Rig Listener +- ❌ Slightly larger resource footprint +- ❌ **USB port exclusivity** (when using direct USB mode) — Cannot share radio with other apps + +**Supported Radios:** +- Same as Rig Listener (Yaesu, Kenwood, Icom, Elecraft) +- **Plus:** Can proxy to flrig/rigctld for any radio they support + +**Setup:** +```bash +cd rig-bridge +npm install +node rig-bridge.js + +# Open http://localhost:5555 in browser +# Select radio type and COM port +# Click "Save & Connect" +``` + +**When to use:** +- You prefer GUI configuration over CLI +- You want to access rig control from multiple devices on your network +- You need to frequently switch between different radios +- You want a visual confirmation of connection status + +--- + +### 3️⃣ Rig Control Daemon (Original — Most Flexible) + +**What it is:** A lightweight Node.js service that acts as a bridge between OpenHamClock and **existing** rig control software (flrig or rigctld). + +**Best for:** +- Users who already run flrig or rigctld for other applications +- Advanced users who want maximum control via config files +- Raspberry Pi / headless server deployments +- Integration with existing HAMlib-based workflows + +**Pros:** +- ✅ **Works with existing setup** — No need to change your current rig control +- ✅ **Lightweight** — Minimal resource usage +- ✅ **Flexible configuration** — JSON config file with all options +- ✅ **Battle-tested** — Original solution, most mature codebase +- ✅ **Remote access** — Binds to 0.0.0.0 by default for network access + +**Cons:** +- ❌ **Requires flrig or rigctld** — Cannot connect directly to radio +- ❌ Requires Node.js installation +- ❌ Manual configuration (edit JSON file) +- ❌ No built-in web UI + +**Supported Backends:** +- **rigctld** (HAMlib) — Supports 300+ radio models +- **flrig** — Popular GUI rig control software +- **mock** — Simulation mode for testing + +**Setup:** +```bash +cd rig-control +npm install + +# Edit rig-config.json: +{ + "radio": { + "type": "flrig", // or "rigctld" + "host": "127.0.0.1", + "port": 12345 // flrig default, rigctld uses 4532 + } +} + +node rig-daemon.js +``` + +**When to use:** +- You already use flrig or rigctld for WSJT-X, fldigi, etc. +- You want to share radio control across multiple applications +- You're running on a Raspberry Pi or headless server +- You need maximum flexibility and don't mind config files + +--- + +## Technical Comparison + +| Feature | Rig Listener | Rig Bridge | Rig Control Daemon | +|---------|--------------|------------|-------------------| +| **Direct USB** | ✅ Yes | ✅ Yes | ❌ No (needs flrig/rigctld) | +| **Web UI** | ❌ No | ✅ Yes | ❌ No | +| **Standalone Executable** | ✅ Yes | ✅ Yes (optional) | ❌ No | +| **Requires Node.js** | ❌ No | ❌ No (if using exe) | ✅ Yes | +| **Config Method** | CLI Wizard | Web UI | JSON File | +| **Network Access** | ✅ Yes | ✅ Yes | ✅ Yes | +| **Resource Usage** | Low | Medium | Very Low | +| **Setup Time** | 2 minutes | 5 minutes | 10 minutes | +| **Proxy to flrig/rigctld** | ❌ No | ✅ Yes | ✅ Yes (only) | + +--- + +## API Compatibility + +**All three solutions expose the same HTTP API**, so OpenHamClock works identically with any of them: + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/status` | GET | Current freq, mode, PTT, connection status | +| `/stream` | GET | Server-Sent Events (SSE) real-time updates | +| `/freq` | POST | Set frequency: `{ "freq": 14074000 }` | +| `/mode` | POST | Set mode: `{ "mode": "USB" }` | +| `/ptt` | POST | Set PTT: `{ "ptt": true }` | + +**Additional endpoints (Rig Bridge only):** +- `/api/ports` — List available serial ports +- `/api/config` — Get/set configuration via web UI + +--- + +## Migration Guide + +### From Rig Control Daemon → Rig Listener +1. Stop `rig-daemon.js` +2. Stop `flrig` or `rigctld` +3. Download and run Rig Listener +4. Follow the wizard +5. No changes needed in OpenHamClock settings (same port 5555) + +### From Rig Listener → Rig Bridge +1. Stop Rig Listener +2. Download/run Rig Bridge +3. Open http://localhost:5555 and configure +4. No changes needed in OpenHamClock settings + +### From Rig Bridge → Rig Control Daemon +1. Install and start `flrig` or `rigctld` +2. Stop Rig Bridge +3. Configure `rig-control/rig-config.json` +4. Run `node rig-daemon.js` +5. No changes needed in OpenHamClock settings + +--- + +## Troubleshooting + +### All Solutions +- **Port 5555 in use:** Another rig control service is running. Stop it first. +- **OpenHamClock can't connect:** Check firewall, ensure service is running +- **No frequency updates:** Verify radio is connected and powered on + +### Rig Listener / Rig Bridge (Direct USB) +- **No COM ports found:** Install USB driver (Silicon Labs CP210x for Yaesu) +- **Port opens but no data:** Baud rate mismatch — check radio's CAT settings +- **Linux permission denied:** `sudo usermod -a -G dialout $USER` then log out/in + +### Rig Control Daemon +- **Connection refused:** Ensure flrig/rigctld is running first +- **Wrong port:** Check `rig-config.json` matches flrig/rigctld port + +--- + +## Recommendations by Use Case + +### 🏕️ Field Operation / Portable +**Use Rig Listener** — Single executable, no dependencies, works offline + +### 🏠 Home Station (Single Radio) +**Use Rig Listener** — Simplest setup, direct USB connection + +### 🏠 Home Station (Multiple Apps) +**Use Rig Control Daemon** — Share flrig/rigctld with WSJT-X, fldigi, etc. + +> **Why?** Direct USB solutions (Rig Listener/Rig Bridge) lock the serial port exclusively. If you run WSJT-X, fldigi, or other CAT control software, they cannot access the radio simultaneously. The Rig Control Daemon works with flrig/rigctld, which act as a "hub" that multiple applications can connect to at once. + +### 🌐 Network Setup (Radio on Different Computer) +**Use Rig Bridge** — Web UI makes remote configuration easy + +### 🐧 Raspberry Pi / Headless Server +**Use Rig Control Daemon** — Lightweight, proven, easy to automate + +### 🔧 Frequent Radio Changes +**Use Rig Bridge** — Web UI makes switching radios quick + +### 🆕 First-Time User +**Use Rig Listener** — Get running in under 2 minutes + +--- + +## Summary + +| Solution | Best For | Complexity | Setup Time | +|----------|----------|------------|------------| +| **Rig Listener** | Most users, simplicity | ⭐ Easy | 2 min | +| **Rig Bridge** | Web UI lovers, network setups | ⭐⭐ Moderate | 5 min | +| **Rig Control Daemon** | Advanced users, existing flrig/rigctld | ⭐⭐⭐ Advanced | 10 min | + +**Still unsure?** Start with **Rig Listener**. You can always switch later — all three use the same API, so OpenHamClock doesn't need reconfiguration. + +--- + +## Getting Help + +- **Documentation:** Each solution has its own README in its folder +- **Issues:** [GitHub Issues](https://github.com/K0CJH/openhamclock/issues) +- **Community:** Check the OpenHamClock community forums + +--- + +**73 de K0CJH** 📻 diff --git a/rig-control/README.md b/rig-control/README.md index fd2f8edd..ef05633d 100644 --- a/rig-control/README.md +++ b/rig-control/README.md @@ -111,6 +111,41 @@ The daemon listens on port `5555` (configurable) and provides the following endp - **CORS Errors**: The daemon enables CORS for all origins by default (`*`) to allow local development. - **Port Conflicts**: If port 5555 is in use, change `server.port` in `rig-config.json`. +### Mixed Content Issues (HTTPS → HTTP) + +**Problem:** If OpenHamClock is accessed via **HTTPS** (e.g., `https://yourdomain.com` or `https://localhost:3000`), browsers will block HTTP requests to the rig daemon (`http://localhost:5555`) due to **Mixed Content** security policies. + +**Browser Behavior:** + +| Browser | Behavior | Workaround | +|---------|----------|------------| +| **Safari (macOS/iOS)** | ❌ **Strictly blocks** all mixed content. No override option. | Must use proxy solution (see below) | +| **Chrome** | ⚠️ Blocks by default. Shows shield icon in address bar to allow insecure content. | Click shield icon → "Load unsafe scripts" | +| **Firefox** | ⚠️ Blocks by default. Shows shield icon in address bar. | Click shield icon → "Disable protection for this session" | +| **Edge** | ⚠️ Blocks by default. Similar to Chrome. | Click shield icon → Allow | + +**Solutions:** + +1. **Use HTTP for OpenHamClock** (Development only): + ```bash + # Access via http://localhost:3000 instead of https:// + ``` + ⚠️ Not recommended for production/remote access. + +2. **Use the Internal Proxy** (Recommended): + - OpenHamClock can proxy rig daemon requests through its main HTTPS server + - In Settings → Rig Control, enable "Use Internal Proxy" + - This routes all rig control through the same HTTPS origin + - Works on all browsers including Safari + +3. **Run Rig Daemon with HTTPS** (Advanced): + - Configure the rig daemon to use SSL/TLS certificates + - Requires self-signed cert setup and browser trust configuration + - Not recommended for local-only setups + +**Recommendation:** For remote/HTTPS deployments, use the **Internal Proxy** feature in OpenHamClock Settings. For local development over HTTP, direct connection works fine. + + ## Experimental Scripts The `scripts/` folder contains experimental installation and utility scripts. These are currently **in testing** and may not function properly on all systems. Use them with caution. From 8c349832fdbf9758a1fb88475c6747468783028b Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 7/8] fix(rig-control): Use dial frequency only for WSJT-X decodes WSJT-X decodes contain two frequency values: - dialFrequency: Radio VFO frequency in Hz (e.g., 14074000) - freq: Audio delta frequency in Hz (e.g., 1234) The delta frequency is just the audio offset within the passband where the signal was decoded. When tuning the rig, we should use only the dial frequency, not dial + delta. Fixed by using dialFrequency directly for WSJT-X decodes while preserving original behavior for other spot types (DX Cluster, POTA, WWFF, SOTA). Changes: - DockableApp.jsx: Use dialFrequency only for WSJT-X spots - RigContext.jsx: Use dialFrequency only in tuneTo function This fix applies to all layouts (Modern, Classic, Dockable) as they all use the tuneTo function from RigContext. UI display unchanged - still shows delta frequency as expected. --- src/DockableApp.jsx | 18 +++++++++++++----- src/contexts/RigContext.jsx | 12 +++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/DockableApp.jsx b/src/DockableApp.jsx index c916a8ff..9d26d207 100644 --- a/src/DockableApp.jsx +++ b/src/DockableApp.jsx @@ -186,11 +186,19 @@ export const DockableApp = ({ if (!spot) return; // 1. Tune Rig if frequency is available and rig control is enabled - // Spot freq is usually in kHz or MHz string - if (enabled && (spot.freq || spot.freqMHz)) { - const freq = spot.freq || (parseFloat(spot.freqMHz) * 1000); // Normalize to kHz for tuneTo (which handles units) - // tuneTo handles unit detection (MHz vs kHz vs Hz) so just pass the raw value - tuneTo(spot.freq || spot.freqMHz, spot.mode); + if (enabled && (spot.freq || spot.freqMHz || spot.dialFrequency)) { + let freqToSend; + + // WSJT-X decodes have dialFrequency (the VFO frequency to tune to) + // The freq field is just the audio delta offset within the passband + if (spot.dialFrequency) { + freqToSend = spot.dialFrequency; // Use dial frequency directly + } else { + // For other spot types (DX Cluster, POTA, etc.), use freq or freqMHz as-is + freqToSend = spot.freq || spot.freqMHz; + } + + tuneTo(freqToSend, spot.mode); } // 2. Set DX Location if location data is available diff --git a/src/contexts/RigContext.jsx b/src/contexts/RigContext.jsx index 0dd1c37b..ce644657 100644 --- a/src/contexts/RigContext.jsx +++ b/src/contexts/RigContext.jsx @@ -182,7 +182,17 @@ export const RigProvider = ({ children, rigConfig }) => { // Handle spot object (recursive call) if (typeof freqInput === 'object' && freqInput !== null) { const spot = freqInput; - const f = spot.freq || spot.freqMHz; + let f; + + // WSJT-X decodes have dialFrequency (VFO frequency) + // The freq field is just the audio delta offset, not part of the tune frequency + if (spot.dialFrequency) { + f = spot.dialFrequency; // Use dial frequency directly + } else { + // For other spot types (DX Cluster, POTA, etc.) + f = spot.freq || spot.freqMHz; + } + const m = spot.mode || modeInput; if (f) { tuneTo(f, m); From 83e6edd20df361c441147a70e687827e43d4cf63 Mon Sep 17 00:00:00 2001 From: ceotjoe Date: Tue, 17 Feb 2026 08:52:20 +0100 Subject: [PATCH 8/8] docs: enhance rig control UserGuide with local and remote UI scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added clear scenario-based documentation for two installation types: 1. Local installation (full OpenHamClock + daemon) 2. Remote UI (daemon only, using openhamclock.com) - Documented HTTPS→HTTP mixed content issues and browser-specific workarounds - Updated config examples to match current rig-config.json format - Added comprehensive troubleshooting and quick reference sections - Removed references to unimplemented Internal Proxy feature - Clarified Safari has no workaround for mixed content blocking --- rig-control/README.md | 24 +--- rig-control/UserGuide.md | 264 +++++++++++++++++++++++++++++++++++---- 2 files changed, 245 insertions(+), 43 deletions(-) diff --git a/rig-control/README.md b/rig-control/README.md index ef05633d..db4ed106 100644 --- a/rig-control/README.md +++ b/rig-control/README.md @@ -113,37 +113,19 @@ The daemon listens on port `5555` (configurable) and provides the following endp ### Mixed Content Issues (HTTPS → HTTP) -**Problem:** If OpenHamClock is accessed via **HTTPS** (e.g., `https://yourdomain.com` or `https://localhost:3000`), browsers will block HTTP requests to the rig daemon (`http://localhost:5555`) due to **Mixed Content** security policies. +**Problem:** If OpenHamClock is accessed via **HTTPS** (e.g., `https://yourdomain.com` or `https://openhamclock.com`), browsers will block HTTP requests to the rig daemon (`http://localhost:5555`) due to **Mixed Content** security policies. **Browser Behavior:** | Browser | Behavior | Workaround | |---------|----------|------------| -| **Safari (macOS/iOS)** | ❌ **Strictly blocks** all mixed content. No override option. | Must use proxy solution (see below) | +| **Safari (macOS/iOS)** | ❌ **Strictly blocks** all mixed content. No override option. | No workaround available. Use Chrome/Firefox/Edge or run OpenHamClock locally via HTTP. | | **Chrome** | ⚠️ Blocks by default. Shows shield icon in address bar to allow insecure content. | Click shield icon → "Load unsafe scripts" | | **Firefox** | ⚠️ Blocks by default. Shows shield icon in address bar. | Click shield icon → "Disable protection for this session" | | **Edge** | ⚠️ Blocks by default. Similar to Chrome. | Click shield icon → Allow | -**Solutions:** +**Recommendation:** For the best experience, run OpenHamClock locally using HTTP (e.g., `http://localhost:3000`) to avoid mixed content issues entirely. See the [User Guide](./UserGuide.md) for detailed setup instructions. -1. **Use HTTP for OpenHamClock** (Development only): - ```bash - # Access via http://localhost:3000 instead of https:// - ``` - ⚠️ Not recommended for production/remote access. - -2. **Use the Internal Proxy** (Recommended): - - OpenHamClock can proxy rig daemon requests through its main HTTPS server - - In Settings → Rig Control, enable "Use Internal Proxy" - - This routes all rig control through the same HTTPS origin - - Works on all browsers including Safari - -3. **Run Rig Daemon with HTTPS** (Advanced): - - Configure the rig daemon to use SSL/TLS certificates - - Requires self-signed cert setup and browser trust configuration - - Not recommended for local-only setups - -**Recommendation:** For remote/HTTPS deployments, use the **Internal Proxy** feature in OpenHamClock Settings. For local development over HTTP, direct connection works fine. ## Experimental Scripts diff --git a/rig-control/UserGuide.md b/rig-control/UserGuide.md index 45be1261..c95cf435 100644 --- a/rig-control/UserGuide.md +++ b/rig-control/UserGuide.md @@ -11,9 +11,33 @@ This feature allows you to: --- +## 📋 Choose Your Setup Scenario + +There are **two ways** to use Rig Control with OpenHamClock: + +### Scenario 1: Local Installation (Full Setup) +You install **both** OpenHamClock and the Rig Control daemon on your local computer. This is ideal for: +- Running everything on one machine (laptop, desktop, Raspberry Pi) +- Development and testing +- Offline operation + +👉 **[Jump to Scenario 1 Instructions](#scenario-1-local-installation)** + +### Scenario 2: Remote UI with Local Daemon +You use the OpenHamClock web interface from **openhamclock.com** (or another hosted instance) but run **only the Rig Control daemon** locally on the computer connected to your radio. This is ideal for: +- Using the hosted version without maintaining your own installation +- Accessing from multiple devices while keeping rig control local +- Simpler setup with fewer components to manage + +👉 **[Jump to Scenario 2 Instructions](#scenario-2-remote-ui-with-local-daemon)** + +--- + +# Scenario 1: Local Installation + ## 🛠 Prerequisites -You need three things installed on the computer connected to your radio (e.g., Raspberry Pi, Mac, or PC): +You need the following installed on the computer connected to your radio (e.g., Raspberry Pi, Mac, or PC): 1. **Git:** To download the software. - [Download Git](https://git-scm.com/downloads) @@ -28,8 +52,6 @@ You need three things installed on the computer connected to your radio (e.g., R ## 📦 Step 1: Install OpenHamClock -If you haven't installed OpenHamClock yet, follow these steps. - 1. Open your terminal/command prompt. 2. Download the code: ```bash @@ -47,25 +69,25 @@ If you haven't installed OpenHamClock yet, follow these steps. --- -## 🚀 Step 2: Install the Rig Control Bridge +## 🚀 Step 2: Install the Rig Control Daemon -The "Rig Control Bridge" (daemon) is a separate small program that sits between OpenHamClock and your radio software. +The "Rig Control Daemon" is a separate small program that sits between OpenHamClock and your radio software. 1. Navigate to the `rig-control` folder: ```bash cd rig-control ``` _(If you are in the main folder, just type `cd rig-control`)_ -2. Install the bridge libraries: +2. Install the daemon libraries: ```bash npm install ``` --- -## ⚙️ Step 3: Configure the Bridge +## ⚙️ Step 3: Configure the Daemon -Tell the bridge which radio software you use. +Tell the daemon which radio software you use. 1. Find `rig-config.json` in the `rig-control` folder. If it doesn't exist, it will be created automatically from `rig-config.json.example` when you first start the daemon. 2. Edit it with any text editor. @@ -76,12 +98,18 @@ Ensure FLRIG is running and **XML-RPC** is enabled in its settings (Config > Set ```json { - "rigType": "flrig", - "flrig": { - "host": "127.0.0.1", - "port": 12345 + "server": { + "host": "0.0.0.0", + "port": 5555, + "cors": "*" }, - "serverPort": 5555 + "radio": { + "type": "flrig", + "host": "127.0.0.1", + "port": 12345, + "pollInterval": 1000, + "pttEnabled": false + } } ``` @@ -89,12 +117,18 @@ Ensure FLRIG is running and **XML-RPC** is enabled in its settings (Config > Set ```json { - "rigType": "rigctld", - "rigctld": { - "host": "127.0.0.1", - "port": 4532 + "server": { + "host": "0.0.0.0", + "port": 5555, + "cors": "*" }, - "serverPort": 5555 + "radio": { + "type": "rigctld", + "host": "127.0.0.1", + "port": 4532, + "pollInterval": 1000, + "pttEnabled": false + } } ``` @@ -124,7 +158,7 @@ In the `openhamclock/rig-control` folder: node rig-daemon.js ``` -- This starts the **Bridge**. +- This starts the **Daemon**. - Standard Port: **5555** - _Note: You do NOT visit this port in your browser. It runs in the background._ @@ -136,9 +170,10 @@ node rig-daemon.js 2. Go to **Settings** (Gear Icon) > **Station Settings**. 3. Scroll to **Rig Control**. 4. Check **Enable Rig Control**. -5. Set **Host URL** to: `http://localhost:5555` - - _(This points the Dashboard on port 3000 to the Bridge on port 5555)_. +5. Set **Daemon URL** to: `http://localhost:5555` + - _(This points the Dashboard on port 3000 to the Daemon on port 5555)_. 6. **Optional:** Check **"Tune Button Enabled"** if you want to trigger your ATU. +7. Click **Save**. --- @@ -158,4 +193,189 @@ Navigate to the dashboard. You should see the Rig Control panel (if enabled). - **Radio won't tune:** Ensure FLRIG is running and connected to the radio. - **Double check ports:** - Browser URL: `http://localhost:3000` - - Settings Rig URL: `http://localhost:5555` + - Settings Daemon URL: `http://localhost:5555` + +--- + +# Scenario 2: Remote UI with Local Daemon + +In this scenario, you use the OpenHamClock web interface from **openhamclock.com** (or another HTTPS-hosted instance) while running only the Rig Control daemon locally on the computer connected to your radio. + +## 🛠 Prerequisites + +You need the following installed on the computer connected to your radio: + +1. **Git:** To download the daemon software. + - [Download Git](https://git-scm.com/downloads) +2. **Node.js:** The engine that runs the daemon. + - **Check:** Open a terminal and type `node -v`. (You want version 18 or higher). + - **Install:** [Download Node.js LTS](https://nodejs.org/). +3. **Radio Software:** One of the following must be running and connected to your radio: + - **FLRIG (Recommended):** [Download FLRIG](http://www.w1hkj.com/files/flrig/) + - **Hamlib (rigctld):** For advanced users. + +--- + +## 📦 Step 1: Install the Rig Control Daemon Only + +1. Open your terminal/command prompt. +2. Download the OpenHamClock repository (we only need the `rig-control` folder): + ```bash + git clone https://github.com/HAMDevs/openhamclock.git + cd openhamclock/rig-control + ``` +3. Install the daemon dependencies: + ```bash + npm install + ``` + +--- + +## ⚙️ Step 2: Configure the Daemon + +1. Find `rig-config.json` in the `rig-control` folder. If it doesn't exist, it will be created automatically from `rig-config.json.example` when you first start the daemon. +2. Edit it with any text editor. + +### If using FLRIG (Easiest) + +Ensure FLRIG is running and **XML-RPC** is enabled in its settings (Config > Setup > UI > XML-RPC). + +```json +{ + "server": { + "host": "0.0.0.0", + "port": 5555, + "cors": "*" + }, + "radio": { + "type": "flrig", + "host": "127.0.0.1", + "port": 12345, + "pollInterval": 1000, + "pttEnabled": false + } +} +``` + +### If using Hamlib (rigctld) + +```json +{ + "server": { + "host": "0.0.0.0", + "port": 5555, + "cors": "*" + }, + "radio": { + "type": "rigctld", + "host": "127.0.0.1", + "port": 4532, + "pollInterval": 1000, + "pttEnabled": false + } +} +``` + +--- + +## ▶️ Step 3: Start the Daemon + +In the `openhamclock/rig-control` folder: + +```bash +node rig-daemon.js +``` + +- This starts the **Daemon**. +- Standard Port: **5555** +- The daemon will run in the background and communicate with your radio software. + +--- + +## 🔗 Step 4: Configure OpenHamClock Web UI + +### Important: HTTPS → HTTP Mixed Content Issue + +When you access OpenHamClock via **HTTPS** (e.g., `https://openhamclock.com`), your browser will **block** direct HTTP connections to your local daemon (`http://localhost:5555`) for security reasons. This is called a "Mixed Content" security policy. + +**Different browsers handle this differently:** + +| Browser | Behavior | Workaround Available? | +|---------|----------|----------------------| +| **Safari (macOS/iOS)** | ❌ Strictly blocks all mixed content | ❌ No workaround available | +| **Chrome** | ⚠️ Blocks by default | ✅ Click shield icon in address bar → "Load unsafe scripts" | +| **Firefox** | ⚠️ Blocks by default | ✅ Click shield icon in address bar → "Disable protection for this session" | +| **Edge** | ⚠️ Blocks by default | ✅ Click shield icon in address bar → Allow | + +### ⚠️ Current Limitations + +**Safari Users:** Unfortunately, Safari does not provide any way to override mixed content blocking. If you're using Safari, you have two options: +1. **Use a different browser** (Chrome, Firefox, or Edge) that allows mixed content overrides +2. **Run OpenHamClock locally** using Scenario 1 (both UI and daemon on HTTP) + +**Chrome/Firefox/Edge Users:** You can use the browser's mixed content override feature (shield icon in address bar), but you'll need to re-enable it each time you reload the page. + +### 📝 Configuration Steps + +1. Open **https://openhamclock.com** (or your hosted instance) in your browser. +2. Go to **Settings** (Gear Icon) > **Station Settings**. +3. Scroll to **Rig Control**. +4. Check **Enable Rig Control**. +5. Set **Daemon URL** to: `http://localhost:5555` +6. **Optional:** Check **"Tune Button Enabled"** if you want to trigger your ATU. +7. Click **Save**. +8. **If using Chrome/Firefox/Edge:** Look for the shield icon in your browser's address bar and click it to allow mixed content. + +> **💡 Recommendation:** For the best experience with rig control, consider using **Scenario 1** (local installation) where both the UI and daemon run on HTTP, avoiding mixed content issues entirely. + +--- + +## ✅ You're Done! + +Navigate to the dashboard at **https://openhamclock.com**. You should see the Rig Control panel (if enabled). + +**Try it out:** +- Click a spot on the **World Map**. +- Click a row in the **DX Cluster** list. +- Click a **POTA** or **SOTA** spot. +- Works across **Classic**, **Modern**, **Tablet**, and **Compact** layouts! + +### Troubleshooting + +- **"Connection Failed":** + - Ensure `node rig-daemon.js` is running in a terminal. + - Verify the daemon is listening on port 5555 (check terminal output). + - **If using HTTPS UI:** Check for mixed content blocking (see browser-specific workarounds above). + +- **Radio won't tune:** + - Ensure FLRIG or rigctld is running and connected to the radio. + - Check the daemon terminal output for error messages. + +- **Mixed Content Errors (Console):** + - **Safari:** No workaround available. Use Chrome/Firefox/Edge or switch to Scenario 1. + - **Chrome/Firefox/Edge:** Click the shield icon in the address bar to allow mixed content. + - **Best Solution:** Consider using Scenario 1 (local installation) to avoid this issue entirely. + +- **Firewall Issues:** + - If the daemon is on a different machine than your browser, ensure port 5555 is open in your firewall. + - Update the **Daemon URL** to use the daemon machine's IP address (e.g., `http://192.168.1.50:5555`). + +--- + +## 🎯 Quick Reference + +### Scenario 1 (Local Installation) +- **What you install:** OpenHamClock + Rig Control Daemon +- **What you run:** `npm start` (OpenHamClock) + `node rig-daemon.js` (Daemon) +- **Where you access UI:** `http://localhost:3000` +- **Daemon URL in Settings:** `http://localhost:5555` +- **Browser compatibility:** All browsers ✅ +- **Mixed content issues:** None (both on HTTP) + +### Scenario 2 (Remote UI) +- **What you install:** Rig Control Daemon only +- **What you run:** `node rig-daemon.js` (Daemon) +- **Where you access UI:** `https://openhamclock.com` (or your hosted instance) +- **Daemon URL in Settings:** `http://localhost:5555` (or `http://your-daemon-ip:5555`) +- **Browser compatibility:** Chrome/Firefox/Edge ✅ (with manual override) | Safari ❌ +- **Mixed content issues:** Requires browser override on each page load (Chrome/Firefox/Edge only)