DX Monitor

Real-time DX Cluster monitor with intelligent Telegram alerts and a web dashboard. Runs as a Docker container on any NAS or Linux server, or as a standalone Windows executable.

DX Monitor connects to any DX Cluster (CC11/VE7CC protocol), cross-references incoming spots against your own logbook, and fires Telegram alerts only for contacts that are relevant to you — new DXCC, new band, new mode, or unconfirmed QSLs. The web dashboard lets you configure everything without touching any file.

v13 Docker Python · Flask Docker Hub ea3tb/dx-monitor Port 8765

DX Monitor

Monitor de DX Cluster en tiempo real con alertas Telegram inteligentes y dashboard web. Se ejecuta como contenedor Docker en cualquier NAS o servidor Linux, o como ejecutable standalone para Windows.

DX Monitor se conecta a cualquier DX Cluster (protocolo CC11/VE7CC), cruza los spots entrantes con tu propio libro de QSO y lanza alertas Telegram solo para los contactos relevantes: nuevo DXCC, nueva banda, nuevo modo o QSL pendiente. El dashboard web permite configurarlo todo sin tocar ningún fichero.

v13 Docker Python · Flask Docker Hub ea3tb/dx-monitor Puerto 8765

Features

Real-time cluster
Continuous TCP connection, CC11/VE7CC protocol. Keepalive every 180 s, auto-reconnect on failure.
Smart alerts
Cross-checks spots against your log. Fires only for new DXCC, new band, new mode, unconfirmed QSL.
Mode inference
Detects FT8, FT4, CW, SSB from spot comment and frequency. Falls back to band plan (IARU R1/R2/R3).
Multi log source
HRD XML, Swisslog MDB, Log4OM SQLite, ADIF. Configurable reload interval.
Telegram bot
Bilingual alerts (ES/EN). Azimuth SP/LP, distance from locator. Local or UTC time.
Web dashboard
Dark/light theme, ES/EN language. Full config and filters from the browser. No env vars needed.
SSE live updates
Push from server to browser with zero polling. Alerts and log appear instantly.
Big CTY auto-update
Downloads cty.dat every 24 h from the official source. DXCC database always current.

Funcionalidades

Cluster en tiempo real
Conexión TCP continua, protocolo CC11/VE7CC. Keepalive cada 180 s, reconexión automática.
Alertas inteligentes
Cruza los spots con tu log. Solo dispara para nuevo DXCC, nueva banda, nuevo modo o QSL pendiente.
Inferencia de modo
Detecta FT8, FT4, CW, SSB del comentario y la frecuencia. Recurre al band plan (IARU R1/R2/R3).
Multi-fuente de log
HRD XML, Swisslog MDB, Log4OM SQLite, ADIF. Intervalo de recarga configurable.
Bot Telegram
Alertas bilingües (ES/EN). Azimut SP/LP, distancia desde locator. Hora local o UTC.
Dashboard web
Tema dark/light, idioma ES/EN. Configuración completa desde el navegador. Sin variables de entorno.
Actualizaciones SSE
Push del servidor al navegador sin polling. Alertas y log aparecen al instante.
Big CTY auto-update
Descarga cty.dat cada 24 h desde la fuente oficial. Base de datos DXCC siempre actualizada.

Architecture

A single Python process runs Flask (web) and background threads (cluster, log reload, CTY update) together in one container. No inter-process communication needed.

Main thread
Flask HTTP server — port 8765. Serves dashboard, REST API, SSE stream.
bucle_cluster
TCP connection to DX Cluster. Parses spots, applies filters, fires Telegram alerts. Keepalive sh/dx 1 every 180 s.
hilo_recarga_log
Reloads the logbook (XML/MDB/SQLite/ADIF) at the configured interval.
hilo_actualizar_cty
Downloads and parses Big CTY (cty.dat) every 24 hours.

Docker (NAS/Linux)

  • Image: ea3tb/dx-monitor:latest
  • Single container, port 8765
  • Volume: dx_docker_data
  • Hostfs mounted read-only at /hostfs
  • No env vars — config via dashboard
  • Restart: unless-stopped

Windows (standalone)

  • DXMonitor.exe — no install
  • System tray icon, auto-opens browser
  • Same Flask + thread architecture
  • Swisslog MDB: Access Engine 2016
  • Built with PyInstaller
  • Windows 10/11 64-bit

Arquitectura

Un único proceso Python ejecuta Flask (web) y hilos de fondo (cluster, recarga de log, actualización CTY) en el mismo contenedor. Sin comunicación entre procesos.

Hilo principal
Servidor HTTP Flask — puerto 8765. Sirve el dashboard, API REST y stream SSE.
bucle_cluster
Conexión TCP al DX Cluster. Parsea spots, aplica filtros, dispara alertas Telegram. Keepalive sh/dx 1 cada 180 s.
hilo_recarga_log
Recarga el libro de QSO (XML/MDB/SQLite/ADIF) al intervalo configurado.
hilo_actualizar_cty
Descarga y parsea Big CTY (cty.dat) cada 24 horas.

Docker (NAS/Linux)

  • Imagen: ea3tb/dx-monitor:latest
  • Contenedor único, puerto 8765
  • Volumen: dx_docker_data
  • Hostfs montado solo lectura en /hostfs
  • Sin variables de entorno
  • Restart: unless-stopped

Windows (standalone)

  • DXMonitor.exe — sin instalación
  • Icono en bandeja, abre navegador automáticamente
  • Misma arquitectura Flask + hilos
  • Swisslog MDB: Access Engine 2016
  • Compilado con PyInstaller
  • Windows 10/11 64-bit

DX Cluster

DX Monitor speaks the CC11/VE7CC protocol over plain TCP. One persistent connection per session.

Connection flow

TCP connect
Login callsign
Password (opt)
Spot stream
Keepalive 180 s

Auto-connect

If cluster_host and cluster_login are set in config.json, the cluster thread connects automatically on startup. Otherwise it waits for the Connect button on the dashboard.

Keepalive & reconnect

Sends sh/dx 1 every 180 seconds to prevent the server from closing the connection. On unexpected disconnect, waits 30 seconds and reconnects automatically. Manual disconnect (via dashboard) stops the reconnect loop.

Pause/resume

Connect and Disconnect buttons act on a threading.Event (_cluster_paused). The cluster thread respects this flag without restarting the process.

Anti-duplicate

Spots for the same (callsign, band, mode) are suppressed within a 10-minute sliding window. This prevents alert floods when multiple stations spot the same DX simultaneously.

Default cluster: hfpredictor.cronux.net:7304 — any CC11-compatible cluster works.

DX Cluster

DX Monitor habla el protocolo CC11/VE7CC sobre TCP plano. Una conexión persistente por sesión.

Flujo de conexión

TCP connect
Login callsign
Password (opt)
Stream de spots
Keepalive 180 s

Auto-conexión

Si cluster_host y cluster_login están en config.json, el hilo del cluster conecta automáticamente al arrancar. Si no, espera al botón Conectar del dashboard.

Keepalive y reconexión

Envía sh/dx 1 cada 180 segundos para evitar que el servidor cierre la conexión. Ante una desconexión inesperada, espera 30 s y reconecta automáticamente. La desconexión manual (desde el dashboard) detiene el bucle de reconexión.

Pausa/reanudación

Los botones Conectar y Desconectar actúan sobre un threading.Event (_cluster_paused). El hilo del cluster respeta este flag sin reiniciar el proceso.

Anti-duplicados

Los spots para el mismo (indicativo, banda, modo) se suprimen dentro de una ventana deslizante de 10 minutos. Evita inundaciones de alertas cuando varias estaciones spotean el mismo DX simultáneamente.

Cluster por defecto: hfpredictor.cronux.net:7304 — funciona cualquier cluster compatible CC11.

Telegram Alerts

DX Monitor sends formatted Telegram messages via the Bot API using your personal bot token and chat ID. All alert parameters are configured from the dashboard — no manual file editing.

Alert types

new_country
DXCC entity never worked before. Highest priority alert.
worked_country
DXCC entity worked but QSL not confirmed.
new_band
DXCC entity not worked on this band (any mode).
band_no_qsl
DXCC/band combination worked, QSL pending.
new_mode
DXCC/band not worked in this mode.
mode_no_qsl
Mode combination worked, QSL pending.

Alert content

Each alert includes: DX callsign and entity, frequency, band, mode, spotter, azimuth Short Path / Long Path (degrees), distance (km), and timestamp in local or UTC time.

Mode inference logic

Mode is determined in this order:

  1. Spot comment explicitly mentions CW / SSB / RTTY / FT8 / FT4 → use that mode
  2. No mode in comment + frequency matches FT8 or FT4 nominal (±1 kHz) → infer FT8 / FT4
  3. No mode, not FT8/FT4 → infer CW or SSB by band plan. Never RTTY.
  4. Frequency in exclusive CW segment → force CW regardless of comment

Language & time

Alert messages are sent in Spanish or English (alert_lang). Timestamps use the configured timezone (40+ grouped zones + manual IANA entry) in local or UTC mode (time_mode).

To get your chat ID: send any message to your bot, then visit
https://api.telegram.org/bot<TOKEN>/getUpdates

Alertas Telegram

DX Monitor envía mensajes Telegram formateados via Bot API usando tu token de bot personal y tu chat ID. Todos los parámetros se configuran desde el dashboard, sin editar ficheros.

Tipos de alerta

pais_nuevo
Entidad DXCC nunca trabajada. Alerta de máxima prioridad.
pais_trabajado
Entidad DXCC trabajada pero QSL no confirmada.
banda_nueva
Entidad DXCC no trabajada en esta banda (ningún modo).
banda_sin_qsl
Combinación DXCC/banda trabajada, QSL pendiente.
modo_nuevo
DXCC/banda no trabajados en este modo.
modo_sin_qsl
Combinación de modo trabajada, QSL pendiente.

Contenido de la alerta

Cada alerta incluye: indicativo y entidad DX, frecuencia, banda, modo, spotter, azimut vía corta / vía larga (grados), distancia (km) y timestamp en hora local o UTC.

Lógica de inferencia de modo

El modo se determina en este orden:

  1. El comentario del spot menciona explícitamente CW / SSB / RTTY / FT8 / FT4 → se usa ese modo
  2. Sin modo en el comentario + frecuencia coincide con nominal FT8 o FT4 (±1 kHz) → se infiere FT8 / FT4
  3. Sin modo, no FT8/FT4 → se infiere CW o SSB por band plan. Nunca RTTY.
  4. Frecuencia en segmento exclusivo CW → se fuerza CW independientemente del comentario

Idioma y hora

Los mensajes se envían en español o inglés (alert_lang). Los timestamps usan la zona horaria configurada (40+ zonas agrupadas + campo IANA manual) en modo local o UTC (time_mode).

Para obtener tu chat ID: envía cualquier mensaje a tu bot y visita
https://api.telegram.org/bot<TOKEN>/getUpdates

Log Sources

DX Monitor reads your logbook to know which DXCCs, bands and modes you have already worked. Four formats are supported and selected from the dashboard.

FormatNotes
HRD XMLHam Radio Deluxe — automatic background export. DX Monitor watches a directory and reloads XML files at the configured interval. Path navigable from the dashboard browser.
Swisslog MDBMicrosoft Access database (.mdb). Requires Access Database Engine 2016 on Windows. The app offers automatic installation if missing.
Log4OM SQLiteLog4OM v2 — reads the SQLite database directly. No export needed.
ADIFUniversal format exported by any logging software. Reload on demand or at interval.

Reload interval

Configurable in minutes from the dashboard. The log thread wakes at each interval, reads the source, and updates the in-memory DXCC/band/mode database used by the alert engine.

Docker path handling

Inside the container, the host filesystem is mounted read-only at /hostfs. The function host_path() transparently converts host paths entered in the dashboard to their /hostfs-prefixed equivalents. The directory browser (📂) navigates /hostfs and displays the real host paths.

For SMB log files from a Windows PC: mount the share as a Docker volume and point the log path to the mount point.

Fuentes de log

DX Monitor lee tu libro de QSO para saber qué DXCCs, bandas y modos ya tienes trabajados. Se soportan cuatro formatos, seleccionables desde el dashboard.

FormatoNotas
HRD XMLHam Radio Deluxe — exportación automática en segundo plano. DX Monitor vigila un directorio y recarga los ficheros XML al intervalo configurado. Ruta navegable desde el explorador del dashboard.
Swisslog MDBBase de datos Microsoft Access (.mdb). Requiere Access Database Engine 2016 en Windows. La app ofrece instalación automática si falta.
Log4OM SQLiteLog4OM v2 — lee la base de datos SQLite directamente. Sin necesidad de exportar.
ADIFFormato universal exportado por cualquier programa de log. Recarga bajo demanda o al intervalo configurado.

Intervalo de recarga

Configurable en minutos desde el dashboard. El hilo de log se activa en cada intervalo, lee la fuente y actualiza la base de datos en memoria de DXCC/banda/modo que usa el motor de alertas.

Paths en Docker

Dentro del contenedor, el filesystem del host se monta en solo lectura en /hostfs. La función host_path() convierte de forma transparente los paths del host introducidos en el dashboard a sus equivalentes prefijados con /hostfs. El explorador de directorios (📂) navega /hostfs y muestra los paths reales del host.

Para logs por SMB desde un PC Windows: monta el recurso compartido como volumen Docker y apunta el log path al punto de montaje.

Real-time Log & Alerts

The bottom of the dashboard shows two live panels updated by SSE — no page refresh, no polling.

How SSE works

Spot received
_escribir_status()
sse_push()
All SSE clients
Dashboard updates

No watcher thread. _escribir_status() writes status.json atomically with os.replace and calls sse_push() directly — zero latency between event and browser update. If the connection drops, the dashboard reconnects automatically after 5 seconds.

Live DX alerts panel

Each incoming alert that passes all filters appears instantly: callsign, entity, frequency, band, mode, azimuth SP/LP, distance, spotter, and timestamp. Anti-duplicate window (10 min) prevents repeated entries for the same spot.

System log panel

Last 40 lines of dx_monitor.log, color-coded by level:

2025-08-14 09:31:02 [INFO] Connected to cluster hfpredictor.cronux.net:7304
2025-08-14 09:31:45 [ALERT] new_country · 3B8FA · 14074 kHz · FT8 · 160° SP / 340° LP · 9847 km
2025-08-14 09:32:11 [INFO] new_band · PY0F · 21074 kHz · FT8
2025-08-14 09:35:00 [WARN] Keepalive sent
2025-08-14 09:42:17 [INFO] CTY updated — 3447 entities loaded

Log lines are translated to the dashboard language (ES/EN), including internal key names like pais_nuevo, modo_sin_qsl, etc. Log timestamps respect the timezone configured in config.json.

Log y alertas en tiempo real

La parte inferior del dashboard muestra dos paneles en vivo actualizados por SSE — sin recarga de página, sin polling.

Cómo funciona SSE

Spot recibido
_escribir_status()
sse_push()
Todos los clientes SSE
Dashboard actualizado

Sin hilo vigilante. _escribir_status() escribe status.json atómicamente con os.replace y llama a sse_push() directamente — latencia cero entre evento y actualización en el navegador. Si se pierde la conexión, el dashboard reconecta automáticamente tras 5 segundos.

Panel de alertas DX en vivo

Cada alerta entrante que pasa todos los filtros aparece al instante: indicativo, entidad, frecuencia, banda, modo, azimut SP/LP, distancia, spotter y timestamp. La ventana antiduplicados (10 min) evita entradas repetidas para el mismo spot.

Panel de log del sistema

Últimas 40 líneas de dx_monitor.log, coloreadas por nivel:

2025-08-14 09:31:02 [INFO] Conectado al cluster hfpredictor.cronux.net:7304
2025-08-14 09:31:45 [ALERT] pais_nuevo · 3B8FA · 14074 kHz · FT8 · 160° SP / 340° LP · 9847 km
2025-08-14 09:32:11 [INFO] banda_nueva · PY0F · 21074 kHz · FT8
2025-08-14 09:35:00 [WARN] Keepalive enviado
2025-08-14 09:42:17 [INFO] CTY actualizado — 3447 entidades cargadas

Las líneas del log se traducen al idioma del dashboard (ES/EN), incluidas las claves internas como pais_nuevo, modo_sin_qsl, etc. Los timestamps respetan la zona horaria configurada en config.json.

Docker Installation

Quick start

Create a docker-compose.yml:

services:
  dxmonitor:
    image: ea3tb/dx-monitor:latest
    container_name: dx_monitor_docker
    restart: unless-stopped
    ports:
      - "8765:8765"
    volumes:
      - /:/hostfs:ro
      - dx_docker_data:/opt/dx_monitor_docker
    networks:
      - dx_net

volumes:
  dx_docker_data:
    driver: local

networks:
  dx_net:
    driver: bridge
# Deploy
docker compose up -d

# Open dashboard
http://<nas-ip>:8765

Useful commands

# Real-time logs
docker logs -f dx_monitor_docker

# Status
docker compose ps

# Stop / restart
docker compose down
docker compose up -d

# Shell access
docker exec -it dx_monitor_docker bash

# View active config
docker exec dx_monitor_docker cat /opt/dx_monitor_docker/config.json

Update to latest image

# Pull new image and redeploy
docker compose pull
docker compose up -d
First run: the volume is empty. config.json is created with empty defaults. Fill in callsign, log path, cluster and Telegram from the dashboard — no file editing needed.

Instalación Docker

Inicio rápido

Crea un docker-compose.yml:

services:
  dxmonitor:
    image: ea3tb/dx-monitor:latest
    container_name: dx_monitor_docker
    restart: unless-stopped
    ports:
      - "8765:8765"
    volumes:
      - /:/hostfs:ro
      - dx_docker_data:/opt/dx_monitor_docker
    networks:
      - dx_net

volumes:
  dx_docker_data:
    driver: local

networks:
  dx_net:
    driver: bridge
# Desplegar
docker compose up -d

# Abrir dashboard
http://<ip-del-nas>:8765

Comandos útiles

# Logs en tiempo real
docker logs -f dx_monitor_docker

# Estado
docker compose ps

# Parar / reiniciar
docker compose down
docker compose up -d

# Acceso al shell
docker exec -it dx_monitor_docker bash

# Ver config activa
docker exec dx_monitor_docker cat /opt/dx_monitor_docker/config.json

Actualizar a la última imagen

# Descargar nueva imagen y redesplegar
docker compose pull
docker compose up -d
Primera ejecución: el volumen está vacío. config.json se crea con defaults vacíos. Rellena indicativo, ruta del log, cluster y Telegram desde el dashboard — sin editar ficheros.

Configuration

All settings are stored in /opt/dx_monitor_docker/config.json and edited from the dashboard. Fields save automatically on blur.

FieldDescription
callsignYour amateur radio callsign
locatorMaidenhead grid locator — auto-calculates lat/lon for azimuth and distance
cluster_hostDX Cluster hostname or IP
cluster_portTCP port (usually 7300 or 7304)
cluster_loginLogin callsign sent to the cluster
cluster_passwordCluster password, hideable with 👁 button
telegram_tokenBot API token, hideable with 👁 button
telegram_chat_idDestination chat ID
alert_langAlert message language: es or en
timezone40+ grouped zones + manual IANA entry (e.g. Europe/Madrid)
time_modelocal (per timezone) or utc
hrd_xml_dirDirectory containing HRD XML export files
hrd_xml_globGlob pattern to match XML files (e.g. *.xml)

Configuración

Todos los ajustes se guardan en /opt/dx_monitor_docker/config.json y se editan desde el dashboard. Los campos se guardan automáticamente al perder el foco.

CampoDescripción
callsignTu indicativo de radioaficionado
locatorLocator Maidenhead — calcula automáticamente lat/lon para azimut y distancia
cluster_hostHostname o IP del DX Cluster
cluster_portPuerto TCP (habitualmente 7300 o 7304)
cluster_loginIndicativo de login enviado al cluster
cluster_passwordContraseña del cluster, ocultable con el botón 👁
telegram_tokenToken de la Bot API, ocultable con el botón 👁
telegram_chat_idChat ID de destino
alert_langIdioma de las alertas: es o en
timezone40+ zonas agrupadas + campo IANA manual (ej. Europe/Madrid)
time_modelocal (según timezone) o utc
hrd_xml_dirDirectorio con los ficheros de exportación XML de HRD
hrd_xml_globPatrón glob para los ficheros XML (ej. *.xml)

Alert Filters

Stored in flags.json. All toggles are available from the dashboard and take effect immediately without restarting.

FilterValuesDescription
iaru_regionR1 / R2 / R3Band plan used for mode inference and frequency validation
bandas_activas160m – 23cmOnly spots on selected bands trigger alerts
modos_activosCW, SSB, RTTY, FT8, FT4Only spots in selected modes trigger alerts
pais_nuevoon/offAlert for new DXCC entity
pais_trabajadoon/offAlert for worked DXCC without QSL
banda_nuevaon/offAlert for new band on known DXCC
banda_sin_qslon/offAlert for band worked, QSL pending
modo_nuevoon/offAlert for new mode
modo_sin_qslon/offAlert for mode worked, QSL pending

Filtros de alerta

Guardados en flags.json. Todos los toggles están disponibles desde el dashboard y surten efecto inmediatamente sin reiniciar.

FiltroValoresDescripción
iaru_regionR1 / R2 / R3Band plan usado para inferencia de modo y validación de frecuencia
bandas_activas160m – 23cmSolo los spots en las bandas seleccionadas generan alertas
modos_activosCW, SSB, RTTY, FT8, FT4Solo los spots en los modos seleccionados generan alertas
pais_nuevoon/offAlerta para nueva entidad DXCC
pais_trabajadoon/offAlerta para DXCC trabajado sin QSL
banda_nuevaon/offAlerta para nueva banda en DXCC conocido
banda_sin_qslon/offAlerta para banda trabajada con QSL pendiente
modo_nuevoon/offAlerta para nuevo modo
modo_sin_qslon/offAlerta para modo trabajado con QSL pendiente

File Structure

/opt/dev_dx_monitor_docker/ ├── docker-compose.yml # end-user compose (Hub image) ├── README.md └── app/ ├── Dockerfile ├── requirements.txt # flask, requests ├── main.py # single process v13 ├── band_plans.py # IARU R1/R2/R3 band plans ├── log_readers.py # HRD/MDB/SQLite/ADIF readers └── templates/ └── dashboard.html # ES/EN dark/light dashboard # Persistent data (Docker volume dx_docker_data) /opt/dx_monitor_docker/ ├── config.json # all user settings ├── flags.json # alert filters ├── status.json # current state (atomic write) ├── dx_monitor.log # application log └── cty.dat # Big CTY database

Estructura de ficheros

/opt/dev_dx_monitor_docker/ ├── docker-compose.yml # compose usuario final (imagen Hub) ├── README.md └── app/ ├── Dockerfile ├── requirements.txt # flask, requests ├── main.py # proceso único v13 ├── band_plans.py # planes IARU R1/R2/R3 ├── log_readers.py # lectores HRD/MDB/SQLite/ADIF └── templates/ └── dashboard.html # dashboard ES/EN dark/light # Datos persistentes (volumen Docker dx_docker_data) /opt/dx_monitor_docker/ ├── config.json # toda la configuración del usuario ├── flags.json # filtros de alerta ├── status.json # estado actual (escritura atómica) ├── dx_monitor.log # log de la aplicación └── cty.dat # base de datos Big CTY