Source
Pick where samples come from. The fixture and file paths work with the page opened straight off disk. The two live paths need the page served over http, because browsers refuse USB and sockets to a file:// origin.
Fixture · no hardware
Synthesises real IQ from the same layout table the decoder reads, at a noise level you choose. The answers are known ahead of time, which is what makes the decode chain testable.
File
Feed a capture you already have. Raw IQ is read as unsigned 8-bit interleaved, the shape rtl_sdr writes.
Dongle over WebUSB CORRECTED AGAINST A WORKING RADIO
This programs the demodulator and the R820T tuner: the initialisation set, the tracking filter for the band, the phase locked loop, the gain, and the decimation filter. Up to v1.7.3 it wrote none of the tuner and none of the decimation filter, and three of its USB register addresses were wrong.
The register map now comes from a working implementation rather than from recollection, which is why it is worth trusting more than the last two attempts. Six corrections, any one of which produces silence on its own: the endpoint control register is 0x2148 and was being written at 0x2158, which is the packet size register, so the sample endpoint never came out of reset; the decimation coefficients were never written, and left at zero they silence the whole converter path; the tuner was never programmed at all; the demodulator was set up for a zero intermediate frequency tuner, which the R820T is not; the tuner was written seven registers at a time rather than one; and the stream used one transfer at a time, which drops samples continuously while the spectrum still looks healthy.
Two things are reported rather than enforced. The loop's lock bit is unreliable on several of the clones this is most likely to meet, so a failure to lock is said plainly and does not block streaming: if nothing decodes, it is the first thing to suspect. And the intermediate frequency filter is not calibrated, which for a 2.4 MHz window inside a filter built for 6 is a comfortable margin rather than a shortcut. Needs a served page, and on Windows the dongle needs the WinUSB driver. The frequency comes from the Band station; a dongle is retuned by stopping and claiming it again.
The gain field defaulted to zero until v1.8.2, and its label said zero meant automatic. That was true while there was an automatic path. There is not one now, so zero meant the lowest gain the tuner has, and a dongle set that way sitting on top of a meter hears a transmission barely above its own noise floor. If the last version framed nothing at all, this is the first thing to change.
Record what the dongle is actually delivering
This is the fastest way to stop guessing. It keeps the raw bytes exactly as they arrive off the endpoint and writes them out as a .cu8 file, the same format rtl_sdr produces. Nothing is interpreted on the way: no correlator, no protocol, no gate. Send that file to someone who can look at the spectrum in it and the question of whether there is a meter in your air stops being a matter of opinion.
Find the sample rate
Everything downstream assumes it knows how many samples make a chip, and if that number is wrong by one, the whole chain produces nothing at all. Measured on the reference implementation's own capture: chip length 78 yields nine frames that satisfy the checksum, and 77 or 79 yield zero. Not fewer, zero. A rate assumption is a cliff rather than a tuning knob.
Two steps, and the second is what makes it honest. An amplitude keyed Manchester burst puts a strong line in its envelope at the bit rate, so one peak in a band-limited spectrum estimates the chip length. Then it decodes at that estimate and its neighbours and counts what survives a checksum that can be believed. The estimate is a guess; the count is evidence, and nothing is claimed on the strength of the guess.
| Samples per chip | Sample rate | Framed | Survived a checksum |
|---|
Sweep the whole band
One dwell sees 2.4 MHz. The band these meters live in runs 902 to 928 MHz, so a single look covers about a tenth of it, and 912.6 is only where one popular tool points by default rather than where the traffic is confined. A sweep dwells, retunes, dwells, and builds a picture of the whole thing. At each stop it runs the detector that assumes no protocol, no preamble and no chip rate, then judges what it found on the one thing measured here that a frame cannot fake: how long it lasts. Width is shown in the table but is not used to rule anything in or out, because three attempts at measuring occupied bandwidth have each been wrong in one direction or the other, and a discriminator that is wrong both ways is worse than none.
-
| MHz | Found | Median length | Width | Loudest | Right length for |
|---|
Is anything there at all
Everything else on this bench assumes a protocol, a preamble and a field map. This assumes none of them: it reports what the samples themselves say. A screen with no meters on it and a receiver that is not hearing anything look identical from the outside and want opposite responses, so they need telling apart.
rtl_tcp over a websocket UNPROVEN
If rtl_tcp already runs somewhere on your network, this reads its stream. rtl_tcp speaks plain TCP, so a one line relay is needed in between. The README carries the command. This is a companion, never load bearing.
What is running
A browser tab has to keep up with the dongle in real time or it starts throwing samples away, and a decoder that is quietly falling behind looks exactly like a quiet neighbourhood. So the cost of every block is measured and the verdict is stated plainly. When the arithmetic runs long the drawing gets thinned out first, which is said rather than done silently.
Band
See the traffic before blaming the decoder. Consumption meters in North America transmit in the 900 MHz licence free band, and rtlamr centres on 912.6 MHz by default.
Frequency and correction reach the dongle only on the live paths. The fixture ignores them, and a file carries whatever it was captured at. A dongle already running is retuned the moment you change this.
The default is 917.58 MHz rather than the 912.6 that rtlamr uses, because the only CENTRON entry with a fixed frequency in the reference meter table sits there. A 2.36 MHz window centred on 912.6 spans 911.4 to 913.8 and misses it by nearly four megahertz, which is a good way to spend an evening hearing nothing.
Treat it as the best first guess, not a fact about your meter. That entry is a Schlumberger-era CENTRON and yours may be newer; most Itron electric models are listed hopping across wide ranges instead, 910 to 920 being commonest and some spanning 902 to 928. If a stated frequency finds nothing, the sweep on Source covers the whole band and settles it properly.
Spectrum
Waterfall
Level
Every readout on this page is a copy of something. This prints the originals: whether a source object exists, whether it is running, whether it is feeding the same decoder this page reads from, and what each of them has counted. If a card disagrees with this, the card is wrong. Copy it into a message rather than describing what you see.
Preamble
The demodulation bench. Every protocol gets its own correlator running over the same chip stream, so the hit counts here tell you which protocol your neighbourhood actually uses before you commit to one in a config file.
Correlator hits
| Protocol | Chip rate | Frame | Correlations | Refused | Framed | Checksum pass | Checksum | Field layout |
|---|
Detector
The correlator score on pure noise has a mean of zero and a standard deviation of one over the square root of the template length, which is measured in the self test rather than assumed. So a single flat threshold means different things for different preamble lengths, and until v1.8.1 this used one: 0.55, which is three and a half standard deviations above chance for a 21 bit preamble and only three for a 16 bit one. At three standard deviations, with every chip offset tested thirty three thousand times a second, noise frames several times a second. A screen full of meters heard once, with impossible readings and no signal, is what that looks like.
The first button is the behaviour of v1.8.0, which is the version that appeared to work: three standard deviations and no signal gate. It is a setting rather than a version, so nothing has to be rolled back to get it. What it shows is not meters, it is the correlator matching noise, and the readouts on this station and in easy mode will now tell you that while it does it. It is genuinely useful for one thing: proving that the receive chain is delivering samples at all.
The second gate is the one that does the work, because it does not depend on the preamble at all: a transmission carries energy above the noise floor and noise carries none. It holds the short preambles the correlator alone cannot defend. Rejections are counted, because a screen showing nothing has to be able to say whether it heard nothing or threw everything away.
| Protocol | Preamble | Threshold | Above chance | Ceiling | A false frame every | Rejected as noise |
|---|
Dropping frames on an unconfirmed checksum would hide real meters behind a polynomial nobody has verified. Advisory keeps them visible and says so.
Chip trace
Two chips make one bit. A clean capture shows an obvious high and low inside every bit period.
Burst finder
A correlator only finds the pattern it was told to look for. This one finds bursts by their energy instead, and reports the bits at the head of each, so a sync pattern that is wrong or unknown gets recovered rather than reading as an empty band. Patterns are shown in both senses because nothing in an amplitude keyed signal reveals its own polarity.
| Bits | Sense | Pattern at the head of the burst | Seen |
|---|
Field layout table
Bit offsets are data, not code. SCM and SCM+ ship as laid out below. The rest ship with framing and correlators only and their field offsets marked unconfirmed, because a wrong offset that nobody flagged is worse than a gap that is labelled. Confirm one against a real packet in Log, edit here, and apply.
Rounds
Every meter in earshot, once each. Your neighbour's meter decodes exactly as cleanly as yours does, which is the whole reason the band column exists. Claim a meter to see its full number and to carry it into the rest of the instrument.
| Mine | Meter | Protocol | Utility | Type | Last reading | Heard | Pass / adv / fail | Signal | First | Last | Band |
|---|
How the band is decided
Bearing
The second line of evidence for the only question that matters, and it does not need the meter pit lid off. Carry the dongle to two or three marked spots and see whose signal cares that you moved.
Why swing and not strength
Signal strength is not distance. A meter fifty metres away with a clear path can read louder than your own behind a wet wall, and different meter models transmit at different powers, so the loudest number in the room proves nothing. What is informative is how much a meter's level changes between two places you stood. Walk from your own pit to the end of the drive and the geometry to your meter changes enormously in proportion, while the geometry to a meter fifty metres off barely changes at all. So the ranking here is by swing, and the meter that moved with you is the candidate.
This is corroboration and not proof. Walking the dial on Odometer is still the only thing that settles ownership. This is the evidence you can gather while you wait for a dry afternoon.
Marks
Stand somewhere, name it, start the mark, wait for a few transmissions, stop it. Then move somewhere meaningfully different and do it again. Two marks at least, and further apart is worth more than more of them.
| Mark | Ran for | Receptions |
|---|
Who moved with you
| Meter |
|---|
Antenna and placement
Odometer
A meter register is an odometer: it counts up and it never counts down. The wheels below turn only on a reception that passes its checksum and does not contradict the one before it. Everything refused lands in the pile beside them with the reason attached.
A register of finite width wraps back to zero, and a wrap read as a contradiction would stop a real meter working for good. A fall from near the ceiling to near the floor is a wrap and is accepted. Anything else that falls is still refused. Set the hourly rate above and a rise too large for the elapsed time is refused as well, because a register only going up is necessary and not sufficient.
Each # in the mask is one digit. Digits to the right of the point are the brass wheels, the way the sweep digits on a real register are a different colour from the billed digits.
Walk the dial
Go outside, read the meter face, type it in. This tells you which of the numbers in earshot is yours and which mask makes the arithmetic work, which is the one question the log files never answer.
Refused receptions
Differential intervals
Some protocols carry a short history of their own inside every transmission, which is finer than anything a derivative sensor can reconstruct from the cumulative reading. Both the offset and the element width of this array are reconstructions in this build. The sweep on Proof is how you settle them.
Consumption history
Manifold
Prove the path to the broker before the daemon depends on it. This publishes the same topics rtlamr2mqtt does, so whatever Home Assistant makes of these messages is what it will make of the daemon's.
listener 9001 then protocol websockets in mosquitto.conf, and a restart. That is a change on the broker, not something this page can work around. Nothing else here changes.
Broker
Credentials stay in this tab. Nothing here is written to disk, because a password saved into a single file app is a password waiting to be shared by accident.
Traffic
What would be published
Built from the claimed meters and their masks. Read it, then send it once and watch Home Assistant.
Orders
What the bench is for. Everything learned here leaves as configuration the daemon can run unattended, plus the evidence that says why those numbers and not others.
Claimed meters
Everything the daemon needs per meter, and nothing it does not. A meter has to be claimed on Rounds before it appears here, which keeps a neighbour out of your config file by construction.
| Meter | Protocol | Sensor name | Format | Unit | Class | Icon | Band |
|---|
rtlamr2mqtt.yaml
Home Assistant helpers
A cumulative register on its own tells you very little. These turn it into daily and monthly use, an instantaneous flow rate, and the two alerts that catch the leaks people actually get.
Session
The roster, the claims, the masks, the edited layouts and the settings. Not the readings themselves unless you ask, and never a broker password.
Held in this browser
Claimed meters, their settings, the solved layouts and a bounded tail of readings, kept so that closing the tab does not throw away an afternoon. Never a broker password. IndexedDB is deliberately not used, because a page opened off the disk gets it refused or made opaque.
When this page is served
Opened off the disk this file is complete and self contained. One thing switches itself on when the page is served: a worker taking the per sample arithmetic off the main thread, which is all of the cost. Being installable turns out not to be available at any price, and the reason is worth knowing rather than papering over: a service worker has to be fetched from a URL of its own, and a page that is one file has no second URL to give it. So the convention wins and the feature does not ship. The button below writes the service worker out as a file, for anyone who decides that trade is worth making on their own server.
Leaving it running
A browser tab is not a service, and a backgrounded tab gets throttled. For an hour on the bench a screen lock is the honest fix. This is not a substitute for the daemon and does not pretend to be.
Verification plan
A Test Bench shaped plan whose first case is the one people skip: does the number on the screen match the number on the meter.
| # | Case | Expected |
|---|
Proof
Up to here this instrument has been careful to say what it does not know. This station solves for it instead, out of frames you already have. A proof house is where a barrel is tested before it gets a stamp, and nothing here gets a stamp it has not earned.
Frames on the bench
Everything below works from frames belonging to one meter. Three is the minimum and they have to differ from each other, which in practice means readings taken far enough apart that the register moved. More is better, and readings spread across a wider range are worth more than readings taken quickly.
The checksum polynomial
For frames of one length the initial value contributes exactly the same amount to every one of them, so under the right polynomial every undamaged frame leaves the same leftover when the register starts at zero, and under a wrong one the leftovers scatter. Every candidate is therefore scored by how many of your frames agree, which is what lets a share of damaged transmissions sit in the set without sinking the answer. The frames that disagree get named.
Where the fields are
Two things are true of a meter without anyone having to say so: the identifier does not change, and the register only goes up. Both are searchable. The strip below is the raw evidence, one mark per bit, brass where the bit moved across your frames and dark where it never did.
The bits that moved are the low end of a register. Reading only those does not climb, because the low bits of a counter wrap round constantly, so a window has to reach further left to take in the bits above them, and those bits did not move. Where exactly the field begins is therefore not something the air can settle. Read it off the meter face and put it in the box below, and the search collapses to the one window that produces it. That number is the only anchor in this whole instrument that does not come out of this instrument.
How wide is one interval
An interval array is the one field whose correctness can be judged from a single frame, because the values have to look like consumption over a short period. This sweeps the width and the offset and reports how each one looks. It does not pick one.
Referee
The definitive test of a configuration, and arguably what this whole bench is for. Leave the daemon running, subscribe to what it publishes, decode the same air independently here, and reconcile the two reading by reading.
What an agreement is worth
Stated up front rather than buried, because it decides how much weight to put on a clean result: both sides are listening to the same transmissions. Where this instrument's field map for a protocol is still a reconstruction, agreement means the two of you share an assumption, not that either of you is right. A meter walked against its own face is what turns agreement into proof. What this station does settle, and settles conclusively, is whether the daemon is publishing the number you think it is.
Listen to the daemon
This uses the broker connection from Manifold, so connect there first. The filter wants the topics the daemon publishes state on, which for rtlamr2mqtt is the base topic, then a meter number, then state.
Reconciliation
| Finding | Meter | Daemon says | This bench says | What it means |
|---|
Disagreement report
Everything above as a file you can keep, paste into a ticket, or read next to the daemon's own log.
Log
Raw frames, in order, with the bits laid out against the layout that read them. This is where an unconfirmed bit offset gets confirmed: pick a frame from a meter you have already dialled and check the fields land where the reading says they should.
| Time | Protocol | Meter | Reading | Signal | Checksum |
|---|
Selected frame
Pick a row above.