📡 Scanner Plugin – Documentation v4.2c
📡

Scanner Plugin

Plugin Documentation for FM-DX-Webserver
Client Script: scanner.js
Server Script: scanner_server.js
Version: 4.2c
Author: Highpoint · powered by PE5PVB
Last Update: March 2026
Repository: github.com/Highpoint2000/webserver-scanner
v4.2c

Table of Contents

1What is the Scanner Plugin?3
2Architecture – Components and Data Flow4
3Installation5
4Configuration – The Scanner Wizard6
4.1General Settings6
4.2Sensitivity & Scan Timing7
4.3Scan Modes8
4.4Spectrum & Difference Scan9
4.5Logging Settings10
4.6FMLIST Integration11
4.7Multiple Configuration Files12
5User Interface13
5.1Auto Scan Button13
5.2Scanner Controls (Sensitivity, Mode, Hold Time)14
5.3Search Buttons14
5.4Map Viewer & URDS Log Validator Buttons15
6Technical Deep Dive16
6.1Scan Algorithm16
6.2Sensitivity Calibration17
6.3Spectrum & Difference Scan Internals18
6.4WebSocket Communication19
6.5Authentication & Access Control20
6.6Logging – HTML, CSV, FMLIST21
6.7Blacklist & Whitelist22
6.8Antenna Switching23
6.9Dynamic Config File Watcher23
7All Configuration Parameters Reference24
8Important Notes & Known Issues27
9Version History (Summary)28

1 · What is the Scanner Plugin?

The Scanner Plugin is a comprehensive add-on for the FM-DX-Webserver that transforms a passive FM receiver into an intelligent, automated FM DX logging station. It provides automatic frequency scanning across the FM band (or a configurable sub-range), real-time RDS-based station identification, sophisticated signal filtering, and automated logging to multiple output formats including HTML, URDS CSV, and FMLIST.

The plugin runs as two cooperating scripts:

Key capabilities at a glance:

Compatibility: Version 4.2c is compatible with FM-DX-Webserver ≥ 1.4.0(a) and FMDX Connector 2.0a. It uses the new plugins_api for privileged tuner commands, enabling operation even in Admin Lock mode.

2 · Architecture – Components and Data Flow

+----------------------------------------------------------------------+ | FM-DX-Webserver Node.js Process | | | | +----------------------------------------------------------------+ | | | scanner_server.js | | | | | | | | +--------------------+ +--------------------------------+ | | | | | Config Manager | | Scan Engine | | | | | | | | | | | | | | scanner.json -----+->| startScan(direction) | | | | | | scanner_X.json | | AutoScan() | | | | | | (hot-reload) | | startSearch(direction) | | | | | | | | stopAutoScan() | | | | | | fs.watchFile -----+ | checkStereo() | | | | | +--------------------+ | PE5PVBlog() | | | | | +---------------+----------------+ | | | | +--------------------+ | | | | | | Signal Analysis |<-----------------+ | | | | | | | | | | | SensitivityCalib | +--------------------------------+ | | | | | SpectrumFilter | | Log Writers | | | | | | DifferenceFilter | | | | | | | | BlacklistFilter +->| writeHTMLLogEntry() | | | | | | WhitelistFilter | | writeCSVLogEntry() | | | | | +--------------------+ | writeLogFMLIST() | | | | | +--------------------------------+ | | | | | | | | WebSocket /text -----------> TEF Tuner (sendDataToClient) | | | | WebSocket /data_plugins <--> Browser Clients | | | | plugins_api (privileged) --> Tuner (Admin Lock mode) | | | +----------------------------------------------------------------+ | | | +----------------------------------------------------------------------+ +----------------------------------------------------------------------+ | User's Browser | | | | +----------------------------------------------------------------+ | | | scanner.js | | | | | | | | setupSendSocket() --------> WebSocket /data_plugins | | | | handleWebSocketMessage() <-- Server broadcasts | | | | ScannerButtons() -- Auto Scan button + long-press panel | | | | SearchButtons() -- << / >> search buttons | | | | createScannerControls() -- Sensitivity / Mode / HoldTime | | | | initSignalUnitWatcher() -- live dBf / dBuV / dBm sync | | | | initializeMapViewerButton() -- URDS Map Viewer | | | | initializeValidatorButton() -- URDS Log Validator | | | +----------------------------------------------------------------+ | | | +----------------------------------------------------------------------+

3 · Installation

  1. Download the latest release as a ZIP archive from the GitHub releases page.
  2. Unpack all files from the plugins/ folder inside the ZIP into:
    …/fm-dx-webserver-main/plugins/
  3. Stop or close the FM-DX-Webserver if it is running.
  4. Start the webserver with npm run webserver in a Node.js console and check the console output for errors.
  5. Open the webserver settings page and activate the Scanner plugin in the plugin list.
  6. Stop the webserver again.
  7. Start the webserver again with npm run webserver and verify the console output — the scanner should now initialise and print its configuration summary.
  8. Edit the automatically created configuration file at:
    …/fm-dx-webserver-main/plugins_configs/scanner.json
    (Use the Scanner Wizard — see §4)
  9. Stop the webserver, then start it once more for the configuration to take full effect.
Tip: After the first start the scanner.json is created automatically with sensible defaults. You do not need to create it manually. All subsequent changes to the JSON are picked up on-the-fly without a server restart (see §6.9).
Speaker module: If you enable acoustic beep signalling (BEEP_CONTROL: true), the speaker npm module will be installed automatically on first start. With newer Node.js versions this may fail — downgrade Node.js if acoustic signalling is required.

4 · Configuration – The Scanner Wizard

The recommended way to create or modify the scanner configuration is the Scanner Wizard, an interactive web tool that generates a ready-to-use scanner.json file with all options described inline:

🧙 https://tef.noobish.eu/logos/scanner_wizard.html
Open the wizard, configure your settings, copy the generated JSON into plugins_configs/scanner.json and restart the server.

Alternatively you can edit the JSON file directly. The sections below describe every parameter group.

4.1 · General Settings

ParameterDefaultDescription
Scanmode11 = Online mode (uses fmdx.org TX data for station identification). 0 = Offline mode (no network lookups, station fields left empty).
Autoscan_PE5PVB_ModefalseSet to true to use the built-in firmware scan function of PE5PVB / TEF6686 ESP32 receivers. Also recommended for FMDX Scanner Mode.
Search_PE5PVB_ModefalseSet to true to use the PE5PVB firmware search function for the ◀◀ / ▶▶ buttons.
StartAutoScan"off""off" – manual start only. "on" – scan starts automatically with the webserver. "auto" – scan starts 10 s after the last user disconnects and stops when a user reconnects.
AntennaSwitch"off""on" enables automatic antenna cycling at the upper band limit. Requires at least 2 antennas configured in the webserver.
OnlyScanHoldTime"off""on" forces the scanner to always wait the full ScanHoldTime before moving on, regardless of RDS data. Useful for FM-DX monitor mode.

4.2 · Sensitivity & Scan Timing

ParameterDefaultDescription
defaultSensitivityValue30Signal threshold in dBf / dBµV / dBm (unit set by SignalStrengthUnit). Valid values depend on the unit — see table below. If SensitivityCalibrationFrequenz is set, this becomes the threshold above the noise floor (1–10 recommended).
SensitivityCalibrationFrequenz""A frequency in MHz (e.g. "87.3") on which the scanner measures the current noise floor before each scan pass and adds defaultSensitivityValue to derive the actual threshold dynamically. Leave empty to disable.
defaultScanHoldTime5Time in seconds the scanner pauses on a detected frequency before moving on. Valid: 1, 2, 3, 4, 5, 7, 10, 15, 20, 30. Not used in PE5PVB mode.
scanIntervalTime500Milliseconds between frequency steps during scanning (max 1000). A higher value increases detection reliability at the cost of scan speed.
scanBandwith0IF bandwidth in Hz during scanning. 0 = auto. Supported values: 56000, 64000, 72000, 84000, 97000, 114000, 133000, 151000, 184000, 200000, 217000, 236000, 254000, 287000, 311000.
tuningLowerLimit""Lower scan band limit in MHz (e.g. "87.5"). Overrides the webserver setting. Leave empty to use the webserver value.
tuningUpperLimit""Upper scan band limit in MHz (e.g. "108.0"). Maximum is 108.0. Leave empty to use the webserver value.
SignalStrengthUnit"dBf"Unit for all signal strength display and thresholds. Accepted values: "dBf", "dBµV", "dBm". Must match the webserver's configured display unit.

Sensitivity value ranges by unit

UnitTypical rangeNotes
dBf1 – 80Native hardware unit; no conversion applied
dBµV1 – 80Internally offset by +10.875 before sending to tuner
dBm−115 – −40Internally offset by +119.75 before sending to tuner

4.3 · Scan Modes

The defaultScannerMode parameter (default: "normal") sets the active scan mode at startup. The user can change it live via the scanner controls dropdown. Available modes:

ModeRequiresDescription
normalSteps through all frequencies in the configured band at 0.1 MHz increments (0.01 MHz below 74 MHz), stops when signal + RDS detected.
blacklistEnableBlacklist: trueSame as normal but skips frequencies listed in blacklist.txt.
whitelistEnableWhitelist: trueScans only the frequencies listed in whitelist.txt at 0.01 MHz increments.
spectrumSpectrum Graph plugin + EnableSpectrumScan: trueUses the spectrum array to select only frequencies above the sensitivity threshold and below the limiter value. Much faster than normal scan.
spectrumBLspectrum + EnableBlacklist: trueSpectrum scan with blacklist filtering applied.
differenceSpectrum Graph plugin + EnableDifferenceScan: trueScans only frequencies whose signal has changed by more than SpectrumChangeValue dBf/dBµV compared to the previous scan pass. Ideal for detecting tropospheric events.
differenceBLdifference + EnableBlacklist: trueDifference scan with blacklist filtering applied.

4.4 · Spectrum & Difference Scan

The spectrum and difference scan modes require the Spectrum Graph plugin to be installed and active. In addition, the variable rescanDelay in the SpectrumGraph.json must be set to 0 so that a fresh spectrum is captured automatically after each full band sweep.

ParameterDefaultDescription
EnableSpectrumScanfalseEnables the spectrum and spectrumBL modes.
EnableDifferenceScanfalseEnables the difference and differenceBL modes.
SpectrumLimiterValue50Upper signal strength limit in dBf/dBµV. Frequencies above this value (strong local stations) are excluded from spectrum/difference scan. Also used as the X-axis limiter displayed in the scanner UI.
SpectrumPlusMinusValue60Signal threshold in dBf/dBµV at which the adjacent channels (±0.1 MHz) of a strong local station are also filtered out.
SpectrumChangeValue3Minimum signal change in dBf/dBµV between scan passes required for a frequency to be included in difference scan. Set to 0 to disable the difference filter.
How spectrum scan works: Before each scan pass, the Spectrum Graph plugin provides a full-band power array (sigArray). The scanner filters this array to find frequencies where the signal is above Sensitivity but below SpectrumLimiterValue, excluding the ±0.1 MHz neighbours of any station above SpectrumPlusMinusValue. Only those surviving frequencies are tuned and checked for RDS.

4.5 · Logging Settings

ParameterDefaultDescription
HTMLlogOnlyIDtrueWhen true, only stations with a known fmdx.org station ID are written to the filtered HTML log.
HTMLlogRAWfalseWhen true, a raw (unfiltered) HTML log entry is also written for every received PI code, regardless of identification status.
HTMLOnlyFirstLogfalseWhen true, each station is logged only once per session (first-seen mode). Duplicates are suppressed.
CSVcreatetrueEnables creation of the URDS CSV log file in /web/logs/ and activates the Map Viewer and Validator buttons in the UI.
CSVcompletePStrueWhen true, waits for a complete (error-free) PS name before writing the CSV entry. When false, writes immediately and updates the last entry if the PS improves.
UTCtimetrueWhen true, all timestamps in HTML and CSV logs are in UTC. When false, local system time is used.
Log_BlacklistfalseEnables the log blacklist (blacklist_log.txt). Frequencies and/or PI codes listed there are excluded from HTML and CSV log entries, including manual search results.

Log file locations

…/fm-dx-webserver-main/web/logs/ ├── CSVfilename ← name of current CSV file (or "NoFileName") ├── YYYYMMDDTHHMMSS_fm_rds.csv ← URDS CSV log (new file per session) ├── SCANNER_YYYY-MM-DD_filtered.html ← filtered HTML log └── SCANNER_YYYY-MM-DD.html ← raw HTML log (if HTMLlogRAW = true)

4.6 · FMLIST Integration

The scanner can automatically submit DX log entries to FMLIST when a station exceeds the configured distance thresholds. A valid OM ID and a webserver UUID token are required.

ParameterDefaultDescription
FMLIST_OM_ID""Your FMLIST OM ID (e.g. "1234"). If empty, the value from the webserver's FMLIST INTEGRATION settings is used.
FMLIST_Autolog"off""off" – disabled. "on" – log all identified DX stations (hides manual log button). "auto" – log only during autoscan mode.
FMLIST_MinDistance200Minimum station distance in km for an autolog entry. Minimum enforced value: 200 km.
FMLIST_MaxDistance2000Maximum station distance in km. Stations beyond this are not logged.
FMLIST_LogInterval60Minimum interval in minutes before the same station ID can be logged again. Minimum: 60 minutes.
FMLIST_CanLogServer""Address of a central CanLogServer (e.g. "127.0.0.1:2000") that manages log repetition across multiple webserver instances. When set, the local FMLIST_LogInterval is ignored.
FMLIST_ShortServerName""Short server identifier (max 10 characters) prepended to FMLIST log messages (e.g. "DXserver01").
FMLIST_BlacklistfalseEnables the FMLIST blacklist (blacklist_fmlist.txt). Frequencies and/or PI codes listed there are excluded from FMLIST submissions.
UUID required: The webserver's UUID token (config.identification.token) must be set and non-null. Without it, FMLIST submissions are blocked and an error is logged. Stations beyond 900 km are automatically tagged as Sporadic E type on FMLIST.

4.7 · Multiple Configuration Files

Since version 4.2, the scanner supports multiple named configuration profiles. Additional profiles are stored as extra JSON files in the plugins_configs/ folder following the naming pattern:

plugins_configs/
├── scanner.json          ← default profile
├── scanner_Dave.json     ← profile "Dave"
├── scanner_SpE.json      ← profile "SpE"
└── scanner_Tropo.json    ← profile "Tropo"

