Write the aircraft down, and draw where they went
ADS-B was a live table and nothing else: an aircraft was overhead for four minutes and then gone, with nothing kept. Now everything heard goes into adsb_<time>.jsonl as it arrives -- one object per frame, the raw hex beside what was read out of it, flushed per line because a listening session ends with control-C -- with a readable report beside it. flights.py asks who the aircraft are: adsbdb for the airframe and the route, hexdb behind it, cached for a month. What needs no website is answered without one, because the ICAO address block says which country registered the aircraft and the first three letters of an airline callsign are its designator. Nothing but the address and the callsign heard on the air is ever sent. bandsaunter flights [LOG...] --out sky.gif reads a log back and draws the evening as a map with the clock running. Every frame is a moment: each aircraft is where it actually was then, interpolated between the position reports either side of it and dead-reckoned from its last speed and heading between them, and dropped rather than guessed at once it has not been heard for --stale seconds. The GIF is written here -- palette, LZW, frame differencing against a transparent index -- so nothing but numpy is needed; ffmpeg writes an MP4 where it happens to be installed, and .png draws the whole evening at once. The decoder needed 6.3 s to read a second of sky, so a live capture was losing six frames in seven. Reading the bits off a running total instead of summing each window takes that to 0.6 s, with identical output. --simulate flies six aircraft that are not there past a receiver that is not there, through the real encoder, the real checksum and the real decoder, so all of this can be tried without an aerial. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
a8a8548369
commit
eae60cb04d
15 changed files with 3857 additions and 177 deletions
|
|
@ -91,6 +91,32 @@ def test_every_frame_survives_a_noisy_receiver(noise):
|
|||
assert registry.aircraft["4CA1FA"].located
|
||||
|
||||
|
||||
def test_a_frame_at_the_very_end_of_the_block_is_not_half_read():
|
||||
"""The bit reader runs off the end of the samples, and a long frame that
|
||||
does not fit must not be read as the short frame that does."""
|
||||
frame = gen.identification(0x4CA1FA, "RYR1234")
|
||||
samples = gen.modulate([frame], gap_us=8.0)
|
||||
cut = samples[:samples.size - int(0.5 * RATE / 1e6)] # half a bit short
|
||||
got = decode_frames(cut, RATE)
|
||||
assert got == [] or got[0].icao == "4CA1FA"
|
||||
|
||||
|
||||
def test_a_second_of_sky_is_decoded_in_less_than_a_second():
|
||||
"""A capture mode that cannot keep up is not capturing. Timed loosely --
|
||||
this is about the shape of the work, not the speed of the machine."""
|
||||
import time
|
||||
|
||||
from bandsaunter.adsb import SAMPLE_RATE, SimulatedSky, default_sky
|
||||
|
||||
sky = SimulatedSky(default_sky(), noise=0.02, seed=1)
|
||||
block = sky.read_samples(int(SAMPLE_RATE))
|
||||
started = time.perf_counter()
|
||||
got = decode_frames(block, SAMPLE_RATE)
|
||||
took = time.perf_counter() - started
|
||||
assert len(got) >= 18 # six aircraft, three frames each
|
||||
assert took < 3.0, f"a second of sky took {took:.1f} s to decode"
|
||||
|
||||
|
||||
def test_many_aircraft_are_kept_apart():
|
||||
frames = []
|
||||
for i, icao in enumerate((0x4CA1FA, 0xA0B1C2, 0x3C6444, 0x780102)):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue