mirror of
https://github.com/ruvnet/RuView
synced 2026-07-24 17:43:20 +00:00
research(R11): maritime sensing — through-bulkhead impossible, through-seam works (#712)
Physics scrutiny of WiFi-band maritime sensing scenarios. Steel skin depth is 3.25 um at 2.4 GHz, making bulkheads utterly opaque. Saltwater attenuation is 853 dB/m. The 'through-bulkhead WiFi radar' framing common in conservation/maritime is wrong; the actual feasible category is 'through-seam' sensing exploiting slot diffraction through gaskets, hatch seals, and vent grilles. Composite link budget for 7 maritime scenarios (ESP32-S3 121 dB budget, 10 dB SNR margin): FEASIBLE: - Man-overboard surface @ 200 m: +25 dB - Cabin door, 2 mm seam: +31 dB - Cabin door, 5 mm seam: +39 dB - Container, 30 mm vent slot: +45 dB IMPOSSIBLE: - Closed 10 mm steel door: -938 dB - Submarine pressure hull: -929 dB - Head 30 cm underwater: -231 dB Five feasible verticals catalogued: man-overboard surface, through-seam crew vitals, container tamper detection, hatch-seal predictive maintenance, engine-room thermal anomaly via condensation. Composes with prior threads: - R6 Fresnel envelope + slot diffraction = narrower composite envelope - R10 link-budget primitives reused unmodified for air-side maritime - R7 multi-link consistency essential against superstructure jammers - R14 privacy framework transfers directly to crew-cabin monitoring Honest scope: best-case ignores vessel vibration (5-30 Hz, in-band with R10 gait frequencies), engine ignition noise, salt-spray, steel-surface multipath. Maritime gait-classification is harder than land. The romantic 'through-hull radar' is now explicitly debunked. The actual product roadmap is gasket-leakage sensing, surface detection, and predictive-maintenance audits. Coordination: ticks/tick-10.md, no PROGRESS.md edit.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""R11 — Maritime / through-bulkhead RF propagation.
|
||||
|
||||
See docs/research/sota-2026-05-22/R11-maritime-sensing.md.
|
||||
|
||||
Computes:
|
||||
- Steel bulkhead RF attenuation (skin depth) at WiFi bands
|
||||
- Seam-leakage diffraction loss
|
||||
- Saltwater attenuation (man-overboard surface sensing)
|
||||
- Composite link budget for three maritime scenarios
|
||||
|
||||
Pure NumPy.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
C = 2.998e8
|
||||
MU_0 = 4 * np.pi * 1e-7 # H/m
|
||||
EPS_0 = 8.854e-12 # F/m
|
||||
|
||||
# Material properties (typical values)
|
||||
STEEL_SIGMA = 1.0e7 # S/m (mild steel conductivity)
|
||||
SALTWATER_SIGMA = 4.8 # S/m (35 ppt at 20 deg C)
|
||||
SALTWATER_EPSR = 81.0 # relative permittivity
|
||||
|
||||
|
||||
def skin_depth_m(freq_ghz: float, sigma: float, mu_r: float = 1.0) -> float:
|
||||
"""Classical skin depth: delta = 1 / sqrt(pi * f * mu * sigma)."""
|
||||
f = freq_ghz * 1e9
|
||||
return 1.0 / np.sqrt(np.pi * f * MU_0 * mu_r * sigma)
|
||||
|
||||
|
||||
def bulk_attenuation_db_per_mm(freq_ghz: float, sigma: float, mu_r: float = 1.0) -> float:
|
||||
"""Per-mm attenuation through bulk conductor."""
|
||||
delta = skin_depth_m(freq_ghz, sigma, mu_r)
|
||||
# Field decays as exp(-x/delta), power as exp(-2x/delta)
|
||||
# In dB per metre: 20/(delta*ln(10)) = 8.686/delta
|
||||
return 8.686 / delta / 1000 # divide by 1000 to get per-mm
|
||||
|
||||
|
||||
def saltwater_attenuation_db_per_m(freq_ghz: float) -> float:
|
||||
"""Saltwater attenuation per metre via lossy-dielectric model.
|
||||
alpha = (omega/c) * Im(sqrt(eps_r - j*sigma/(omega*eps_0)))
|
||||
Returns dB/m."""
|
||||
omega = 2 * np.pi * freq_ghz * 1e9
|
||||
eps_complex = SALTWATER_EPSR - 1j * SALTWATER_SIGMA / (omega * EPS_0)
|
||||
n_complex = np.sqrt(eps_complex)
|
||||
# Principal sqrt of (a - jb), b>0, has negative imag part. The wave
|
||||
# attenuation coefficient is alpha = omega/c * |Im(n)| -- take abs().
|
||||
alpha = omega * abs(n_complex.imag) / C # Np/m
|
||||
return float(8.686 * alpha) # dB/m
|
||||
|
||||
|
||||
def seam_diffraction_loss_db(seam_width_mm: float, freq_ghz: float) -> float:
|
||||
"""Approximate diffraction loss through a narrow slot in a conductor.
|
||||
For slot width w << lambda, the slot acts as a high-pass filter:
|
||||
L_slot = 20 * log10(lambda / (2 * w)) when w < lambda/2
|
||||
0 when w >= lambda/2
|
||||
Crude but captures the 1st-order physics. Real slot antennas are more
|
||||
complex; for forensic 'how much leaks through the door seal' work
|
||||
this is the right scale."""
|
||||
lam_mm = (C / (freq_ghz * 1e9)) * 1000
|
||||
if seam_width_mm >= lam_mm / 2:
|
||||
return 0.0
|
||||
return max(0.0, 20 * np.log10(lam_mm / (2 * seam_width_mm)))
|
||||
|
||||
|
||||
def maritime_scenario(name: str, freq_ghz: float, bulkhead_mm: float,
|
||||
seam_mm: float, free_air_m: float,
|
||||
saltwater_m: float = 0.0) -> dict:
|
||||
"""Composite path loss for a maritime sensing scenario."""
|
||||
# Free-space loss
|
||||
fspl = 32.45 + 20 * np.log10(freq_ghz) + 20 * np.log10(max(0.1, free_air_m + 0.1))
|
||||
# Bulkhead loss (if any propagation through metal)
|
||||
bulk_loss = bulkhead_mm * bulk_attenuation_db_per_mm(freq_ghz, STEEL_SIGMA)
|
||||
# Seam diffraction (alternative path)
|
||||
seam_loss = seam_diffraction_loss_db(seam_mm, freq_ghz) if seam_mm > 0 else 999.0
|
||||
# Saltwater loss
|
||||
water_loss = saltwater_m * saltwater_attenuation_db_per_m(freq_ghz)
|
||||
# The actual propagation path takes whichever is lower (bulk OR seam)
|
||||
best_metal_path = min(bulk_loss, seam_loss)
|
||||
total = fspl + best_metal_path + water_loss
|
||||
return {
|
||||
"scenario": name,
|
||||
"freq_ghz": freq_ghz,
|
||||
"fspl_db": fspl,
|
||||
"bulk_loss_db": bulk_loss,
|
||||
"seam_loss_db": seam_loss,
|
||||
"metal_path_used": "seam" if seam_loss < bulk_loss else "bulk",
|
||||
"metal_path_loss_db": best_metal_path,
|
||||
"saltwater_loss_db": water_loss,
|
||||
"total_loss_db": total,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": 121 - total - 10, # 10 dB SNR margin for DSP
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--out", default="examples/research-sota/r11_maritime_results.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
# 1. Skin depth + per-mm attenuation
|
||||
materials_grid = {}
|
||||
for f in [2.4, 5.0]:
|
||||
delta_steel_um = skin_depth_m(f, STEEL_SIGMA) * 1e6 # micrometres
|
||||
att_steel = bulk_attenuation_db_per_mm(f, STEEL_SIGMA)
|
||||
att_water = saltwater_attenuation_db_per_m(f)
|
||||
materials_grid[f"{f}_GHz"] = {
|
||||
"steel_skin_depth_um": delta_steel_um,
|
||||
"steel_atten_dB_per_mm": att_steel,
|
||||
"saltwater_atten_dB_per_m": att_water,
|
||||
}
|
||||
|
||||
# 2. Three maritime scenarios
|
||||
scenarios = [
|
||||
maritime_scenario("man-overboard, surface-floating", 2.4,
|
||||
bulkhead_mm=0, seam_mm=0, free_air_m=200, saltwater_m=0),
|
||||
maritime_scenario("man-overboard, head 30 cm underwater", 2.4,
|
||||
bulkhead_mm=0, seam_mm=0, free_air_m=200, saltwater_m=0.3),
|
||||
maritime_scenario("crew vitals through 10 mm steel cabin door (closed)", 2.4,
|
||||
bulkhead_mm=10, seam_mm=0, free_air_m=3),
|
||||
maritime_scenario("crew vitals through cabin door (2 mm seam gap)", 2.4,
|
||||
bulkhead_mm=10, seam_mm=2, free_air_m=3),
|
||||
maritime_scenario("crew vitals through cabin door (5 mm seam gap)", 2.4,
|
||||
bulkhead_mm=10, seam_mm=5, free_air_m=3),
|
||||
maritime_scenario("container intrusion (steel cargo container, 2 mm walls, 30 mm vent slot)", 2.4,
|
||||
bulkhead_mm=2, seam_mm=30, free_air_m=10),
|
||||
maritime_scenario("through hull (submarine, 30 mm pressure hull)", 2.4,
|
||||
bulkhead_mm=30, seam_mm=0, free_air_m=1),
|
||||
]
|
||||
|
||||
out = {
|
||||
"model": "skin-depth steel + lossy-dielectric saltwater + slot-diffraction seam",
|
||||
"materials": materials_grid,
|
||||
"scenarios": scenarios,
|
||||
}
|
||||
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(args.out).write_text(json.dumps(out, indent=2))
|
||||
|
||||
# Print headlines
|
||||
print("=== Skin depth + bulk attenuation ===")
|
||||
for fkey, m in materials_grid.items():
|
||||
print(f" {fkey:>8} steel: skin={m['steel_skin_depth_um']:>6.2f} um, "
|
||||
f"attenuation={m['steel_atten_dB_per_mm']:>9.1f} dB/mm "
|
||||
f"saltwater={m['saltwater_atten_dB_per_m']:>6.1f} dB/m")
|
||||
print()
|
||||
print("=== Composite maritime scenarios @ 2.4 GHz ===")
|
||||
print(f"{'Scenario':<58} {'FSPL':>6} {'Metal':>6} {'Water':>6} {'Total':>6} {'Margin':>7}")
|
||||
for s in scenarios:
|
||||
print(f"{s['scenario']:<58} {s['fspl_db']:>6.1f} "
|
||||
f"{s['metal_path_loss_db']:>6.1f} {s['saltwater_loss_db']:>6.1f} "
|
||||
f"{s['total_loss_db']:>6.1f} {s['snr_margin_db']:>+7.1f}")
|
||||
print()
|
||||
print(f"Wrote {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"model": "skin-depth steel + lossy-dielectric saltwater + slot-diffraction seam",
|
||||
"materials": {
|
||||
"2.4_GHz": {
|
||||
"steel_skin_depth_um": 3.248736671806984,
|
||||
"steel_atten_dB_per_mm": 2673.654677948628,
|
||||
"saltwater_atten_dB_per_m": 852.7792439147287
|
||||
},
|
||||
"5.0_GHz": {
|
||||
"steel_skin_depth_um": 2.2507907903927653,
|
||||
"steel_atten_dB_per_mm": 3859.0881200843564,
|
||||
"saltwater_atten_dB_per_m": 867.7495416795573
|
||||
}
|
||||
},
|
||||
"scenarios": [
|
||||
{
|
||||
"scenario": "man-overboard, surface-floating",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 86.07916660695635,
|
||||
"bulk_loss_db": 0.0,
|
||||
"seam_loss_db": 999.0,
|
||||
"metal_path_used": "bulk",
|
||||
"metal_path_loss_db": 0.0,
|
||||
"saltwater_loss_db": 0.0,
|
||||
"total_loss_db": 86.07916660695635,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": 24.92083339304365
|
||||
},
|
||||
{
|
||||
"scenario": "man-overboard, head 30 cm underwater",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 86.07916660695635,
|
||||
"bulk_loss_db": 0.0,
|
||||
"seam_loss_db": 999.0,
|
||||
"metal_path_used": "bulk",
|
||||
"metal_path_loss_db": 0.0,
|
||||
"saltwater_loss_db": 255.83377317441858,
|
||||
"total_loss_db": 341.9129397813749,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": -230.91293978137492
|
||||
},
|
||||
{
|
||||
"scenario": "crew vitals through 10 mm steel cabin door (closed)",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 49.88145871091758,
|
||||
"bulk_loss_db": 26736.546779486278,
|
||||
"seam_loss_db": 999.0,
|
||||
"metal_path_used": "seam",
|
||||
"metal_path_loss_db": 999.0,
|
||||
"saltwater_loss_db": 0.0,
|
||||
"total_loss_db": 1048.8814587109175,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": -937.8814587109175
|
||||
},
|
||||
{
|
||||
"scenario": "crew vitals through cabin door (2 mm seam gap)",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 49.88145871091758,
|
||||
"bulk_loss_db": 26736.546779486278,
|
||||
"seam_loss_db": 29.891207909453847,
|
||||
"metal_path_used": "seam",
|
||||
"metal_path_loss_db": 29.891207909453847,
|
||||
"saltwater_loss_db": 0.0,
|
||||
"total_loss_db": 79.77266662037142,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": 31.227333379628575
|
||||
},
|
||||
{
|
||||
"scenario": "crew vitals through cabin door (5 mm seam gap)",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 49.88145871091758,
|
||||
"bulk_loss_db": 26736.546779486278,
|
||||
"seam_loss_db": 21.93240773601309,
|
||||
"metal_path_used": "seam",
|
||||
"metal_path_loss_db": 21.93240773601309,
|
||||
"saltwater_loss_db": 0.0,
|
||||
"total_loss_db": 71.81386644693066,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": 39.18613355306934
|
||||
},
|
||||
{
|
||||
"scenario": "container intrusion (steel cargo container, 2 mm walls, 30 mm vent slot)",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 60.14065230988498,
|
||||
"bulk_loss_db": 5347.309355897256,
|
||||
"seam_loss_db": 6.369382728340219,
|
||||
"metal_path_used": "seam",
|
||||
"metal_path_loss_db": 6.369382728340219,
|
||||
"saltwater_loss_db": 0.0,
|
||||
"total_loss_db": 66.5100350382252,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": 44.4899649617748
|
||||
},
|
||||
{
|
||||
"scenario": "through hull (submarine, 30 mm pressure hull)",
|
||||
"freq_ghz": 2.4,
|
||||
"fspl_db": 40.88207853739662,
|
||||
"bulk_loss_db": 80209.64033845883,
|
||||
"seam_loss_db": 999.0,
|
||||
"metal_path_used": "seam",
|
||||
"metal_path_loss_db": 999.0,
|
||||
"saltwater_loss_db": 0.0,
|
||||
"total_loss_db": 1039.8820785373966,
|
||||
"esp32_link_budget_db": 121,
|
||||
"snr_margin_db": -928.8820785373966
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user