LoRa radio bearer over a MeshCore private channel. Enable, port and region changes need a daemon restart; the rest hot-reload.
+Total trailing-hour airtime cap shared by every discovery transmission (beacons, solicits, probes). Per-channel caps live in Topology.
@@ -218,6 +251,19 @@ async function dappsSaveConfig(ev) { HeartbeatIntervalSeconds: parseInt(f.HeartbeatIntervalSeconds.value, 10), AutoDiscoverViaNodeCall: f.AutoDiscoverViaNodeCall.checked, NodePromptApplicationCommand: f.NodePromptApplicationCommand.value, + MeshCoreEnabled: f.MeshCoreEnabled.checked, + MeshCorePort: f.MeshCorePort.value, + MeshCoreRegion: f.MeshCoreRegion.value, + MeshCoreTxPowerDbm: parseInt(f.MeshCoreTxPowerDbm.value, 10), + MeshCoreChannelIndex: parseInt(f.MeshCoreChannelIndex.value, 10), + MeshCoreChannelName: f.MeshCoreChannelName.value, + MeshCoreChannelPsk: f.MeshCoreChannelPsk.value, + MeshCoreNodeName: f.MeshCoreNodeName.value, + MeshCoreAirtimeBudgetSecondsPerHour: parseFloat(f.MeshCoreAirtimeBudgetSecondsPerHour.value), + MeshCoreCompress: f.MeshCoreCompress.checked, + MeshCoreCongestionBackoffFraction: parseFloat(f.MeshCoreCongestionBackoffFraction.value), + MeshCoreLbtGuardMs: parseInt(f.MeshCoreLbtGuardMs.value, 10), + MeshCoreReliableDelivery: f.MeshCoreReliableDelivery.checked, }; if (await dappsPostJson('@Url.Content("~/Config")', body)) { const head = document.querySelector('.page-head'); @@ -326,4 +372,31 @@ async function dappsRotatePassword(ev) { } }); })(); + +// MeshCore bearer status + device-control reset (#159). +(function () { + const statusEl = document.getElementById('mc-status'); + const resetBtn = document.getElementById('mc-reset'); + if (!statusEl) return; + async function refresh() { + try { + const r = await fetch('@Url.Content("~/MeshCore/status")'); + if (!r.ok) { statusEl.textContent = 'unavailable'; return; } + const s = await r.json(); + if (!s.enabled) { statusEl.textContent = 'disabled'; if (resetBtn) resetBtn.disabled = true; return; } + if (resetBtn) resetBtn.disabled = false; + const f = s.freqMhz != null ? s.freqMhz.toFixed(3) + 'MHz' : '?'; + const exp = s.reliableExpired ? ' / ' + s.reliableExpired + '✗' : ''; + statusEl.textContent = `${s.linkState} · ${f} SF${s.sf ?? '?'} CR${s.cr ?? '?'} · occ ${(s.occupancy * 100).toFixed(0)}% · rx ${s.delivered} · reliable ${s.reliableConfirmed}✓/${s.reliablePending}⋯${exp} · resets ${s.resets}`; + } catch { statusEl.textContent = 'unavailable'; } + } + if (resetBtn) resetBtn.addEventListener('click', async () => { + resetBtn.disabled = true; + statusEl.textContent = 'resetting…'; + try { await fetch('@Url.Content("~/MeshCore/reset")', { method: 'POST' }); } catch { } + setTimeout(refresh, 5000); + }); + refresh(); + setInterval(refresh, 5000); +})(); diff --git a/src/dapps/dapps.core/Services/MeshCoreBearer.cs b/src/dapps/dapps.core/Services/MeshCoreBearer.cs index 3b7be96..148b64d 100644 --- a/src/dapps/dapps.core/Services/MeshCoreBearer.cs +++ b/src/dapps/dapps.core/Services/MeshCoreBearer.cs @@ -27,11 +27,36 @@ public sealed class MeshCoreBearer : IDappsBackhaul, IAsyncDisposable private MeshCoreLink? _link; private MeshCoreCompanionBackhaul? _backhaul; private MeshCoreInbound? _inbound; + private MeshCoreReliability? _reliability; public bool Enabled { get; private set; } public MeshCoreLink? Link => _link; public MeshCoreInbound? Inbound => _inbound; + ///