From 8030ef70ff22f142b800bfaf558f0257868b3cb2 Mon Sep 17 00:00:00 2001 From: accius Date: Thu, 29 Jan 2026 23:31:29 -0500 Subject: [PATCH] initial commit --- README.md | 242 ++++++++++++ config.js | 194 ++++++++++ index.html | 1035 +++++++++++++++++++++++++++++++++++++++++++++++++++ server.py | 125 +++++++ setup-pi.sh | 194 ++++++++++ 5 files changed, 1790 insertions(+) create mode 100644 README.md create mode 100644 config.js create mode 100644 index.html create mode 100644 server.py create mode 100644 setup-pi.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..55d7f83 --- /dev/null +++ b/README.md @@ -0,0 +1,242 @@ +# OpenHamClock + +**A modern, open-source amateur radio dashboard - spiritual successor to HamClock** + +*In memory of Elwood Downey, WB0OEW, creator of the original HamClock* + +--- + +## Overview + +OpenHamClock is a web-based kiosk-style application that provides real-time space weather, radio propagation information, and other data useful to amateur radio operators. It's designed to run on any platform with a web browser, with special consideration for Raspberry Pi deployments. + +## Features + +### Current Features (v1.0.0) + +- **World Map with Day/Night Terminator** + - Real-time gray line display + - Sun position tracking + - DE and DX location markers with path visualization + +- **Time Displays** + - UTC time (large, prominent display) + - Local time with date + - Uptime counter + +- **Location Information** + - DE (your location) with Maidenhead grid square + - DX (target location) with grid square + - Short path and long path bearing + - Distance calculation + - Sunrise/sunset times for both locations + +- **Space Weather Panel** + - Solar Flux Index (SFI) + - Sunspot Number + - K-Index and A-Index + - X-Ray flux + - Overall conditions assessment + +- **Band Conditions** + - Visual display for all HF bands + - Color-coded conditions (Good/Fair/Poor) + - VHF band status + +- **DX Cluster Feed** + - Live spot display (placeholder for API integration) + - Frequency, callsign, comment, and time + +- **POTA Activity** + - Parks on the Air activator tracking + - Reference, frequency, and mode display + +### Planned Features (Roadmap) + +- [ ] Live API integration for space weather (NOAA, hamqsl.com) +- [ ] Real DX cluster connectivity (Telnet/WebSocket) +- [ ] Live POTA/SOTA API integration +- [ ] Satellite tracking +- [ ] VOACAP propagation predictions +- [ ] Contest calendar integration +- [ ] Hamlib/flrig radio control +- [ ] Rotator control +- [ ] Customizable panel layout +- [ ] Multiple map projections +- [ ] ADIF log file integration +- [ ] RESTful API for external control +- [ ] Touch screen support +- [ ] Alarm/alert system + +## Installation + +### Option 1: Direct Browser Use + +Simply open `index.html` in any modern web browser. No server required! + +```bash +# Clone or download the files +firefox index.html +# or +chromium-browser index.html +``` + +### Option 2: Raspberry Pi Kiosk Mode + +1. **Install Raspberry Pi OS** (Desktop version recommended) + +2. **Copy OpenHamClock files** + ```bash + mkdir ~/openhamclock + cp index.html ~/openhamclock/ + ``` + +3. **Run the setup script** + ```bash + chmod +x setup-pi.sh + ./setup-pi.sh + ``` + +4. **Manual Kiosk Setup** (alternative) + ```bash + # Install unclutter to hide mouse cursor + sudo apt-get install unclutter + + # Create autostart entry + mkdir -p ~/.config/autostart + cat > ~/.config/autostart/openhamclock.desktop << EOF + [Desktop Entry] + Type=Application + Name=OpenHamClock + Exec=chromium-browser --kiosk --noerrdialogs --disable-infobars --incognito file:///home/pi/openhamclock/index.html + EOF + ``` + +### Option 3: Local Web Server + +For advanced features (future API integrations), run with a local server: + +```bash +# Python 3 +cd openhamclock +python3 -m http.server 8080 + +# Then open http://localhost:8080 +``` + +### Option 4: Electron Desktop App (Future) + +Coming soon: Packaged desktop applications for Windows, macOS, and Linux. + +## Configuration + +Edit the following values in `index.html` to customize: + +```javascript +// Your callsign +const [callsign, setCallsign] = useState('YOUR_CALL'); + +// Your location (lat, lon) +const [deLocation, setDeLocation] = useState({ lat: 39.7392, lon: -104.9903 }); + +// Default DX location +const [dxLocation, setDxLocation] = useState({ lat: 35.6762, lon: 139.6503 }); +``` + +### Future Configuration File + +A separate `config.js` will be provided for easier configuration: + +```javascript +// config.js (coming soon) +export default { + callsign: 'K0CJH', + location: { + lat: 39.7392, + lon: -104.9903 + }, + theme: 'dark', + panels: ['clock', 'map', 'weather', 'dx', 'bands'], + // ... more options +}; +``` + +## Display Resolutions + +OpenHamClock is responsive and works at various resolutions: + +| Resolution | Recommended Use | +|------------|-----------------| +| 800x480 | Small Pi displays, Inovato Quadra | +| 1024x600 | 7" Pi touchscreens | +| 1280x720 | HD ready monitors | +| 1600x960 | Recommended for full features | +| 1920x1080 | Full HD monitors | +| 2560x1440 | Large displays, high detail | + +## API Data Sources (Planned) + +| Data | Source | Status | +|------|--------|--------| +| Space Weather | NOAA SWPC | Planned | +| Band Conditions | hamqsl.com | Planned | +| DX Cluster | Various Telnet nodes | Planned | +| POTA | pota.app API | Planned | +| SOTA | sotawatch.org | Planned | +| Satellites | N2YO, CelesTrak | Planned | + +## Technical Details + +### Architecture + +- **Frontend**: React 18 (single-file, no build required) +- **Styling**: CSS-in-JS with CSS variables for theming +- **Maps**: SVG-based rendering (no external tiles) +- **Data**: Currently static, API integration planned + +### Browser Support + +- Chrome/Chromium 80+ +- Firefox 75+ +- Safari 13+ +- Edge 80+ + +### Dependencies + +None! OpenHamClock loads React and Babel from CDN for simplicity. + +For offline/airgapped deployments, download these files: +- react.production.min.js +- react-dom.production.min.js +- babel.min.js + +## Contributing + +Contributions are welcome! Areas where help is needed: + +1. **API Integrations** - Connect to live data sources +2. **Satellite Tracking** - SGP4 propagator implementation +3. **Map Improvements** - Better landmass rendering, additional projections +4. **Testing** - Various Pi models and display sizes +5. **Documentation** - User guides, translations +6. **Design** - UI/UX improvements, accessibility + +## License + +MIT License - Free for personal and commercial use. + +## Acknowledgments + +- **Elwood Downey, WB0OEW** - Creator of the original HamClock. His work inspired thousands of amateur radio operators worldwide. Rest in peace, OM. +- **Amateur Radio Community** - For continued innovation and the spirit of experimentation. + +## Contact + +- **GitHub**: [github.com/your-repo/openhamclock](https://github.com) +- **Email**: your-email@example.com + +--- + +**73 de K0CJH** + +*"The original HamClock will cease to function in June 2026. OpenHamClock aims to carry on Elwood's legacy with a modern, open-source implementation that the community can maintain and improve together."* diff --git a/config.js b/config.js new file mode 100644 index 0000000..bc56e18 --- /dev/null +++ b/config.js @@ -0,0 +1,194 @@ +/** + * OpenHamClock Configuration + * + * Edit this file to customize your OpenHamClock instance. + * After making changes, refresh the browser to apply. + * + * For Raspberry Pi: Edit this file at ~/openhamclock/config.js + */ + +const OpenHamClockConfig = { + // ======================================== + // STATION INFORMATION + // ======================================== + + // Your callsign (displayed in header) + callsign: "K0CJH", + + // Your location (DE - "This End") + // Find coordinates: https://www.latlong.net/ + location: { + lat: 39.7392, // Latitude (positive = North, negative = South) + lon: -104.9903 // Longitude (positive = East, negative = West) + }, + + // Default DX location (far end for path calculations) + // Set to a frequently worked location, or leave as default + defaultDX: { + lat: 35.6762, // Tokyo, Japan + lon: 139.6503 + }, + + // ======================================== + // DISPLAY OPTIONS + // ======================================== + + // Theme: 'dark' (default), 'light' (coming soon) + theme: "dark", + + // Time format: '24h' or '12h' + timeFormat: "24h", + + // Date format: 'iso' (YYYY-MM-DD), 'us' (MM/DD/YYYY), 'eu' (DD/MM/YYYY) + dateFormat: "iso", + + // Show seconds in time display + showSeconds: true, + + // ======================================== + // PANELS TO DISPLAY + // ======================================== + + // Enable/disable individual panels + panels: { + utcClock: true, + localClock: true, + worldMap: true, + deInfo: true, + dxInfo: true, + spaceWeather: true, + bandConditions: true, + dxCluster: true, + potaActivity: true, + sotaActivity: false, // Coming soon + satellites: false, // Coming soon + contests: false // Coming soon + }, + + // ======================================== + // MAP OPTIONS + // ======================================== + + map: { + // Map style: 'standard', 'terrain', 'minimal' + style: "standard", + + // Show day/night terminator (gray line) + showTerminator: true, + + // Show grid lines + showGrid: true, + + // Show path between DE and DX + showPath: true, + + // Path style: 'greatCircle' or 'straight' + pathStyle: "greatCircle" + }, + + // ======================================== + // DX CLUSTER SETTINGS + // ======================================== + + dxCluster: { + // Enable live DX cluster connection + enabled: false, // Set to true when API is implemented + + // Cluster node (Telnet) + node: "dxc.nc7j.com", + port: 7373, + + // Login callsign (usually your call) + login: "K0CJH", + + // Filter options + filters: { + // Only show spots for these bands (empty = all bands) + bands: [], // e.g., ["20m", "40m", "15m"] + + // Only show these modes (empty = all modes) + modes: [], // e.g., ["FT8", "CW", "SSB"] + + // Minimum spot age to display (minutes) + maxAge: 30 + } + }, + + // ======================================== + // POTA/SOTA SETTINGS + // ======================================== + + pota: { + enabled: true, + + // Filter by state/region (empty = all) + regions: [], // e.g., ["K-CO", "K-WY"] + + // Maximum number of spots to show + maxSpots: 10 + }, + + sota: { + enabled: false, + + // Filter by association (empty = all) + associations: [], // e.g., ["W7C", "W0C"] + + maxSpots: 10 + }, + + // ======================================== + // SPACE WEATHER DATA SOURCES + // ======================================== + + dataRefresh: { + // Refresh interval in seconds + spaceWeather: 300, // 5 minutes + bandConditions: 300, // 5 minutes + dxCluster: 5, // 5 seconds (live) + pota: 60, // 1 minute + sota: 60 // 1 minute + }, + + // ======================================== + // SOUND/ALERTS (Coming Soon) + // ======================================== + + alerts: { + enabled: false, + + // Sound alerts for new DX spots + dxClusterSound: false, + + // Alert for specific DXCC entities + watchedEntities: [], // e.g., ["VP8", "3Y", "P5"] + + // Alert for space weather events + spaceWeatherAlert: false + }, + + // ======================================== + // ADVANCED + // ======================================== + + advanced: { + // Enable debug logging + debug: false, + + // Custom CSS (appended to page) + customCSS: "", + + // API endpoints (for self-hosted data servers) + apiEndpoints: { + spaceWeather: null, // null = use default + dxCluster: null, + pota: null, + sota: null + } + } +}; + +// Export for use in main application +if (typeof module !== 'undefined' && module.exports) { + module.exports = OpenHamClockConfig; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..09d7051 --- /dev/null +++ b/index.html @@ -0,0 +1,1035 @@ + + + + + + OpenHamClock - Amateur Radio Dashboard + + + + + + + + + +
+ + + + diff --git a/server.py b/server.py new file mode 100644 index 0000000..b50ca36 --- /dev/null +++ b/server.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +""" +OpenHamClock Development Server + +A simple HTTP server for OpenHamClock with API proxy capabilities. +This allows the application to fetch live data from external sources +without CORS issues. + +Usage: + python3 server.py [port] + + Default port: 8080 + Open http://localhost:8080 in your browser + +Requirements: + Python 3.7+ + requests library (optional, for API proxy) +""" + +import http.server +import socketserver +import json +import urllib.request +import urllib.error +import sys +import os +from datetime import datetime + +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080 + +# API endpoints for live data +API_ENDPOINTS = { + 'solarflux': 'https://services.swpc.noaa.gov/json/solar-cycle/observed-solar-flux.json', + 'kindex': 'https://services.swpc.noaa.gov/json/planetary_k_index_1m.json', + 'xray': 'https://services.swpc.noaa.gov/json/goes/primary/xrays-7-day.json', + 'sunspots': 'https://services.swpc.noaa.gov/json/solar-cycle/sunspots.json', + 'pota': 'https://api.pota.app/spot/activator', + 'bands': 'https://www.hamqsl.com/solarxml.php', # HamQSL solar data +} + +class OpenHamClockHandler(http.server.SimpleHTTPRequestHandler): + """Custom HTTP handler with API proxy support.""" + + def do_GET(self): + # Handle API proxy requests + if self.path.startswith('/api/'): + self.handle_api() + else: + # Serve static files + super().do_GET() + + def handle_api(self): + """Proxy API requests to avoid CORS issues.""" + endpoint = self.path.replace('/api/', '').split('?')[0] + + if endpoint not in API_ENDPOINTS: + self.send_error(404, f"Unknown API endpoint: {endpoint}") + return + + try: + url = API_ENDPOINTS[endpoint] + print(f"[{datetime.now().strftime('%H:%M:%S')}] Fetching: {url}") + + # Make the request + req = urllib.request.Request( + url, + headers={'User-Agent': 'OpenHamClock/1.0'} + ) + + with urllib.request.urlopen(req, timeout=10) as response: + data = response.read() + content_type = response.headers.get('Content-Type', 'application/json') + + # Send response + self.send_response(200) + self.send_header('Content-Type', content_type) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Cache-Control', 'max-age=60') + self.end_headers() + self.wfile.write(data) + + except urllib.error.URLError as e: + print(f"[ERROR] Failed to fetch {endpoint}: {e}") + self.send_error(502, f"Failed to fetch data: {e}") + except Exception as e: + print(f"[ERROR] {e}") + self.send_error(500, str(e)) + + def log_message(self, format, *args): + """Custom logging format.""" + if args[0].startswith('GET /api/'): + return # Already logged in handle_api + print(f"[{datetime.now().strftime('%H:%M:%S')}] {args[0]}") + + +def main(): + # Change to the directory containing this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + os.chdir(script_dir) + + print("=" * 50) + print(" OpenHamClock Development Server") + print("=" * 50) + print() + print(f" Serving from: {script_dir}") + print(f" URL: http://localhost:{PORT}") + print(f" Press Ctrl+C to stop") + print() + print(" Available API endpoints:") + for name, url in API_ENDPOINTS.items(): + print(f" /api/{name}") + print() + print("=" * 50) + print() + + with socketserver.TCPServer(("", PORT), OpenHamClockHandler) as httpd: + httpd.allow_reuse_address = True + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nServer stopped.") + + +if __name__ == "__main__": + main() diff --git a/setup-pi.sh b/setup-pi.sh new file mode 100644 index 0000000..f38eeb6 --- /dev/null +++ b/setup-pi.sh @@ -0,0 +1,194 @@ +#!/bin/bash +# +# OpenHamClock Raspberry Pi Setup Script +# Configures Pi for kiosk mode operation +# +# Usage: chmod +x setup-pi.sh && ./setup-pi.sh +# + +set -e + +echo "========================================" +echo " OpenHamClock Raspberry Pi Setup" +echo "========================================" +echo "" + +# Check if running on Raspberry Pi +if [ ! -f /proc/device-tree/model ]; then + echo "Warning: This doesn't appear to be a Raspberry Pi." + echo "Continuing anyway..." +fi + +# Get the current user +CURRENT_USER=$(whoami) +HOME_DIR=$(eval echo ~$CURRENT_USER) +OPENHAMCLOCK_DIR="$HOME_DIR/openhamclock" + +echo "Installing for user: $CURRENT_USER" +echo "Install directory: $OPENHAMCLOCK_DIR" +echo "" + +# Update system +echo ">>> Updating system packages..." +sudo apt-get update -qq + +# Install required packages +echo ">>> Installing required packages..." +sudo apt-get install -y -qq \ + chromium-browser \ + unclutter \ + xdotool \ + x11-xserver-utils + +# Create OpenHamClock directory if it doesn't exist +echo ">>> Setting up OpenHamClock directory..." +mkdir -p "$OPENHAMCLOCK_DIR" + +# Copy index.html if it exists in the current directory +if [ -f "index.html" ]; then + cp index.html "$OPENHAMCLOCK_DIR/" + echo ">>> Copied index.html to $OPENHAMCLOCK_DIR" +fi + +# Create the autostart directory +echo ">>> Configuring autostart..." +mkdir -p "$HOME_DIR/.config/autostart" + +# Create autostart entry for OpenHamClock +cat > "$HOME_DIR/.config/autostart/openhamclock.desktop" << EOF +[Desktop Entry] +Type=Application +Name=OpenHamClock +Comment=Amateur Radio Dashboard +Exec=/bin/bash $OPENHAMCLOCK_DIR/start-kiosk.sh +Terminal=false +Hidden=false +X-GNOME-Autostart-enabled=true +EOF + +# Create kiosk start script +echo ">>> Creating kiosk start script..." +cat > "$OPENHAMCLOCK_DIR/start-kiosk.sh" << 'EOF' +#!/bin/bash +# +# OpenHamClock Kiosk Mode Launcher +# + +# Wait for desktop to be ready +sleep 5 + +# Disable screen blanking and power management +xset s off +xset -dpms +xset s noblank + +# Hide the mouse cursor after 3 seconds of inactivity +unclutter -idle 3 -root & + +# Kill any existing Chromium processes +pkill -f chromium-browser || true +sleep 2 + +# Start Chromium in kiosk mode +chromium-browser \ + --kiosk \ + --noerrdialogs \ + --disable-infobars \ + --disable-session-crashed-bubble \ + --disable-restore-session-state \ + --disable-features=TranslateUI \ + --check-for-update-interval=31536000 \ + --disable-component-update \ + --overscroll-history-navigation=0 \ + --incognito \ + "file://$HOME/openhamclock/index.html" +EOF + +chmod +x "$OPENHAMCLOCK_DIR/start-kiosk.sh" + +# Create a stop script +cat > "$OPENHAMCLOCK_DIR/stop-kiosk.sh" << 'EOF' +#!/bin/bash +# Stop OpenHamClock kiosk mode +pkill -f chromium-browser +pkill -f unclutter +echo "OpenHamClock stopped." +EOF + +chmod +x "$OPENHAMCLOCK_DIR/stop-kiosk.sh" + +# Create a restart script +cat > "$OPENHAMCLOCK_DIR/restart-kiosk.sh" << 'EOF' +#!/bin/bash +# Restart OpenHamClock +$HOME/openhamclock/stop-kiosk.sh +sleep 2 +$HOME/openhamclock/start-kiosk.sh & +EOF + +chmod +x "$OPENHAMCLOCK_DIR/restart-kiosk.sh" + +# Create systemd service for headless operation (optional) +echo ">>> Creating systemd service (for headless operation)..." +sudo tee /etc/systemd/system/openhamclock.service > /dev/null << EOF +[Unit] +Description=OpenHamClock Kiosk +After=graphical-session.target + +[Service] +Type=simple +User=$CURRENT_USER +Environment=DISPLAY=:0 +ExecStart=/bin/bash $OPENHAMCLOCK_DIR/start-kiosk.sh +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=graphical-session.target +EOF + +# Disable screen blanking in config.txt +echo ">>> Configuring boot options..." +if ! grep -q "consoleblank=0" /boot/cmdline.txt 2>/dev/null; then + sudo sed -i '$ s/$/ consoleblank=0/' /boot/cmdline.txt 2>/dev/null || true +fi + +# Configure GPU memory for better graphics (optional) +if ! grep -q "gpu_mem=" /boot/config.txt 2>/dev/null; then + echo "gpu_mem=128" | sudo tee -a /boot/config.txt > /dev/null 2>/dev/null || true +fi + +echo "" +echo "========================================" +echo " Setup Complete!" +echo "========================================" +echo "" +echo "OpenHamClock has been installed to: $OPENHAMCLOCK_DIR" +echo "" +echo "Files created:" +echo " - $OPENHAMCLOCK_DIR/index.html (main application)" +echo " - $OPENHAMCLOCK_DIR/start-kiosk.sh (start in kiosk mode)" +echo " - $OPENHAMCLOCK_DIR/stop-kiosk.sh (stop kiosk)" +echo " - $OPENHAMCLOCK_DIR/restart-kiosk.sh (restart kiosk)" +echo "" +echo "Auto-start:" +echo " OpenHamClock will automatically start on next boot." +echo "" +echo "Manual commands:" +echo " Start: ~/openhamclock/start-kiosk.sh" +echo " Stop: ~/openhamclock/stop-kiosk.sh" +echo " Restart: ~/openhamclock/restart-kiosk.sh" +echo "" +echo "To disable auto-start:" +echo " rm ~/.config/autostart/openhamclock.desktop" +echo "" +echo "Reboot recommended to apply all changes." +echo "" +echo "73 de OpenHamClock!" +echo "" + +read -p "Would you like to reboot now? (y/N) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + sudo reboot +fi