When more than one scanner config file exists, a drop-down selector appears in the webserver settings panel (admin login required). Selecting a profile instantly loads and applies all its settings — including scan mode, sensitivity, FMLIST options, and logging preferences — without restarting the server. The active profile is persisted across server restarts in plugins/Scanner/scanner_state.json.

Note: The dropdown is only visible if at least two scanner configuration files are present in plugins_configs/. Changes to any config file on disk are picked up immediately via the file watcher (§6.9).

5 · User Interface

5.1 · Auto Scan Button

The Auto Scan button is injected into the FM-DX-Webserver's main tuning panel. Its behaviour depends on press duration:

InteractionEffectRequirement
Short pressStarts / stops the automatic scanning processAdmin or Tune authentication
Long press (≥ 1 s)Opens or closes the scanner control panel (Sensitivity / Mode / Hold Time dropdowns)Admin authentication

While autoscan is active:

Authentication required: The autoscan start/stop and the scanner control panel require the user to be logged in as Administrator or as a Tune-authenticated user. Without authentication, a toast warning is shown and no action is taken.

5.2 · Scanner Controls (Sensitivity, Mode, Hold Time)

When the scanner control panel is open (long-press on Auto Scan button), three dropdown controls appear:

ControlTitle attributeDescription
SensitivityScanner SensitivitySets the minimum signal threshold. Available values depend on the active SignalStrengthUnit. In PE5PVB mode, shows values 1–30 without a unit suffix.
Scanner ModeScanner ModeSelects the active scan mode. Only modes enabled in the configuration (blacklist, whitelist, spectrum, etc.) appear in the list.
Scanhold TimeScanhold TimeSets the hold time in seconds: 1, 2, 3, 4, 5, 7, 10, 15, 20, 30.

Changes to any dropdown are sent immediately to the server via WebSocket. The server responds with an updated broadcast that refreshes all connected clients. The control panel visibility is persisted in a browser cookie (scannerControlsStatus) for 7 days.

The scanner client also watches the webserver's signal-unit selector (#signal-selector-input) every 250 ms. When the unit changes (dBf → dBµV → dBm), the sensitivity dropdown is rebuilt with the correct value list and labels, and the currently displayed sensitivity value is recalculated accordingly.

5.3 · Search Buttons

Two search buttons (◀◀ and ▶▶) are inserted next to the standard frequency step buttons:

Search buttons are hidden during active autoscan. They are always available without admin login (unrestricted command), but a warning is shown if the tuner is locked to admin.

5.4 · Map Viewer & URDS Log Validator Buttons

If CSVcreate: true is set and a CSV log file exists, two additional icon buttons are injected into the plugin panel (desktop only — hidden on touch devices):

ButtonIconAction
Mapviewer🌐 globeOpens the current URDS CSV log in the URDS Map Viewer (via CORS proxy at cors-proxy.de:13128). If no CSV file is available, a warning toast is shown.
Validator✅ file-circle-checkOpens the current CSV log in the URDS Log Validator (via CORS proxy). Validates the URDS format and reports any issues.

6 · Technical Deep Dive

6.1 · Scan Algorithm

The scan engine runs as a setInterval loop on the server with a configurable tick rate (scanIntervalTime, default 500 ms). Each tick calls updateFrequency(), which:

  1. Advances currentFrequency by the appropriate step (0.01 MHz below 74 MHz, 0.1 MHz above — or 0.01 MHz in whitelist mode)
  2. Applies the active filter (blacklist / whitelist / spectrum / difference) to skip ineligible frequencies
  3. Calls sendDataToClient(currentFrequency) to retune the receiver
  4. Waits for the next tick and evaluates the incoming signal data via checkStereo() or PE5PVBlog()
AutoScan() called │ ├─ SensitivityCalibrationFrequenz set? │ YES → SensitivityValueCalibration() │ tune to calibration freq │ wait for signal to stabilise (2 s) │ Sensitivity = noise + defaultSensitivityValue │ NO → use defaultSensitivityValue directly │ ├─ startScan('up') │ clearInterval(scanInterval) │ isScanning = true │ updateFrequency() – first tick immediately │ scanInterval = setInterval(updateFrequency, scanIntervalTime) │ └─ On each tick: advance frequency apply mode filter (BL / WL / spectrum / difference) sendDataToClient(freq) ← handleSocketMessage() receives tuner data ← checkStereo() / PE5PVBlog() evaluates signal signal > Sensitivity AND RDS/stereo? YES → hold, log, then startScan('up') NO → next tick

Band-end wrap behaviour

When currentFrequency exceeds tuningUpperLimit during autoscan, the handleBandEnd() routine is called. It:

  1. Stops the interval and locks the scanner via isCalibrating = true
  2. Sends the next-antenna command if antenna switching is enabled
  3. Jumps to tuningLowerLimit and waits for a fresh spectrum scan (up to 15 s)
  4. Runs SensitivityValueCalibration() again if configured
  5. Releases the lock and restarts startScan('up')

