From 0490dd656da98418fb384bd44f25ddf71f474bd5 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 10:53:14 -0700 Subject: [PATCH] added antenna LOS pointing to diagnostic plots --- @rfSensor/plotPerformance.m | 52 ++++++++++++++++++++++++++++++++----- test/test_rfSensor.m | 45 +++++++++----------------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m index 28a780d..df9dfe9 100644 --- a/@rfSensor/plotPerformance.m +++ b/@rfSensor/plotPerformance.m @@ -9,6 +9,12 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; end + % Clear local caches so this visualization always uses its own grid + obj.rssCache = []; + for ii = 1:numel(otherSensors) + otherSensors{ii}.rssCache = []; + end + otherSensorsPos = otherSensorsPos + [0, 0, altitude]; % Create grid on which to evalute SINR, SNR @@ -29,24 +35,56 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) % normalize in linear scale SINR = 10.^(SINR/10); SINR = SINR ./ max(SINR(:)); SINR = 10 * log10(SINR); - SNR = 10.^(SNR/10); SNR = SNR ./ max(SNR(:)); SNR = 10 * log10(SNR); - + SNR = 10.^(SNR/10); SNR = SNR ./ max(SNR(:)); SNR = 10 * log10(SNR); + + % Collect sensor positions and boresight parameters for overlay + sensorXY = [0, 0; otherSensorsPos(:, 1:2)]; + sensorTilts = [obj.tilt; cellfun(@(s) s.tilt, otherSensors)]; + sensorAzimuths = [obj.azimuth; cellfun(@(s) s.azimuth, otherSensors)]; + tailScale = 0.5 * d; + f = figure; tiledlayout(1, 2, TileSpacing="compact", Padding="compact"); nexttile; imagesc(distances, distances, SNR); axis("image"); set(gca, 'YDir', 'normal'); - colorbar; - xlabel("X (m)"); ylabel("Y (m)"); + colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SNR (dB)"); subtitle("No interfering sources"); + addSensorOverlay(gca, sensorXY, sensorTilts, sensorAzimuths, tailScale); nexttile; imagesc(distances, distances, SINR); axis("image"); set(gca, 'YDir', 'normal'); - colorbar; - xlabel("X (m)"); ylabel("Y (m)"); + colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SINR (dB)"); subtitle(sprintf("%d interfering source(s)", size(otherSensorsPos, 1))); -end \ No newline at end of file + addSensorOverlay(gca, sensorXY, sensorTilts, sensorAzimuths, tailScale); +end + +function addSensorOverlay(ax, sensorXY, tilts, azimuths, tailScale) + % Draw a marker + boresight arrow for each sensor. + % Tail direction follows azimuth convention (0=+Y, 90=+X, clockwise). + % Tail length = tailScale * sind(tilt), so nadir (0°) has no tail and + % horizon (90°) has the full tailScale length. + hold(ax, 'on'); + for ii = 1:size(sensorXY, 1) + x = sensorXY(ii, 1); + y = sensorXY(ii, 2); + if ii == 1 + c = [0, 0, 0]; + mk = 'o'; + else + c = [0.9, 0.2, 0.2]; + mk = 'x'; + end + scatter(ax, x, y, 80, c, mk, LineWidth=2); + if tilts(ii) > 0 + u = tailScale * sind(tilts(ii)) * sind(azimuths(ii)); + v = tailScale * sind(tilts(ii)) * cosd(azimuths(ii)); + quiver(ax, x, y, u, v, 0, Color=c, LineWidth=2, MaxHeadSize=1.0); + end + end + hold(ax, 'off'); +end diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 3bf6b84..fa7a988 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -91,11 +91,11 @@ classdef test_rfSensor < matlab.unittest.TestCase altitude = 30; sensor1 = rfSensor; - sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, 15, 45); sensor2 = rfSensor; - sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, 10, 150); sensor3 = rfSensor; - sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, 20, 200); pos1 = [0, 0, altitude]; pos2 = [6, -4, altitude - 1]; @@ -107,49 +107,28 @@ classdef test_rfSensor < matlab.unittest.TestCase targetPos = [Xg(:), Yg(:), zeros(numel(Xg), 1)]; % Call 1: cache empty, does all computations for this timestep - [SINR1, ~, sensor1, others] = sensor1.sensorPerformance(pos1, targetPos, [pos2; pos3], {sensor2; sensor3}); + [~, ~, sensor1, others] = sensor1.sensorPerformance(pos1, targetPos, [pos2; pos3], {sensor2; sensor3}); sensor2 = others{1}; sensor3 = others{2}; % Calls 2 and 3 use cached data - [SINR2, ~, sensor2, others] = sensor2.sensorPerformance(pos2, targetPos, [pos1; pos3], {sensor1; sensor3}); + [~, ~, sensor2, others] = sensor2.sensorPerformance(pos2, targetPos, [pos1; pos3], {sensor1; sensor3}); sensor1 = others{1}; sensor3 = others{2}; - [SINR3, ~, sensor3, ~] = sensor3.sensorPerformance(pos3, targetPos, [pos1; pos2], {sensor1; sensor2}); - + [~, ~, sensor3, ~] = sensor3.sensorPerformance(pos3, targetPos, [pos1; pos2], {sensor1; sensor2}); % All caches should be populated after the three calls tc.assertNotEmpty(sensor1.rssCache); tc.assertNotEmpty(sensor2.rssCache); tc.assertNotEmpty(sensor3.rssCache); - % Plot SINR from each UAV's perspective - sz = size(Xg); - SINR1 = reshape(SINR1, sz); - SINR2 = reshape(SINR2, sz); - SINR3 = reshape(SINR3, sz); - - f = figure; - tiledlayout(f, 1, 3, TileSpacing="compact", Padding="compact"); - - nexttile; - imagesc(distances, distances, SINR1); axis image; set(gca, YDir="normal"); hold on; - scatter(pos1(1), pos1(2), 80, "g", "o", LineWidth=2); - scatter([pos2(1), pos3(1)], [pos2(2), pos3(2)], 80, "r", "x", LineWidth=2); - hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 1"); - - nexttile; - imagesc(distances, distances, SINR2); axis image; set(gca, YDir="normal"); hold on; - scatter(pos2(1), pos2(2), 80, "g", "o", LineWidth=2); - scatter([pos1(1), pos3(1)], [pos1(2), pos3(2)], 80, "r", "x", LineWidth=2); - hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 2"); - - nexttile; - imagesc(distances, distances, SINR3); axis image; set(gca, YDir="normal"); hold on; - scatter(pos3(1), pos3(2), 80, "g", "o", LineWidth=2); - scatter([pos1(1), pos2(1)], [pos1(2), pos2(2)], 80, "r", "x", LineWidth=2); - hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 3"); + % Plot SINR from each UAV's perspective. + % otherSensorsPos for plotPerformance: XY = offset from calling sensor, Z = absolute_alt - calling_alt. + % This is exactly posOther - posSelf for each row. + sensor1.plotPerformance(pos1(3), [pos2 - pos1; pos3 - pos1], {sensor2; sensor3}); + sensor2.plotPerformance(pos2(3), [pos1 - pos2; pos3 - pos2], {sensor1; sensor3}); + sensor3.plotPerformance(pos3(3), [pos1 - pos3; pos2 - pos3], {sensor1; sensor2}); end end end \ No newline at end of file