Fix Meteor LRPT decoding in Docker and enhance weather satellite UI

Docker fixes:
- Add missing COPY for /usr/local/share/ (pipeline definitions were never
  reaching the runtime image — root cause of silent SatDump failures)
- Add libfftw3-double3 and libfftw3-single3 runtime dependencies
- Handle arm64 vs x86 install path differences (/usr vs /usr/local)
- Split SatDump compile and staging into separate layers for better caching
- Add build-time assertions to catch missing pipelines early

UI enhancements:
- Timezone selector (UTC, Local, Eastern, Central, Mountain, Pacific)
  with localStorage persistence — all time displays update instantly
- Pass analysis bar showing 24h quality breakdown and best upcoming pass
- Enhanced pass cards with cardinal direction (NW→SE), BEST badge
- Console timestamps, log level filters (ALL/SIGNAL/PROG/ERR), COPY/CLR
- Pass count in stats strip
- Demo data mode for UI testing without SDR or live satellite pass
- Meteor M2-4 80k baud fallback pipeline option

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
mitchross
2026-03-25 00:05:31 -04:00
parent 1dde2a008e
commit 43fb735e4e
6 changed files with 722 additions and 167 deletions
+54 -2
View File
@@ -2484,6 +2484,25 @@
</div>
</div>
<div class="wxsat-strip-divider"></div>
<div class="wxsat-strip-group">
<div class="wxsat-strip-stat">
<span class="wxsat-strip-value" id="wxsatStripPassCount">0</span>
<span class="wxsat-strip-label">PASSES</span>
</div>
</div>
<div class="wxsat-strip-divider"></div>
<div class="wxsat-strip-group">
<span class="wxsat-strip-label" style="margin-right: 4px;">TZ</span>
<select id="wxsatTimezone" class="wxsat-tz-select" onchange="WeatherSat.setTimezone(this.value)">
<option value="UTC">UTC</option>
<option value="local">Local</option>
<option value="US/Eastern">Eastern</option>
<option value="US/Central">Central</option>
<option value="US/Mountain">Mountain</option>
<option value="US/Pacific">Pacific</option>
</select>
</div>
<div class="wxsat-strip-divider"></div>
<div class="wxsat-strip-group">
<label class="wxsat-schedule-toggle" title="Auto-capture passes">
<input type="checkbox" id="wxsatAutoSchedule" onchange="WeatherSat.toggleScheduler(this)">
@@ -2515,6 +2534,29 @@
</div>
</div>
<!-- Pass Analysis Bar -->
<div class="wxsat-analysis-bar" id="wxsatAnalysisBar">
<div class="wxsat-analysis-stats">
<div class="wxsat-analysis-stat">
<span class="wxsat-analysis-value" id="wxsatAnalysisTotal">0</span>
<span class="wxsat-analysis-label">24h passes</span>
</div>
<div class="wxsat-analysis-stat">
<span class="wxsat-analysis-value excellent" id="wxsatAnalysisExcellent">0</span>
<span class="wxsat-analysis-label">excellent</span>
</div>
<div class="wxsat-analysis-stat">
<span class="wxsat-analysis-value good" id="wxsatAnalysisGood">0</span>
<span class="wxsat-analysis-label">good</span>
</div>
<div class="wxsat-analysis-stat">
<span class="wxsat-analysis-value fair" id="wxsatAnalysisFair">0</span>
<span class="wxsat-analysis-label">fair</span>
</div>
</div>
<div class="wxsat-analysis-best" id="wxsatAnalysisBest">--</div>
</div>
<!-- Capture progress -->
<div class="wxsat-capture-status" id="wxsatCaptureStatus">
<div class="wxsat-capture-info">
@@ -2543,8 +2585,18 @@
<span class="wxsat-phase-step" data-phase="complete">COMPLETE</span>
</div>
</div>
<button class="wxsat-strip-btn" id="wxsatConsoleToggle"
onclick="WeatherSat.toggleConsole()" title="Toggle console">&#x25BC;</button>
<div class="wxsat-console-filters" id="wxsatConsoleFilters">
<button class="wxsat-console-filter active" data-filter="all" onclick="WeatherSat.filterConsole('all')">ALL</button>
<button class="wxsat-console-filter" data-filter="signal" onclick="WeatherSat.filterConsole('signal')">SIGNAL</button>
<button class="wxsat-console-filter" data-filter="progress" onclick="WeatherSat.filterConsole('progress')">PROG</button>
<button class="wxsat-console-filter" data-filter="error" onclick="WeatherSat.filterConsole('error')">ERR</button>
</div>
<div class="wxsat-console-actions">
<button class="wxsat-strip-btn" onclick="WeatherSat.exportConsole()" title="Copy log to clipboard">COPY</button>
<button class="wxsat-strip-btn" onclick="WeatherSat.clearConsole()" title="Clear console">CLR</button>
<button class="wxsat-strip-btn" id="wxsatConsoleToggle"
onclick="WeatherSat.toggleConsole()" title="Toggle console">&#x25BC;</button>
</div>
</div>
<div class="wxsat-console-body" id="wxsatConsoleBody">
<div class="wxsat-console-log" id="wxsatConsoleLog">
+59 -47
View File
@@ -2,13 +2,13 @@
<div id="weatherSatMode" class="mode-content">
<div class="section">
<h3>Weather Satellite Decoder</h3>
<div class="alpha-mode-notice">
ALPHA: Weather Satellite capture is experimental and may fail depending on SatDump version, SDR driver support, and pass conditions.
</div>
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 12px;">
Receive and decode Meteor LRPT weather imagery.
Uses SatDump for live SDR capture and image processing, and also shows Meteor imagery produced by the ground-station scheduler.
</p>
<div class="alpha-mode-notice">
ALPHA: Weather Satellite capture is experimental and may fail depending on SatDump version, SDR driver support, and pass conditions.
</div>
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 12px;">
Receive and decode Meteor LRPT weather imagery.
Uses SatDump for live SDR capture and image processing, and also shows Meteor imagery produced by the ground-station scheduler.
</p>
</div>
<div class="section">
@@ -18,8 +18,9 @@
<select id="weatherSatSelect" class="mode-select">
<option value="METEOR-M2-3" selected>Meteor-M2-3 (137.900 MHz LRPT)</option>
<option value="METEOR-M2-4">Meteor-M2-4 (137.900 MHz LRPT)</option>
</select>
</div>
<option value="METEOR-M2-4-80K">Meteor-M2-4 80k baud (fallback)</option>
</select>
</div>
<div class="form-group">
<label>Gain (dB)</label>
<input type="number" id="weatherSatGain" value="40" step="0.1" min="0" max="50">
@@ -69,7 +70,7 @@
<li><strong style="color: var(--text-primary);">Connection:</strong> Solder elements to coax center + shield, connect to SDR via SMA</li>
</ul>
<p style="margin-top: 6px; color: var(--text-dim); font-style: italic;">
Best starter antenna. Good enough for a clean Meteor LRPT pass when the satellite gets high overhead.
Best starter antenna. Good enough for a clean Meteor LRPT pass when the satellite gets high overhead.
</p>
</div>
@@ -132,8 +133,8 @@
<li><strong style="color: var(--text-primary);">Antenna up:</strong> Point the antenna straight UP (zenith) for best overhead coverage</li>
<li><strong style="color: var(--text-primary);">Avoid:</strong> Metal roofs, power lines, buildings blocking the sky</li>
<li><strong style="color: var(--text-primary);">Coax length:</strong> Keep short (&lt;10m). Signal loss at 137 MHz is ~3 dB per 10m of RG-58</li>
<li><strong style="color: var(--text-primary);">LNA:</strong> Mount at the antenna feed point, NOT at the SDR end.
Recommended: a low-noise 137 MHz filtered LNA near the antenna feed point</li>
<li><strong style="color: var(--text-primary);">LNA:</strong> Mount at the antenna feed point, NOT at the SDR end.
Recommended: a low-noise 137 MHz filtered LNA near the antenna feed point</li>
<li><strong style="color: var(--text-primary);">Bias-T:</strong> Enable the Bias-T checkbox above if your LNA is powered via the coax from the SDR</li>
</ul>
</div>
@@ -162,9 +163,9 @@
<td style="padding: 3px 4px; color: var(--text-dim);">Polarization</td>
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">RHCP</td>
</tr>
<tr>
<td style="padding: 3px 4px; color: var(--text-dim);">Meteor (LRPT) bandwidth</td>
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">~140 kHz</td>
<tr>
<td style="padding: 3px 4px; color: var(--text-dim);">Meteor (LRPT) bandwidth</td>
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">~140 kHz</td>
</tr>
</table>
</div>
@@ -177,29 +178,30 @@
<span class="wxsat-collapse-icon collapsed" style="font-size: 10px; transition: transform 0.2s; display: inline-block;">&#9660;</span>
</h3>
<div class="wxsat-test-decode-body collapsed" style="overflow: hidden;">
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 8px;">
Decode a pre-recorded Meteor IQ file without SDR hardware.
Shared ground-station recordings are also accepted by the backend.
</p>
<div class="form-group">
<label>Satellite</label>
<select id="wxsatTestSatSelect" class="mode-select">
<option value="METEOR-M2-3" selected>Meteor-M2-3 (LRPT)</option>
<option value="METEOR-M2-4">Meteor-M2-4 (LRPT)</option>
</select>
</div>
<div class="form-group">
<label>File Path (server-side)</label>
<input type="text" id="wxsatTestFilePath" value="data/weather_sat/samples/meteor_lrpt.sigmf-data" style="font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 11px;">
</div>
<div class="form-group">
<label>Sample Rate</label>
<select id="wxsatTestSampleRate" class="mode-select">
<option value="500000">500 kHz (IQ LRPT)</option>
<option value="1000000">1 MHz (IQ narrow)</option>
<option value="2400000" selected>2.4 MHz (INTERCEPT default)</option>
</select>
</div>
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 8px;">
Decode a pre-recorded Meteor IQ file without SDR hardware.
Shared ground-station recordings are also accepted by the backend.
</p>
<div class="form-group">
<label>Satellite</label>
<select id="wxsatTestSatSelect" class="mode-select">
<option value="METEOR-M2-3" selected>Meteor-M2-3 (LRPT)</option>
<option value="METEOR-M2-4">Meteor-M2-4 (LRPT)</option>
<option value="METEOR-M2-4-80K">Meteor-M2-4 80k baud</option>
</select>
</div>
<div class="form-group">
<label>File Path (server-side)</label>
<input type="text" id="wxsatTestFilePath" value="data/weather_sat/samples/meteor_lrpt.sigmf-data" style="font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 11px;">
</div>
<div class="form-group">
<label>Sample Rate</label>
<select id="wxsatTestSampleRate" class="mode-select">
<option value="500000">500 kHz (IQ LRPT)</option>
<option value="1000000">1 MHz (IQ narrow)</option>
<option value="2400000" selected>2.4 MHz (INTERCEPT default)</option>
</select>
</div>
<button class="mode-btn" onclick="WeatherSat.testDecode()" style="width: 100%; margin-top: 4px;">
Test Decode
</button>
@@ -223,15 +225,25 @@
</div>
</div>
<div class="section">
<h3>Debug / Test</h3>
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 8px;">
Load sample pass data and console output to test the UI without an SDR or live satellite pass.
</p>
<button class="mode-btn" onclick="WeatherSat.loadDemoData()" style="width: 100%;">
Load Demo Data
</button>
</div>
<div class="section">
<h3>Resources</h3>
<div style="display: flex; flex-direction: column; gap: 6px;">
<a href="https://github.com/SatDump/SatDump" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
SatDump Documentation
</a>
<a href="https://www.rtl-sdr.com/rtl-sdr-tutorial-receiving-meteor-m2-weather-satellite-images/" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
Meteor Reception Guide
</a>
</div>
</div>
</div>
<a href="https://github.com/SatDump/SatDump" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
SatDump Documentation
</a>
<a href="https://www.rtl-sdr.com/rtl-sdr-tutorial-receiving-meteor-m2-weather-satellite-images/" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
Meteor Reception Guide
</a>
</div>
</div>
</div>