Read the weather sensors on 433 MHz, and let them be given names
A consumer weather station is two things. The display on the kitchen wall is one of them; the other is a plastic box on a fence post that says what it can see every sixteen seconds, in the clear, to anyone who happens to be listening. This reads the box. A section of its own, like the aircraft one, and for the same reason: it does not fit through the scanner. A sensor message is a burst of a carrier switched on and off, a fifth of a second long, and the scan path is a squelch and a recorder -- it would record the bursts as clicks in a WAV file and decode nothing. `bandsaunter weather` listens, `bandsaunter readings` reads a log back, `bandsaunter sensors` says what is out there. Item 6 in the main menu is the same thing without a command line. Five families: the Tower 592TXR, the 5-in-1, the 6045M lightning detector, the 609TXC and the 606TX. Temperature, humidity, wind speed and direction, rainfall, strike counts, how far off the storm is, and battery state from all of them. Every one is implemented from its published description and checked against frames built from the same description, which proves the framing, the parity, the checksums and the arithmetic and is not the same as having held one of each. The naming is the point. A sensor broadcasts an identity, and that identity is a number that came out of a hat in a factory; it tells one sensor from another and is no use at all for telling which is which. So press n while listening: the display comes down, the sensors are listed, you name one, and it goes back up, with the receiver running throughout. That is the moment it is possible -- the sensor is on the screen saying 3.1 degrees, and the person watching is the one who knows that the cold one is the shed. An hour later it is a list of hexadecimal again. Names are written the instant they are given rather than at exit, to a neighbouring file renamed over the old one, and one given before a sensor has ever been heard waits under its identity and moves across when the first message says which model it is. Four things keep the neighbours' doorbells off the display. The checks the message carries; a second copy, for the two models that carry only one byte of check between them; a plausibility range, because a checksum can be satisfied by a message the hardware could not send; and where in the burst the message sits. That last one is the one that is easy to miss: a seven-byte message read out of the front of a real eight-byte one is made of that message's own payload bytes, whose parity is already correct, so the parity bits contribute nothing and one byte of sum is all that is left -- and corroboration cannot help, the three copies being identical. What gives that window away every time is that it ends a whole byte before the burst does. The Atlas is nine bytes like the lightning detector and lays its payload out differently, so every decoder insists on a message type it knows. Anything else that frames correctly is reported with its identity and no weather, because wrong weather under somebody's sensor name is a worse answer than none. ism.py now delegates to this rather than keeping a second implementation of the tower sensor, which fixes the channel letters -- A is 3, B is 2, C is 0, and there is no D -- and the battery bit, which is set while the battery is good. The two thinly-checked models are not reported from a scan at all: a scan hears one burst, and they need two. The option menus are now handed the module that owns the options rather than importing the aircraft one, so one set of screens drives both sections and will drive a third. 169 new tests, checked against nineteen deliberately broken builds; two of the tests were too weak to notice their own mutation and were rewritten. Full suite 2252 passed. Built as 2026-09-07_01. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
f01de4117f
commit
65cc03b78d
18 changed files with 6006 additions and 123 deletions
|
|
@ -1,5 +1,5 @@
|
|||
.\" Generated by packaging/make-man.py -- do not edit by hand.
|
||||
.TH BANDSAUNTER 1 "2026-09-06" "bandsaunter 2026-09-06_04" "User Commands"
|
||||
.TH BANDSAUNTER 1 "2026-09-07" "bandsaunter 2026-09-07_01" "User Commands"
|
||||
.SH NAME
|
||||
bandsaunter \- scan, record and identify radio signals with an RTL-SDR
|
||||
.SH SYNOPSIS
|
||||
|
|
@ -85,6 +85,20 @@ animation. See
|
|||
.B AIRCRAFT
|
||||
below.
|
||||
.TP
|
||||
.B weather
|
||||
Listen to the AcuRite weather sensors on 433.92 MHz, and name them as they
|
||||
arrive. See
|
||||
.B WEATHER SENSORS
|
||||
below.
|
||||
.TP
|
||||
.B readings
|
||||
Read a weather log back: the report, and a spreadsheet. See
|
||||
.B WEATHER SENSORS
|
||||
below.
|
||||
.TP
|
||||
.B sensors
|
||||
List every weather sensor heard, and give them names.
|
||||
.TP
|
||||
.B analyze
|
||||
Identify a signal in an already-recorded file, decode Morse from it, or write
|
||||
out the picture it turns out to be.
|
||||
|
|
@ -2148,6 +2162,289 @@ sideband by which way the signal's energy leans, so
|
|||
.B \-\-mode usb
|
||||
is not needed. The frequency in the filename is the carrier \[em] the
|
||||
frequency to dial into a radio.
|
||||
.SH WEATHER SENSORS
|
||||
A consumer weather station is two things. The display on the kitchen wall is
|
||||
one of them; the other is a plastic box on a fence post that says what it can
|
||||
see every sixteen seconds, in the clear, on 433.92 MHz, to anyone who happens
|
||||
to be listening.
|
||||
.B bandsaunter weather
|
||||
reads the box.
|
||||
.PP
|
||||
It is a mode of its own, like the aircraft one, and for the same reason: it
|
||||
does not fit through the scanner. A sensor message is a burst of a carrier
|
||||
switched on and off, a fifth of a second long, and the scan path is a squelch
|
||||
and a recorder \[em] it would record the bursts as clicks in a WAV file and
|
||||
decode nothing.
|
||||
.SS What it reads
|
||||
Five families, each with its own framing and its own check.
|
||||
.TP
|
||||
.B "Tower 592TXR / 06002RM"
|
||||
Seven bytes: temperature and humidity.
|
||||
.TP
|
||||
.B "5-in-1 06014RM / VN1TXC"
|
||||
Eight bytes, in two kinds sent alternately: wind speed with wind direction and
|
||||
rainfall, or wind speed with temperature and humidity. It has more to say than
|
||||
fits in one message, so the display keeps the newest value of each quantity
|
||||
rather than the newest message.
|
||||
.TP
|
||||
.B "Lightning 6045M"
|
||||
Nine bytes: temperature, humidity, the cumulative strike count, and how far
|
||||
off the storm is. A bit set when the detector believes it is being interfered
|
||||
with is shown too, because a strike count that climbs while it is set is not
|
||||
lightning.
|
||||
.TP
|
||||
.B 609TXC
|
||||
Five bytes: temperature and humidity.
|
||||
.TP
|
||||
.B 606TX
|
||||
Four bytes: temperature, and nothing else at all.
|
||||
.PP
|
||||
Battery state comes from all of them. The Atlas, the 986 and 515 fridge
|
||||
thermometers, the 00275rm room monitor and the 899 standalone rain gauge are
|
||||
on the same band and are not decoded; a message from one whose framing happens
|
||||
to match is reported as an unknown message type with its identity and nothing
|
||||
else, rather than guessed at.
|
||||
.PP
|
||||
These formats are implemented from their published descriptions and are
|
||||
checked against frames built from the same descriptions. That proves the
|
||||
framing, the parity, the checksums and the arithmetic; it is not the same as
|
||||
having held every one of these sensors.
|
||||
.SS Naming a sensor
|
||||
A sensor broadcasts an identity, and that identity is a number that came out
|
||||
of a hat in a factory \[em] or a different number out of the same hat the next
|
||||
time the batteries were changed. It tells one sensor from another and is no
|
||||
use at all for telling which is which.
|
||||
.PP
|
||||
So press
|
||||
.B n
|
||||
while listening. The display comes down, the sensors are listed with numbers,
|
||||
you pick one and type a name, and it goes back up. The receiver keeps running
|
||||
throughout: a slow typist loses a few seconds of weather and nothing else.
|
||||
That is the moment it is possible to do \[em] the sensor is on the screen
|
||||
saying 3.1 degrees, and the person watching is the one who knows that the cold
|
||||
one is the shed.
|
||||
.PP
|
||||
Names can also be given with
|
||||
.BI \-\-name " ID=NAME"
|
||||
on
|
||||
.B "bandsaunter weather"
|
||||
or
|
||||
.BR "bandsaunter sensors" ,
|
||||
before or after anything has been heard: a name given before the sensor has
|
||||
ever been received waits under its identity alone, because nothing yet knows
|
||||
which model it is, and moves across the moment the first message arrives. They
|
||||
live in
|
||||
.I sensors.yaml
|
||||
beside the settings, are written the moment they are given rather than when
|
||||
the program exits, and are written to a neighbouring file which is renamed
|
||||
over the old one, so a machine losing power halfway through leaves either the
|
||||
old names or the new ones and never half of each. Nothing is ever dropped for
|
||||
being stale.
|
||||
.SS Why nothing false gets through
|
||||
433 MHz is a crowded band \[em] doorbells, car keys, tyre-pressure sensors,
|
||||
garage doors \[em] and a decoder that looks at every bit offset of every burst
|
||||
will find a message in noise if it is allowed to. Four things stop it.
|
||||
.PP
|
||||
The three newer models carry an eight-bit sum plus odd parity in the top bit
|
||||
of every payload byte, which is twelve to fourteen bits of check.
|
||||
.PP
|
||||
The two older models carry one byte of check between them, which is one false
|
||||
message in two hundred and fifty-six, so those two are only believed when the
|
||||
same message arrives twice. It costs nothing: these sensors send everything
|
||||
three times in a row, for exactly this reason.
|
||||
.PP
|
||||
Nothing outside what the hardware can report is accepted \[em] no temperature
|
||||
beyond \-40 to 70 \[de]C, no humidity above 100 per cent, no wind the
|
||||
anemometer cannot physically produce.
|
||||
.PP
|
||||
And a message must sit where a message sits. A seven-byte message read out of
|
||||
the front of a real eight-byte one is made of that message's own payload
|
||||
bytes, whose parity is already correct, so the parity bits contribute nothing
|
||||
and one byte of sum is all that is left. Corroboration does not help either,
|
||||
the copies of a message being identical. What gives that window away every
|
||||
time is that it ends a whole byte before the burst does.
|
||||
.SS Getting it off the air
|
||||
The receiver is tuned a little to one side of 433.92 MHz, because every
|
||||
RTL-SDR puts a spike of its own at whatever it is tuned to, and a spike
|
||||
sitting on top of a signal that works by being switched on and off is the one
|
||||
thing that stops it being off. The sensors are shifted back to the middle in
|
||||
software, which puts the spike out at the edge instead.
|
||||
.B \-\-offset 0
|
||||
tunes straight at them, which is worth trying once to see what the spike was
|
||||
costing.
|
||||
.PP
|
||||
A running average of the complex samples then rejects the spike, and only
|
||||
after that is the magnitude taken \[em] filtering before detection rather than
|
||||
after is what keeps the neighbours out of the envelope of the sensor.
|
||||
.PP
|
||||
Slicing the envelope into bits never measures anything against a clock. The
|
||||
newer sensors vary the length of the pulse and keep the gaps even; the two
|
||||
older ones keep the pulse even and vary the gap. Both readings of the same
|
||||
pulses are tried and the checksums say which it was. A transmitter running ten
|
||||
per cent fast is read correctly and never noticed, which matters: these are
|
||||
unlocked and drift with the temperature, and an outdoor sensor in January is
|
||||
not the one that was on the fence in July.
|
||||
.SS Afterwards
|
||||
When the listening stops, two tables. The first is about reception \[em] who,
|
||||
how often, how well \[em] and is the one to look at when something is missing:
|
||||
these transmit on a fixed cycle, so a gap of thirty seconds from a sensor that
|
||||
sends every sixteen means half of them are being missed, and that is an aerial
|
||||
problem rather than a weather one. The second is the first, last, lowest and
|
||||
highest of everything each sensor reported.
|
||||
.PP
|
||||
There is no average, deliberately. These arrive every sixteen seconds when the
|
||||
sensor is in range and not at all when it is not, and rain and cold both
|
||||
shorten the range of a 433 MHz transmitter, so the mean of what was received
|
||||
is the mean of a sample whose gaps are themselves the weather. A bearing gets
|
||||
no lowest or highest either: north is 0 and also 360.
|
||||
.PP
|
||||
.B \-\-csv
|
||||
writes a column per quantity and a row per reading, with the sensor's name in
|
||||
the second column and the unit in the heading rather than beside every number.
|
||||
.B "bandsaunter readings \-\-csv"
|
||||
does the same to an old log, and takes
|
||||
.BI \-\-sensor " NAME"
|
||||
to narrow it to one sensor.
|
||||
.PP
|
||||
The log keeps the raw bytes of every message underneath whatever was made of
|
||||
them, because the message is the evidence and the rest of the line is an
|
||||
opinion about it. Readings are converted once, on the way in, to Celsius,
|
||||
kilometres an hour, millimetres and kilometres \[em] different models report
|
||||
in different units \[em] so
|
||||
.B \-\-units imperial
|
||||
changes only what is shown, and can be changed afterwards on an old log.
|
||||
.SS Without a sensor
|
||||
.B \-\-simulate
|
||||
puts six sensors on a fence that does not exist, one of every model,
|
||||
transmitting real messages with real checksums, keyed on and off as a real one
|
||||
does, through the real filter, the real slicer and the real decoders. Nothing
|
||||
touches the receiver.
|
||||
.SS If nothing is heard
|
||||
These are a few milliwatts. A quarter-wave whip for 433.92 MHz is 17 cm of
|
||||
wire, and the stock telescopic aerial set to about that length works well;
|
||||
indoors, behind a wall, with the dongle in the back of a machine, is usually
|
||||
the problem. Try
|
||||
.B \-\-gain 40
|
||||
if the automatic gain control is not finding them, and
|
||||
.B \-\-messages
|
||||
to watch individual receptions arrive while moving the aerial about.
|
||||
.SH WEATHER OPTIONS
|
||||
Every option the weather side takes, in the four groups the menu shows them
|
||||
in. Each is a flag here and a line in the menu, and both come from one table
|
||||
in the program, so they cannot disagree.
|
||||
.SS Receiver
|
||||
.TP
|
||||
.B --device
|
||||
Receiver \[em] which receiver to use, when more than one is plugged in.
|
||||
.br
|
||||
Setting name \fBdevice\fR, default \fB0\fR.
|
||||
.br
|
||||
Accepts: at least 0.
|
||||
.TP
|
||||
.B --gain
|
||||
Gain \[em] tuner gain in dB, or automatic.
|
||||
.br
|
||||
Setting name \fBgain\fR, default \fBauto\fR.
|
||||
.RS
|
||||
.PP
|
||||
Leave it automatic first. If a sensor you know is there never appears, try 40 or so.
|
||||
.RE
|
||||
.TP
|
||||
.B --rate
|
||||
Sample rate \[em] how fast to sample; a quarter of a megasample is the minimum (Hz).
|
||||
.br
|
||||
Setting name \fBrate\fR, default \fB1.024 MHz\fR.
|
||||
.br
|
||||
Accepts: at least 250000.
|
||||
.TP
|
||||
.B --frequency --freq
|
||||
Sensors on \[em] where the sensors transmit (Hz).
|
||||
.br
|
||||
Setting name \fBfrequency\fR, default \fB433.92 MHz\fR.
|
||||
.br
|
||||
Accepts: at least 3e+08, at most 1e+09.
|
||||
.TP
|
||||
.B --offset
|
||||
Tuning offset \[em] how far to one side of the sensors to tune the receiver (Hz).
|
||||
.br
|
||||
Setting name \fBoffset\fR, default \fB250 kHz\fR.
|
||||
.br
|
||||
Accepts: at least 0.
|
||||
.RS
|
||||
.PP
|
||||
A quarter of the sample rate is right and is the default. Only set it to zero to see what the spike was costing.
|
||||
.RE
|
||||
.TP
|
||||
.B --simulate / --no-simulate
|
||||
Invent a garden \[em] put imaginary sensors on an imaginary fence.
|
||||
.br
|
||||
Setting name \fBsimulate\fR, default \fBno\fR.
|
||||
.RS
|
||||
.PP
|
||||
Turn this on to see what the whole thing does without hardware. Turn it off to hear real sensors.
|
||||
.RE
|
||||
.PP
|
||||
.SS Listening
|
||||
.TP
|
||||
.B --seconds
|
||||
Listen for \[em] how long to listen before stopping (0 = until interrupted) (s).
|
||||
.br
|
||||
Setting name \fBseconds\fR, default \fBuntil stopped\fR.
|
||||
.br
|
||||
Accepts: at least 0.
|
||||
.TP
|
||||
.B --log / --no-log
|
||||
Write a log \[em] write every message down as it arrives.
|
||||
.br
|
||||
Setting name \fBlog\fR, default \fByes\fR.
|
||||
.TP
|
||||
.B --messages / --no-messages
|
||||
Print every message \[em] one line per message instead of a table that updates in place.
|
||||
.br
|
||||
Setting name \fBmessages\fR, default \fBno\fR.
|
||||
.TP
|
||||
.B --hold
|
||||
Keep on screen for \[em] how long a sensor stays on the display after its last message (s).
|
||||
.br
|
||||
Setting name \fBhold\fR, default \fB1800 s\fR.
|
||||
.br
|
||||
Accepts: at least 1.
|
||||
.PP
|
||||
.SS Sensors
|
||||
.TP
|
||||
.B --units
|
||||
Show readings in \[em] metric or imperial, for the display and the export.
|
||||
.br
|
||||
Setting name \fBunits\fR, default \fBmetric\fR.
|
||||
.br
|
||||
Accepts: one of: metric, imperial.
|
||||
.TP
|
||||
.B --only-named / --all-sensors
|
||||
Only named sensors \[em] ignore sensors that have not been given a name.
|
||||
.br
|
||||
Setting name \fBonly_named\fR, default \fBno\fR.
|
||||
.RS
|
||||
.PP
|
||||
Leave it off until you have named things, or there will be nothing to name.
|
||||
.RE
|
||||
.TP
|
||||
.B --unknown / --no-unknown
|
||||
Show unreadable models \[em] list sensors whose messages framed correctly and were not understood.
|
||||
.br
|
||||
Setting name \fBunknown\fR, default \fByes\fR.
|
||||
.PP
|
||||
.SS Afterwards
|
||||
.TP
|
||||
.B --report / --no-report
|
||||
Report at the end \[em] print what each sensor said when the listening stops.
|
||||
.br
|
||||
Setting name \fBreport\fR, default \fByes\fR.
|
||||
.TP
|
||||
.B --csv / --no-csv
|
||||
Also write a spreadsheet \[em] write the readings as CSV beside the log.
|
||||
.br
|
||||
Setting name \fBcsv\fR, default \fBno\fR.
|
||||
.PP
|
||||
.SH FILES
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/config.yaml
|
||||
|
|
@ -2156,6 +2453,13 @@ The settings every run starts from.
|
|||
.I ~/.config/bandsaunter/aircraft.yaml
|
||||
The aircraft options, as saved from the menus.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/weather.yaml
|
||||
The weather options, as saved from the menus.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/sensors.yaml
|
||||
What each weather sensor is called. The only file here holding anything a
|
||||
person typed; safe to edit by hand.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/*.yaml
|
||||
Named profiles.
|
||||
.TP
|
||||
|
|
@ -2164,6 +2468,11 @@ Every ADS-B frame heard in one listening session, with a
|
|||
.I .txt
|
||||
report and any picture drawn from it beside it.
|
||||
.TP
|
||||
.IR weather_ * .jsonl
|
||||
Every weather sensor message heard in one listening session, with a
|
||||
.I .csv
|
||||
of the readings beside it where one was asked for.
|
||||
.TP
|
||||
.I ~/bandsaunter/
|
||||
Where recordings, transcripts and logs are written, unless
|
||||
.B \-\-output
|
||||
|
|
|
|||
|
|
@ -57,15 +57,29 @@ def settings_section() -> list[str]:
|
|||
|
||||
|
||||
def aircraft_section() -> list[str]:
|
||||
"""Every ADS-B option, from the same table the menu and flags come from.
|
||||
|
||||
Written out rather than described in prose, so that an option added to
|
||||
the program cannot quietly fail to appear in its manual.
|
||||
"""
|
||||
"""Every ADS-B option, from the same table the menu and flags come from."""
|
||||
from bandsaunter import aircraft as air
|
||||
|
||||
return options_section(air)
|
||||
|
||||
|
||||
def weather_section() -> list[str]:
|
||||
"""Every weather option, from the same table."""
|
||||
from bandsaunter import weather as wx
|
||||
|
||||
return options_section(wx)
|
||||
|
||||
|
||||
def options_section(air) -> list[str]:
|
||||
"""One section's options, written out from the table the program uses.
|
||||
|
||||
Written out rather than described in prose, so that an option added to
|
||||
the program cannot quietly fail to appear in its manual. Both sections
|
||||
describe their options in the same shape, so this does not need to know
|
||||
which one it has been handed.
|
||||
"""
|
||||
out = []
|
||||
defaults = air.AircraftOptions()
|
||||
defaults = air.defaults()
|
||||
for group in air.OPTION_GROUPS:
|
||||
out.append(f'.SS {esc(group)}')
|
||||
for o in air.in_group(group):
|
||||
|
|
@ -180,6 +194,20 @@ animation. See
|
|||
.B AIRCRAFT
|
||||
below.
|
||||
.TP
|
||||
.B weather
|
||||
Listen to the AcuRite weather sensors on 433.92 MHz, and name them as they
|
||||
arrive. See
|
||||
.B WEATHER SENSORS
|
||||
below.
|
||||
.TP
|
||||
.B readings
|
||||
Read a weather log back: the report, and a spreadsheet. See
|
||||
.B WEATHER SENSORS
|
||||
below.
|
||||
.TP
|
||||
.B sensors
|
||||
List every weather sensor heard, and give them names.
|
||||
.TP
|
||||
.B analyze
|
||||
Identify a signal in an already-recorded file, decode Morse from it, or write
|
||||
out the picture it turns out to be.
|
||||
|
|
@ -1246,6 +1274,177 @@ sideband by which way the signal's energy leans, so
|
|||
.B \-\-mode usb
|
||||
is not needed. The frequency in the filename is the carrier \[em] the
|
||||
frequency to dial into a radio.
|
||||
.SH WEATHER SENSORS
|
||||
A consumer weather station is two things. The display on the kitchen wall is
|
||||
one of them; the other is a plastic box on a fence post that says what it can
|
||||
see every sixteen seconds, in the clear, on 433.92 MHz, to anyone who happens
|
||||
to be listening.
|
||||
.B bandsaunter weather
|
||||
reads the box.
|
||||
.PP
|
||||
It is a mode of its own, like the aircraft one, and for the same reason: it
|
||||
does not fit through the scanner. A sensor message is a burst of a carrier
|
||||
switched on and off, a fifth of a second long, and the scan path is a squelch
|
||||
and a recorder \[em] it would record the bursts as clicks in a WAV file and
|
||||
decode nothing.
|
||||
.SS What it reads
|
||||
Five families, each with its own framing and its own check.
|
||||
.TP
|
||||
.B "Tower 592TXR / 06002RM"
|
||||
Seven bytes: temperature and humidity.
|
||||
.TP
|
||||
.B "5-in-1 06014RM / VN1TXC"
|
||||
Eight bytes, in two kinds sent alternately: wind speed with wind direction and
|
||||
rainfall, or wind speed with temperature and humidity. It has more to say than
|
||||
fits in one message, so the display keeps the newest value of each quantity
|
||||
rather than the newest message.
|
||||
.TP
|
||||
.B "Lightning 6045M"
|
||||
Nine bytes: temperature, humidity, the cumulative strike count, and how far
|
||||
off the storm is. A bit set when the detector believes it is being interfered
|
||||
with is shown too, because a strike count that climbs while it is set is not
|
||||
lightning.
|
||||
.TP
|
||||
.B 609TXC
|
||||
Five bytes: temperature and humidity.
|
||||
.TP
|
||||
.B 606TX
|
||||
Four bytes: temperature, and nothing else at all.
|
||||
.PP
|
||||
Battery state comes from all of them. The Atlas, the 986 and 515 fridge
|
||||
thermometers, the 00275rm room monitor and the 899 standalone rain gauge are
|
||||
on the same band and are not decoded; a message from one whose framing happens
|
||||
to match is reported as an unknown message type with its identity and nothing
|
||||
else, rather than guessed at.
|
||||
.PP
|
||||
These formats are implemented from their published descriptions and are
|
||||
checked against frames built from the same descriptions. That proves the
|
||||
framing, the parity, the checksums and the arithmetic; it is not the same as
|
||||
having held every one of these sensors.
|
||||
.SS Naming a sensor
|
||||
A sensor broadcasts an identity, and that identity is a number that came out
|
||||
of a hat in a factory \[em] or a different number out of the same hat the next
|
||||
time the batteries were changed. It tells one sensor from another and is no
|
||||
use at all for telling which is which.
|
||||
.PP
|
||||
So press
|
||||
.B n
|
||||
while listening. The display comes down, the sensors are listed with numbers,
|
||||
you pick one and type a name, and it goes back up. The receiver keeps running
|
||||
throughout: a slow typist loses a few seconds of weather and nothing else.
|
||||
That is the moment it is possible to do \[em] the sensor is on the screen
|
||||
saying 3.1 degrees, and the person watching is the one who knows that the cold
|
||||
one is the shed.
|
||||
.PP
|
||||
Names can also be given with
|
||||
.BI \-\-name " ID=NAME"
|
||||
on
|
||||
.B "bandsaunter weather"
|
||||
or
|
||||
.BR "bandsaunter sensors" ,
|
||||
before or after anything has been heard: a name given before the sensor has
|
||||
ever been received waits under its identity alone, because nothing yet knows
|
||||
which model it is, and moves across the moment the first message arrives. They
|
||||
live in
|
||||
.I sensors.yaml
|
||||
beside the settings, are written the moment they are given rather than when
|
||||
the program exits, and are written to a neighbouring file which is renamed
|
||||
over the old one, so a machine losing power halfway through leaves either the
|
||||
old names or the new ones and never half of each. Nothing is ever dropped for
|
||||
being stale.
|
||||
.SS Why nothing false gets through
|
||||
433 MHz is a crowded band \[em] doorbells, car keys, tyre-pressure sensors,
|
||||
garage doors \[em] and a decoder that looks at every bit offset of every burst
|
||||
will find a message in noise if it is allowed to. Four things stop it.
|
||||
.PP
|
||||
The three newer models carry an eight-bit sum plus odd parity in the top bit
|
||||
of every payload byte, which is twelve to fourteen bits of check.
|
||||
.PP
|
||||
The two older models carry one byte of check between them, which is one false
|
||||
message in two hundred and fifty-six, so those two are only believed when the
|
||||
same message arrives twice. It costs nothing: these sensors send everything
|
||||
three times in a row, for exactly this reason.
|
||||
.PP
|
||||
Nothing outside what the hardware can report is accepted \[em] no temperature
|
||||
beyond \-40 to 70 \[de]C, no humidity above 100 per cent, no wind the
|
||||
anemometer cannot physically produce.
|
||||
.PP
|
||||
And a message must sit where a message sits. A seven-byte message read out of
|
||||
the front of a real eight-byte one is made of that message's own payload
|
||||
bytes, whose parity is already correct, so the parity bits contribute nothing
|
||||
and one byte of sum is all that is left. Corroboration does not help either,
|
||||
the copies of a message being identical. What gives that window away every
|
||||
time is that it ends a whole byte before the burst does.
|
||||
.SS Getting it off the air
|
||||
The receiver is tuned a little to one side of 433.92 MHz, because every
|
||||
RTL-SDR puts a spike of its own at whatever it is tuned to, and a spike
|
||||
sitting on top of a signal that works by being switched on and off is the one
|
||||
thing that stops it being off. The sensors are shifted back to the middle in
|
||||
software, which puts the spike out at the edge instead.
|
||||
.B \-\-offset 0
|
||||
tunes straight at them, which is worth trying once to see what the spike was
|
||||
costing.
|
||||
.PP
|
||||
A running average of the complex samples then rejects the spike, and only
|
||||
after that is the magnitude taken \[em] filtering before detection rather than
|
||||
after is what keeps the neighbours out of the envelope of the sensor.
|
||||
.PP
|
||||
Slicing the envelope into bits never measures anything against a clock. The
|
||||
newer sensors vary the length of the pulse and keep the gaps even; the two
|
||||
older ones keep the pulse even and vary the gap. Both readings of the same
|
||||
pulses are tried and the checksums say which it was. A transmitter running ten
|
||||
per cent fast is read correctly and never noticed, which matters: these are
|
||||
unlocked and drift with the temperature, and an outdoor sensor in January is
|
||||
not the one that was on the fence in July.
|
||||
.SS Afterwards
|
||||
When the listening stops, two tables. The first is about reception \[em] who,
|
||||
how often, how well \[em] and is the one to look at when something is missing:
|
||||
these transmit on a fixed cycle, so a gap of thirty seconds from a sensor that
|
||||
sends every sixteen means half of them are being missed, and that is an aerial
|
||||
problem rather than a weather one. The second is the first, last, lowest and
|
||||
highest of everything each sensor reported.
|
||||
.PP
|
||||
There is no average, deliberately. These arrive every sixteen seconds when the
|
||||
sensor is in range and not at all when it is not, and rain and cold both
|
||||
shorten the range of a 433 MHz transmitter, so the mean of what was received
|
||||
is the mean of a sample whose gaps are themselves the weather. A bearing gets
|
||||
no lowest or highest either: north is 0 and also 360.
|
||||
.PP
|
||||
.B \-\-csv
|
||||
writes a column per quantity and a row per reading, with the sensor's name in
|
||||
the second column and the unit in the heading rather than beside every number.
|
||||
.B "bandsaunter readings \-\-csv"
|
||||
does the same to an old log, and takes
|
||||
.BI \-\-sensor " NAME"
|
||||
to narrow it to one sensor.
|
||||
.PP
|
||||
The log keeps the raw bytes of every message underneath whatever was made of
|
||||
them, because the message is the evidence and the rest of the line is an
|
||||
opinion about it. Readings are converted once, on the way in, to Celsius,
|
||||
kilometres an hour, millimetres and kilometres \[em] different models report
|
||||
in different units \[em] so
|
||||
.B \-\-units imperial
|
||||
changes only what is shown, and can be changed afterwards on an old log.
|
||||
.SS Without a sensor
|
||||
.B \-\-simulate
|
||||
puts six sensors on a fence that does not exist, one of every model,
|
||||
transmitting real messages with real checksums, keyed on and off as a real one
|
||||
does, through the real filter, the real slicer and the real decoders. Nothing
|
||||
touches the receiver.
|
||||
.SS If nothing is heard
|
||||
These are a few milliwatts. A quarter-wave whip for 433.92 MHz is 17 cm of
|
||||
wire, and the stock telescopic aerial set to about that length works well;
|
||||
indoors, behind a wall, with the dongle in the back of a machine, is usually
|
||||
the problem. Try
|
||||
.B \-\-gain 40
|
||||
if the automatic gain control is not finding them, and
|
||||
.B \-\-messages
|
||||
to watch individual receptions arrive while moving the aerial about.
|
||||
.SH WEATHER OPTIONS
|
||||
Every option the weather side takes, in the four groups the menu shows them
|
||||
in. Each is a flag here and a line in the menu, and both come from one table
|
||||
in the program, so they cannot disagree.
|
||||
.WEATHER_OPTIONS_HERE
|
||||
.SH FILES
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/config.yaml
|
||||
|
|
@ -1254,6 +1453,13 @@ The settings every run starts from.
|
|||
.I ~/.config/bandsaunter/aircraft.yaml
|
||||
The aircraft options, as saved from the menus.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/weather.yaml
|
||||
The weather options, as saved from the menus.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/sensors.yaml
|
||||
What each weather sensor is called. The only file here holding anything a
|
||||
person typed; safe to edit by hand.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/*.yaml
|
||||
Named profiles.
|
||||
.TP
|
||||
|
|
@ -1262,6 +1468,11 @@ Every ADS-B frame heard in one listening session, with a
|
|||
.I .txt
|
||||
report and any picture drawn from it beside it.
|
||||
.TP
|
||||
.IR weather_ * .jsonl
|
||||
Every weather sensor message heard in one listening session, with a
|
||||
.I .csv
|
||||
of the readings beside it where one was asked for.
|
||||
.TP
|
||||
.I ~/bandsaunter/
|
||||
Where recordings, transcripts and logs are written, unless
|
||||
.B \-\-output
|
||||
|
|
@ -1373,6 +1584,8 @@ def main() -> int:
|
|||
text = "\n".join(out)
|
||||
text = text.replace(".AIRCRAFT_OPTIONS_HERE",
|
||||
"\n".join(aircraft_section()))
|
||||
text = text.replace(".WEATHER_OPTIONS_HERE",
|
||||
"\n".join(weather_section()))
|
||||
text = text.replace("\n\n", "\n") # troff dislikes blank lines
|
||||
target = Path(sys.argv[1] if len(sys.argv) > 1
|
||||
else Path(__file__).parent / "bandsaunter.1")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue