Say how strongly each sensor is being heard, and how much of it arrives

Two numbers rather than one, because "how well is this sensor coming in" is
two questions and they can disagree in a way that is worth seeing.

The first is strength: how far the sensor's burst stood above the noise of the
second it arrived in, in decibels, on every reading and in the log and the
spreadsheet.  The burst detector already worked this out and threw it away --
it is the ratio the per-burst threshold is set from -- so this is carrying a
number through rather than measuring a new one.

What it is not is a power at the aerial, and the docstring says so where
somebody will read it.  A dongle has no reference level and, with the tuner
left on automatic, no fixed gain either; anything in dBm would be invention.
A ratio of two amplitudes off the same receiver in the same second is the
honest quantity, and it is enough for the three things anybody wants a signal
reading for: comparing two sensors now, watching one over an evening, and
pointing an aerial.  A fixed --gain makes it comparable between runs as well,
which the help now says.

It is coloured red, amber or green, it is on the live display as well as the
report, and it is kept on a narrow terminal when other columns are dropped --
because somebody moving a whip about while a number climbs is not doing it on
a wide window, and that is the most useful thing this does.

The second is the share of what a sensor sent that actually arrives, which
comes out of the timing for nothing.  These transmit on a fixed cycle, so the
shortest wait ever seen between two of a sensor's messages is that cycle, and
the average wait is the cycle divided by the fraction getting through: one
over the other is the fraction, with no need to know the model or how often it
is meant to speak.

Read together they say more than either does alone.  A strong signal with a
low share is interference or a collision rather than distance.  A weak signal
at a hundred per cent is a sensor at the edge that is getting through anyway
and is best left alone.

The strongest of the three copies of a message is the one reported, not the
first: they go out milliseconds apart and arrive at whatever the fading does
to each.  A reading with no strength -- an older log, a block with no
measurable noise floor to be a ratio to -- leaves the last known figure alone
rather than overwriting it with a zero.

Full suite 2369 passed, checked against five deliberately broken builds
including the one that reports decibels as a power ratio, which is off by a
factor of two and looks entirely reasonable.  Built as 2026-09-07_06.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
The Dust Council 2026-09-07 23:50:37 -07:00
parent 872eadac37
commit 03d3600ecc
10 changed files with 375 additions and 24 deletions

View file

@ -1118,3 +1118,60 @@ def test_the_hex_of_what_was_read_is_reported_so_it_can_be_worked_out_by_hand():
miss = a.near_misses("1111" + frame)[0]
assert len(miss.hex.split()) == 7
assert all(len(byte) == 2 for byte in miss.hex.split())
# ---------------------------------------------------------------------------
# How strongly a sensor was heard
# ---------------------------------------------------------------------------
def test_the_strength_of_a_reading_follows_the_strength_of_the_signal():
"""Four times the amplitude is twelve decibels, and has to come out so.
This is a ratio of two amplitudes off the same receiver in the same
second and nothing more: not a power at the aerial, which a dongle with
no reference level and an automatic gain cannot give. But a ratio that
tracks the signal correctly is enough to compare two sensors, watch one
over an evening, and point an aerial, which is what it is for.
"""
frame = a.tower_frame(0x1A2B, 21.5, 48, "A")
seen = {}
for amplitude in (2.0, 0.5, 0.125):
iq = a.modulate(frame, 250_000.0, amplitude=amplitude, noise=0.02)
got = a.readings_from(iq, 250_000.0)
assert got, f"not heard at {amplitude}"
seen[amplitude] = got[0].snr
assert seen[2.0] - seen[0.5] == pytest.approx(12.0, abs=1.5)
assert seen[0.5] - seen[0.125] == pytest.approx(12.0, abs=1.5)
def test_a_nearer_sensor_reads_stronger_than_a_further_one():
loud = keyed(a.tower_frame(0x1A2B, 21.5, 48, "A"), amplitude=1.0)
faint = keyed(a.tower_frame(0x0C41, 3.2, 91, "B"), amplitude=0.1)
got = {r.sensor: r.snr
for r in a.readings_from(block_of((0.05, loud), (0.5, faint)),
RATE, offset=OFFSET)}
assert set(got) == {"1A2B", "0C41"}
assert got["1A2B"] - got["0C41"] == pytest.approx(20.0, abs=4.0)
def test_the_strongest_of_the_three_copies_is_the_one_reported():
"""They go out milliseconds apart and arrive at whatever fading does to
each, so the best of them is the fairer answer to how well it is heard."""
frame = a.tower_frame(0x1A2B, 21.5, 48, "A")
quiet = a.modulate(frame, 250_000.0, amplitude=0.12, noise=0.0,
repeats=1, lead_us=0.0)
loud = a.modulate(frame, 250_000.0, amplitude=1.0, noise=0.0,
repeats=1, lead_us=0.0)
# On a noise floor, because a ratio needs something to be a ratio to:
# a block of literal silence has no strength to report and says so.
block = block_of((0.04, quiet), (0.5, loud), rate=250_000.0, offset=0.0)
got = a.readings_from(block, 250_000.0)
assert len(got) == 1 and got[0].copies == 2
assert got[0].snr > 25.0 # the loud copy, not the quiet one
def test_a_burst_with_no_measured_level_reports_no_strength():
"""Rather than minus infinity, or a number made up to fill the column."""
assert a.Burst().decibels == 0.0
assert a.Burst(level=float("inf")).decibels == 0.0
assert a.Burst(level=10.0).decibels == pytest.approx(20.0)