Hear every sensor in the garden, not only the loudest one

Reported as reading nothing at all with several sensors in range.  Two faults,
either of which is enough on its own, and both of them things I assumed rather
than checked -- this was written against messages I generated myself and never
against a sensor.

The first is the threshold.  Bursts were found by setting one level per second
of band, halfway between the noise floor and the loudest thing in that second.
That is the obvious way to write it and it is wrong: a sensor on the windowsill
and a sensor at the end of the garden differ by forty decibels, so a level set
halfway to the near one sits above everything the far one ever does.  The far
ones do not come through weakly, they vanish -- and vanish only while the near
one is transmitting, which is as confusing a symptom as radio produces.  A
block with one loud sensor in it yielded exactly one sensor however many were
out there.

Finding bursts is now two passes.  The first asks only where anything happened
at all and asks it against the noise -- the bottom fifth of the second, which
is noise however busy the rest was, and which does not move when something
loud arrives.  Whatever clears that is grouped into regions, and the second
pass re-thresholds each region against its own high and low.  Every sensor is
sliced at its own amplitude.  Six sensors spanning eighty times in strength
now all come back from one second of band.

The second fault is that the slicer knew how a bit is drawn.  It read a pulse
by comparing it with the gap that followed, which is right when the gap is the
complement of the pulse so that every bit takes the same time, and wrong when
the gap is a fixed spacer: a two-hundred-and-twenty microsecond pulse against
a two-hundred microsecond spacer is the longer of the two and reads as a one,
which is the wrong bit, and then every message fails its checksum having said
nothing about why.  Nothing is assumed now -- not which of the pulse and the
gap carries the bit, not whether the gap is a complement or a spacer, not
which of long and short means one.  The same burst is read half a dozen ways
and the checksums say which reading it was, at most one being able to satisfy
one.  Copies are counted per message rather than per reading, or two readings
of one burst would corroborate each other and the rule protecting the two
thinly-checked models would protect nothing.

Both were caught the same way: by measuring, rather than by reading the code
again.  A thousand seconds of the invented garden still yields no sensor that
is not there, and reception of the ones that are is up by a quarter, because
bursts that used to be masked now decode.

And, because none of the above should have needed me: `bandsaunter weather
--diagnose` prints each second taken apart stage by stage -- the noise, the
level a burst must clear, the loudest thing in the block, then every burst
with the lengths of its pulses and gaps and whatever was made of them.  Those
lengths are the useful part: a real message has two or three of them and
nothing in between, which says at a glance whether the trouble is the radio or
the arithmetic.  At the end it says which of five things it was: nothing
arriving, nothing above the noise, something never keyed, bursts that framed
as nothing, or messages that framed and arrived only once.  `--save-iq FILE`
keeps the raw samples for whatever that cannot settle.

Full suite 2286 passed; the new work checked against six deliberately broken
builds.  Built as 2026-09-07_02.

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 18:22:53 -07:00
parent 65cc03b78d
commit 335d83f8a0
10 changed files with 1105 additions and 96 deletions

103
README.md
View file

@ -1959,15 +1959,42 @@ Only then is the magnitude taken. Filtering before detection rather than after
is what keeps the neighbours — a doorbell, a tyre sensor, a car key — from
adding themselves to the envelope of the sensor.
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 — rather than deciding
from the timings, which is guessing, and wrong on a weak burst where the edges
have moved. A transmitter running ten per cent fast is read correctly and
never noticed, which matters: these transmitters are unlocked and drift tens
of kilohertz and a few per cent of rate with the temperature. An outdoor
sensor in January is not the one that was on the fence in July.
**Finding the bursts is done in two passes, and the reason is having more than
one sensor.** The first pass only asks where anything is happening at all, and
asks it against the noise — the bottom fifth of the second, which is noise
however busy the rest was. Whatever clears that is grouped into regions, and
the second pass re-thresholds each region against its own high and low, so
every sensor is sliced at its own amplitude.
The obvious way to write this is one threshold per second, set halfway between
the noise floor and the loudest thing in it. That is wrong, and wrong in a way
worth describing because the symptom is so odd: a sensor on the windowsill and
a sensor at the end of the garden differ by forty decibels, so a threshold
halfway to the near one sits above everything the far one ever does — and the
far ones disappear, completely, and only while the near one is transmitting. A
block with one loud sensor in it yields exactly one sensor however many are out
there.
**Slicing the envelope into bits never measures anything against a clock**,
and assumes as little as it can about how a bit is drawn. Three things are not
assumed. Which of the pulse and the gap carries the bit — the newer sensors
vary the pulse, the older two vary the gap. Whether the gap is the complement
of the pulse, so that every bit takes the same time, or just a fixed spacer:
judged against a fixed 200 µs spacer a short pulse of 220 µs is longer than its
own gap and reads as a one, which is the wrong bit, and every message then
fails its checksum saying nothing about why. And which of long and short means
one.
So the same burst is read half a dozen ways — the pulse against its own gap,
the pulse against each threshold the pulse lengths themselves suggest, the
same for the gaps, and each of those inverted — and the checksums say which
reading it was. At most one of them can satisfy a checksum. It costs a few
microseconds per burst.
A transmitter running ten per cent fast is therefore read correctly and never
noticed, which matters: these are unlocked and drift tens of kilohertz and a
few per cent of rate with the temperature. An outdoor sensor in January is not
the one that was on the fence in July.
### Afterwards
@ -2031,6 +2058,7 @@ only what is shown and can be changed afterwards on an old log.
| Listen for | `--seconds` | until stopped | how long before stopping |
| Write a log | `--log` / `--no-log` | yes | one line of JSON per message |
| Print every message | `--messages` / `--no-messages` | no | a stream of lines instead of a table |
| Say what is arriving | `--diagnose` / `--no-diagnose` | no | each second taken apart stage by stage |
| Keep on screen for | `--hold` | 1800 s | how long a sensor stays after its last message |
| Show readings in | `--units` | metric | metric or imperial, for the display and the export |
| Only named sensors | `--only-named` / `--all-sensors` | no | ignore anything without a name |
@ -2039,6 +2067,7 @@ only what is shown and can be changed afterwards on an old log.
| Also write a spreadsheet | `--csv` / `--no-csv` | no | CSV beside the log |
`--name ID=NAME` is not a setting: it names a sensor and is repeatable.
Neither is `--save-iq FILE`, which is a one-off capture of the raw samples.
Saved in `weather.yaml` beside the other settings, from the menu's **s** or by
hand. `bandsaunter readings` also takes `--sensor NAME` to narrow a log to one
@ -2061,12 +2090,56 @@ a program that cannot read a thermometer.
### 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 `--gain 40` if the automatic gain control is not finding
them, and `--messages` to watch individual receptions arrive while moving the
aerial about.
**`bandsaunter weather --diagnose` is the answer to this**, because "nothing
was heard" is four different faults wearing the same coat and they want four
different answers. It prints each second of band taken apart stage by stage:
```
12.4s noise 0.0219 gate 0.0450 peak 1.0179 (46.4x the noise) 3 bursts
60 pulses pulses 219×29 399×27 597×4 gaps 221×26 402×29 600×4
Tower 592TXR 1A2B ch A temperature 8.4 C humidity 88%
```
Read it from the left.
**`peak` is barely above `noise`, no bursts.** Nothing is arriving. That is an
aerial. These are a few milliwatts: a quarter-wave whip for 433.92 MHz is 17 cm
of wire, which is the stock telescopic aerial collapsed to about that, and
indoors behind a wall with the dongle in the back of a machine is usually the
problem. Try `--gain 40` if the automatic gain control is not finding them.
**`peak` is well above `noise` and there are no bursts.** Something is there
and did not group — usually a continuous transmitter rather than a keyed one,
which is not one of these.
**Bursts, but the pulse lengths are not two or three clean groups.** The
receiver is hearing it and the slicing is wrong. A real message shows two or
three lengths with nothing in between, like the `219×29 399×27 597×4` above.
A smear of lengths means noise is being sliced as signal, or two sensors are
transmitting over each other.
**Bursts with clean pulse lengths and `nothing framed`.** The radio is fine
and the message is from a model this does not read, or reads differently.
That line of pulse lengths is exactly what is needed to add it.
**`framed, but this model needs the same message twice`.** It was read
correctly and arrived once. The two older models are only believed on a second
copy; a stronger signal fixes it.
When the listening stops it says which of those it was, once:
```
╭───────────────────────── what that came to ──────────────────────────╮
│ 40 bursts were received and sliced, and not one of them framed as a │
│ message. The radio end is working: what is arriving is a model this │
│ cannot read, or reads differently. The pulse lengths printed above │
│ are exactly what is needed to add it. │
╰──────────────────────────────────────────────────────────────────────╯
```
`--save-iq FILE` writes the raw samples alongside, for working out anything the
diagnosis cannot. It is 8 MB a second at the default rate, so bound it with
`--seconds 60`.
## Meters on 900 MHz