In normal / blacklist / whitelist modes the scanner remembers the last tuned frequency across band-end restarts and continues from where it left off rather than returning to the lower limit, avoiding repeated scanning of already-checked frequencies within the same autoscan session.

6.2 · Sensitivity Calibration

When SensitivityCalibrationFrequenz is set, the scanner performs an automatic noise-floor measurement before every full band sweep instead of using a fixed threshold.

async function SensitivityValueCalibration() {
  isCalibrating = true;
  try {
    clearInterval(scanInterval);

    // --- Path 1: spectrum array available ---
    if (sigArray.length > 0) {
      const entry = sigArray.find(
        i => parseFloat(i.freq).toFixed(2) === targetFreqStr);
      if (entry) {
        Sensitivity = Math.round(parseFloat(entry.sig))
                    + Math.round(defaultSensitivityValue);
      }
    }

    // --- Path 2: physical tune + 2 s wait ---
    else {
      sendDataToClient(calibFreq);
      // wait until tuner reports calibration frequency
      while (Math.abs(parseFloat(freq) - calibFreq) >= 0.05) {
        await sleep(100);
      }
      await sleep(2000); // stabilise
      Sensitivity = Math.round(strength)
                  + Math.round(defaultSensitivityValue);
    }

    // broadcast updated Sensitivity to all clients
    DataPluginsSocket.send(JSON.stringify(
      createMessage('broadcast', '255.255.255.255',
        Scan, '', Sensitivity, ScannerMode, ScanHoldTime, FMLIST_Autolog)));
  } finally {
    isCalibrating = false;
  }
}
When calibration runs: At the start of every autoscan pass (band-end wrap), after a manual sensitivity change, and after a scanner mode change. The isCalibrating flag blocks checkStereo() from interrupting the measurement with a false detection.

6.3 · Spectrum & Difference Scan Internals

Both spectrum-based modes rely on the sigArray provided by the Spectrum Graph plugin via the data_plugins WebSocket. The scanner listens for type: 'sigArray' messages and processes them in handleDataPluginsMessage():

Step 1 – Local station masking

// Find all entries above SpectrumLimiterValue
const primaryFreqs = sigArray.filter(e =>
  e.sig > SpectrumLimiterValue &&
  Math.round(e.freq * 100) % 10 === 0  // primary 0.1 MHz grid only
);

// Extend mask to ±0.1 MHz neighbours above SpectrumPlusMinusValue
primaryFreqs.forEach(p => {
  if (p.sig >= SpectrumPlusMinusValue) {
    sigArray.filter(e =>
      e.freq >= p.freq - 0.1 && e.freq <= p.freq + 0.1
    ).forEach(e => extendedMask.add(e.freq));
  }
});

// sigArraySpectrum = everything NOT masked
sigArraySpectrum = sigArray.filter(e =>
  !extendedMask.has(e.freq) && !primaryFreqs.some(p => p.freq === e.freq)
);

Step 2 – Difference filter

// freqMap2 = previous scan pass signals for current antenna
sigArrayDifference = sigArraySpectrum.filter(item => {
  const sig  = parseFloat(item.sig);
  if (sig < Sensitivity) return false;            // below threshold
  if (!freqMap2.has(item.freq)) return true;     // new frequency
  return Math.abs(sig - freqMap2.get(item.freq))  // changed by
       > SpectrumChangeValue;                      // SpectrumChangeValue
});

Step 3 – Per-antenna baseline storage

Four baseline arrays (sigArraySave0sigArraySave3) store the previous spectrum pass for each of up to four antennas independently. This prevents false difference detections when the antenna is switched.

6.4 · WebSocket Communication

All real-time communication between server and clients uses the FM-DX-Webserver's /data_plugins WebSocket endpoint. Messages follow this envelope:

{
  "type":   "Scanner",
  "value":  {
    "status":             "request" | "response" | "broadcast" | "command",
    "Scan":               "on" | "off",
    "Sensitivity":        number,          // internal dBf value
    "ScannerMode":        "normal" | ...,
    "ScanHoldTime":       number,          // seconds
    "ScanPE5PVB":         boolean,
    "SearchPE5PVB":       boolean,
    "SpectrumLimiterValue":number,
    "StatusFMLIST":       "off" | "on" | "auto",
    "InfoFMLIST":         "...",           // optional status string
    "AvailableConfigs":   ["default", ...],
    "ActiveConfig":       "default" | "..."
  },
  "source": "clientIP",
  "target": "serverIP | 255.255.255.255"
}

Message flow overview

Browser Server │ │ │── status: "request" ──────────▶│ sendInitialWebSocketMessage() │◀─ status: "response" ──────────│ SendResponseMessage(clientIP) │ (full current state) │ │ │ │── status: "command" │ │ Scan: "on" ─────────────────▶│ AutoScan() │◀─ status: "broadcast" ─────────│ → all clients updated │ (Scan: "on", ...) │ │ │ │── status: "command" │ │ Sensitivity: 35 ────────────▶│ Sensitivity = 35 │◀─ status: "response" ──────────│ → client updated │ │ │── status: "command" │ │ LoadScannerConfig: "SpE" ───▶│ applyScannerConfig() │◀─ status: "response" ──────────│ → full state re-sent │ │ │── status: "command" │ │ Search: "up" ───────────────▶│ startSearch('up') [no auth needed] │ │ │◀─ status: "broadcast" ─────────│ FMLIST log result │ InfoFMLIST: "successful" │

6.5 · Authentication & Access Control

The plugin distinguishes between two client types based on the message.source field:

Client typeIdentified byAuth required?
Legacy Scanner (browser)Source does not contain "tef"No — full access always granted
TEF Logger / ConnectorSource contains "tef" (case-insensitive)Yes — if adminPass is set in config

TEF Logger authentication flow

TEF Logger Server │ │ │── status: "auth_request" │ │ password: "secret" ─────────▶│ │ ├─ adminPass set? │ │ YES → compare passwords │ │ match → authorizedClients.add(ws) │◀─ status: "auth_success" ──────│ send auth_success + response │ │ no match → auth_failed │ │ NO → grant immediately (no password) │ │ authorizedClients.add(ws) │◀─ status: "auth_failed" ───────│

All scan and config commands from TEF Logger clients are silently ignored until authentication succeeds. Search commands (Search: "up"/"down") are exempt from authentication and are always executed immediately for any client type.

On the browser side, authentication is handled by checking the webserver's page text:

6.6 · Logging – HTML, CSV, FMLIST

HTML log

The HTML log is created from FilteredTemplate.html (located in the scanner plugin folder) as a header template and appended with one <tr> row per detected station. The row includes: date, time, frequency, PI code, PS name (spaces replaced with underscores), station name, city, ITU country, antenna name, polarisation, ERP, signal strength (converted to the configured unit), distance, azimuth, station ID, scan mode (A=auto / M=manual), and three action links (Stream, Map, FMLIST).

URDS CSV log

A new CSV file is created at the start of each autoscan session (format: YYYYMMDDTHHMMSS_fm_rds.csv). Each entry is one line with 40+ comma-separated fields compatible with the URDS (Universal RDS) format, including nanosecond-resolution timestamps, GPS coordinates, signal levels in dBµV, RDS fields (PI, PS, TP, TA, PTY, ECC, AF, RT), and transmitter metadata fetched live from the maps.fmdx.org API.

// CSV line structure (abbreviated)
30,{UNIXTIME},freq,{Hz},rdson,{SNRMIN},{SNRMAX},
{dateTimeNs},{GPS_LAT},{GPS_LON},{GPS_MODE},{GPS_ALT},{GPS_TIME},
{PI},1,{PS},1,{TA},{TP},{MUSIC},{PTY},{GRP},{STEREO},{DYNPTY},
{OTHERPI},,{ALLPSTEXT},{OTHERPS},,{ECC},{STATIONID},{AF},{RT},,,,,
{TX_NAME},{TX_CITY},{TX_ITU},{TX_ERP},{TX_POL},{TX_DIST},{TX_AZ},{TX_LAT},{TX_LON}

TX latitude/longitude are fetched live from maps.fmdx.org/api/?id={stationid} for each log entry. If the station ID is missing or the API call fails, the TX coordinate fields are left empty.

FMLIST autolog

When FMLIST_Autolog is set to "on" or "auto", the function writeLogFMLIST() is called for each identified DX station that passes the distance and blacklist filters. The submission is a JSON POST to api.fmlist.org/fmdx.org/slog.php and includes the webserver's UUID token, OM ID, coordinates, and a log message string. A successful "OK!" response triggers a green toast notification in all connected browsers.

6.7 · Blacklist & Whitelist

Three separate text files control frequency/PI filtering. All files are located in plugins/Scanner/ and are reloaded automatically when changed on disk (via fs.watchFile):

FileControlled byFormatEffect
blacklist.txtEnableBlacklistOne frequency per line (e.g. 89.000)Frequencies skipped during blacklist/spectrumBL/differenceBL scan modes
whitelist.txtEnableWhitelistOne frequency per lineOnly these frequencies are scanned in whitelist mode
blacklist_log.txtLog_BlacklistOne entry per line: freq;PI, freq, or PIMatching stations excluded from HTML and CSV log entries
blacklist_fmlist.txtFMLIST_BlacklistOne entry per line: freq;PI, freq, or PIMatching stations excluded from FMLIST submissions

Frequency matching uses a tolerance of ±0.05 MHz (floating-point safe). Whitelist matching uses exact two-decimal rounding. The combined blacklist+PI format allows suppressing a specific station on a shared frequency (e.g. 89.000;D3C3) without affecting other stations on 89.0 MHz.

6.8 · Antenna Switching

When AntennaSwitch: "on" is configured and the FM-DX-Webserver has two or more antennas enabled, the scanner cycles through them at the end of every full band sweep via sendNextAntennaCommand():

function sendNextAntennaCommand() {
  if (enabledAntennas.length < 2) return;
  const ant = enabledAntennas[currentAntennaIndex];
  sendCommandToClient(`Z${ant.number - 1}`); // Z0–Z3
  currentAntennaIndex =
    (currentAntennaIndex + 1) % enabledAntennas.length;
}

In auto mode (StartAutoScan: "auto"), the antenna is also restored to its pre-scan state when a user connects and autoscan stops. The current antenna at scan start is saved in saveAutoscanAntenna and restored via sendCommandToClient(`Z${saveAutoscanAntenna}`).

The four spectrum baseline arrays (sigArraySave0–3) are maintained per antenna index so that the difference scan can compare against the correct previous pass for each antenna independently.

6.9 · Dynamic Config File Watcher

The scanner uses fs.watchFile() (poll-based, 1-second interval) rather than fs.watch() (inotify-based) for config file monitoring. This is intentional — fs.watchFile is more reliable for config files across different filesystems and prevents rapid duplicate triggers during atomic saves by text editors.

fs.watchFile(scanner.json, { interval: 1000 }) │ └─ curr.mtimeMs !== prev.mtimeMs? YES → loadConfig(filePath) ← re-parse JSON applyScannerConfig() ← apply all values updateSettings() ← rewrite scanner.js broadcast to all clients ← update UI immediately restart scan if active ← clean restart with new config

The plugins_configs/ directory itself is also watched via fs.watch() with a 500 ms debounce. When a new scanner_X.json file appears or is deleted, availableScannerConfigs is updated and the admin dropdown in the browser is refreshed automatically. If the currently active profile file is deleted, the scanner falls back to scanner.json.

The function updateSettings() rewrites specific constant declarations directly inside scanner.js (the browser-side client file) using regex replacements, so that the browser always receives the correct feature flags (EnableBlacklist, EnableSpectrumScan, SignalStrengthUnit, AvailableScannerConfigs, ActiveScannerConfig, etc.) on the next page load. A byte-for-byte comparison prevents unnecessary disk writes.

7 · All Configuration Parameters Reference

Complete reference for plugins_configs/scanner.json. All parameters are optional — missing keys are filled with the defaults shown.

ParameterDefaultDescription
General
Scanmode10 = offline, 1 = online (fmdx.org station lookup)
Autoscan_PE5PVB_ModefalseUse PE5PVB firmware scan engine instead of software scan
Search_PE5PVB_ModefalseUse PE5PVB firmware search for ◀◀ / ▶▶ buttons
StartAutoScan"off""off" / "on" / "auto"
AntennaSwitch"off""off" / "on" – cycle antennas at upper band limit
OnlyScanHoldTime"off""off" / "on" – always wait full hold time regardless of RDS
Sensitivity & Timing
defaultSensitivityValue30Signal threshold in configured unit; or dBf/dBµV/dBm offset above noise if calibration is active
SensitivityCalibrationFrequenz""Reference frequency in MHz for dynamic noise-floor calibration (e.g. "87.3"). Empty = disabled.
defaultScanHoldTime5Hold time in seconds: 1, 2, 3, 4, 5, 7, 10, 15, 20, 30
defaultScannerMode"normal"Startup scan mode: normal / blacklist / whitelist / spectrum / spectrumBL / difference / differenceBL
scanIntervalTime500Milliseconds between frequency steps (max 1000)
scanBandwith0IF bandwidth in Hz during scan (0 = auto)
tuningLowerLimit""Lower band limit in MHz; empty = use webserver value
tuningUpperLimit""Upper band limit in MHz (max 108.0); empty = use webserver value
SignalStrengthUnit"dBf""dBf" / "dBµV" / "dBm"
Filtering
EnableBlacklistfalseEnable blacklist.txt frequency filter
EnableWhitelistfalseEnable whitelist.txt frequency filter
Spectrum & Difference Scan
EnableSpectrumScanfalseEnable spectrum / spectrumBL modes
EnableDifferenceScanfalseEnable difference / differenceBL modes
SpectrumChangeValue3Minimum signal change in dBf/dBµV for difference mode
SpectrumLimiterValue50Upper signal limit in dBf/dBµV — strong locals excluded above this
SpectrumPlusMinusValue60Signal level at which ±0.1 MHz neighbours of locals are also masked
Logging
HTMLlogOnlyIDtrueHTML log: only stations with known fmdx.org ID
HTMLlogRAWfalseAlso write raw (unfiltered) HTML log for every PI received
HTMLOnlyFirstLogfalseLog each station only once per session
CSVcreatetrueCreate URDS CSV log file and enable Map Viewer / Validator buttons
CSVcompletePStrueWait for complete error-free PS before writing CSV entry
UTCtimetrueUse UTC timestamps in all log files
Log_BlacklistfalseExclude blacklist_log.txt entries from HTML and CSV logs
FMLIST Integration
FMLIST_OM_ID""Your FMLIST OM ID; falls back to webserver config if empty
FMLIST_Autolog"off""off" / "on" / "auto"
FMLIST_MinDistance200Minimum station distance in km (enforced minimum: 200)
FMLIST_MaxDistance2000Maximum station distance in km
FMLIST_LogInterval60Re-log interval in minutes (enforced minimum: 60)
FMLIST_CanLogServer""CanLogServer address (e.g. "127.0.0.1:2000"); overrides local interval
FMLIST_ShortServerName""Short server name for log messages (max 10 chars)
FMLIST_BlacklistfalseExclude blacklist_fmlist.txt entries from FMLIST submissions
Miscellaneous
BEEP_CONTROLfalseAcoustic beep signals for scan events (requires speaker npm module)

8 · Important Notes & Known Issues

Spectrum scan requires rescanDelay = 0

The Spectrum Graph plugin must have rescanDelay set to 0 in its own config file (plugins_configs/SpectrumGraph.json). Without this, the spectrum array will not be refreshed after each band sweep and the scanner will repeatedly tune the same frequencies.

SpectrumLimiterValue must be greater than Sensitivity

In spectrum and difference modes, Sensitivity must always be strictly less than SpectrumLimiterValue. If this condition is violated (e.g. after a manual sensitivity change), the server logs the error "Sensitivity must be smaller than SpectrumLimiter!" and the browser displays a red toast warning. The scan continues but will find no valid frequencies until the values are corrected.

PE5PVB mode limitations

FMLIST submissions require a valid UUID

The webserver must have a UUID token configured (config.identification.token not null). Without it, all FMLIST POST requests are blocked server-side. The UUID is set during the FM-DX-Webserver initial setup or via the settings page.

maps.fmdx.org TX coordinate lookup adds latency

For every CSV log entry, the scanner fetches the transmitter's geographic coordinates from maps.fmdx.org/api/?id={stationid}. This is an HTTPS request with a 5-second timeout. On slow or offline connections the TX lat/lon fields will be empty but the log entry is still written. Consider setting Scanmode: 0 (offline mode) if internet connectivity is not available.

Speaker module and Node.js compatibility

The speaker npm module requires native compilation (node-gyp). On Node.js versions above 18, compilation may fail. If acoustic control is not required, leave BEEP_CONTROL: false (default).

Public IP fetch on startup

The browser-side script fetches the client's public IP from https://icanhazip.com at startup to populate the WebSocket message source field. If this service is unavailable, the initial WebSocket request will fail and the scanner will show an error toast. The page must be reloaded to retry.

Mobile layout constraints

On screens narrower than 769 px, the scanner button is placed inside a popup panel ("Bandwidth & Antennas" or "Filters") rather than the main tuning row. The Map Viewer and Validator buttons are suppressed on all touch devices (pointer: coarse media query).

9 · Version History (Summary)

VersionDateKey Changes
4.2cMarch 2026URDS Log Validator button; improved signal unit watcher; stabilised spectrum cooldown; TEF Logger session-based auth; dynamic config dropdown in settings panel; scanner_state.json persistence; CanLogServer integration improvements; TX coordinate lookup via maps.fmdx.org; per-antenna difference baseline arrays.
4.2bFebruary 2026Multiple named config file support (scanner_X.json); live hot-reload via fs.watchFile; LoadScannerConfig WebSocket command; active config persisted across restarts.
4.2aJanuary 2026plugins_api privileged command support (Admin Lock compatible); GPS WebSocket listener for dynamic QTH; OnlyScanHoldTime parameter; improved band-end wrap with calibration re-run.
4.1xLate 2025Difference scan mode (differenceBL); per-antenna spectrum baselines; SpectrumPlusMinusValue filter; FMLIST blacklist; log blacklist (blacklist_log.txt); CSVcompletePS option; FMLIST short server name.
4.0xMid 2025Spectrum scan modes (spectrum, spectrumBL); Spectrum Graph plugin integration; dynamic ellipse gate; SensitivityCalibrationFrequenz; acoustic beep control; URDS CSV log format; Map Viewer button; HTML first-log mode.
3.x2024–2025Whitelist mode; antenna switching; PE5PVB search mode; configurable band limits; dBµV / dBm unit support; mobile UI improvements; FMLIST autolog (on/auto/off); CanLogServer.
2.x2024Blacklist mode; PE5PVB autoscan mode; admin-only controls; scanner controls panel (long-press); cookie persistence; blinking autoscan overlay.
1.x2023–2024Initial release: basic FM band scan, RDS-based detection, HTML log, WebSocket architecture, configurable sensitivity and hold time.

Scanner Plugin · Documentation v4.2c · Author: Highpoint · powered by PE5PVB · March 2026
github.com/Highpoint2000/webserver-scanner · Scanner Wizard