Neptune RF water meter frequency hopping pattern

A couple of years ago, my water utility installed new remote-read water meters, Neptune e-Coder R900i, in every home in their service area.  As any casual reader of this blog knows, I’m a big fan of measuring and data when it comes to household resource consumption. I’ve got electricity pretty well covered, but water and gas are still lacking. I could install secondary meters with pulse counters, by that seems silly – I already have remote-read meters installed, it’s just that the data they send isn’t accessible to me. Let’s see if we can start to remedy that!

Reading the product literature for this meter, we learn a few things right off the bat:

Transmitter Specifications:

  • Transmit period: Every 14 seconds
  • Transmitter channels: 50
  • Channel frequency: 910 to 920 MHz spread spectrum

Great!  We know nothing about the signal, but we know where to start looking to find it.  Is there any cheap hardware which could listen in on these frequencies?  Why yes, there is!

nooelec dvb tuner [amzn]

The rtl-sdr project has repurposed Digital Video Broadcasting (DVB) receivers as general-purpose software-defined radios.  We can control frequency and gain, and listen in on signals within the tuned bandwidth.

For now, we’re just trying to find the signals; we don’t yet care what they look like, we just want to know where they are so we can start listening.  To this end we can use the rtl_power utility from the rtl-sdr suite, which listens in on a given bandwidth, slices it into smaller buckets, and gives us time-stamped signal strength for each frequency bucket.  I used keenerd’s tree on github, as it has many bleeding-edge fixes and features (he was also supremely helpful on the IRC channel, thanks!)

However, the device can’t listen in on the entire 910-920MHZ spectrum at once; its maximum bandwidth is about 2MHz.  So to listen across all 10MHZ of the published range would require sweeping, and we might miss the short signals the meter emits if we’re sweeping the wrong range at the time it sends the signal.  But that’s ok; we know that the periodicity of the signal is once every 14 seconds, and that it hops across 50 frequencies.  We don’t know how random the sequence is across the 50 frequencies, but let’s assume it’s simple, that it repeats every 14×50 seconds, or every 700 seconds.  So let’s try listening in to dedicated 1MHZ bands at a time, for 700s each, and line up the results; essentially “binning” the signals we get into 50 14-second-aligned slots.


# 50 channels, each for 14s; 50*14 is 700, gather 1 cycle at each freq

for I in `seq 910 1 919`; do
    let J=$I+1
    rtl_power -g 0.9 -p -39.906 -f ${I}M:${J}M:5000 -c 20% -i 1s -e 700s -E rtl_power-${I}M-${J}M-5000-i1.csv

This tells rtl_power to use gain 0.9 (pretty low, the antenna was very near the meter, and I don’t want my neighbors’ meters interfering), calibrate the frequency, crop the gathered data by 20% (data at the edges of the bandwidth can be dodgy), record power every 1s, and stop after 700s.  We record 5000 bins in the range, fairly fine grained.  The -E switch tells rtl_power to record timestamps as seconds since epoch.  We do this for each of the 10 1GHz ranges between 910MHz and 920MHz in succession.

This gives us 10 CSV files, with one row per second, and 5000 time-stamped frequency columns.  Now what?  We’d like to know what it’s doing across the entire documented frequency range.

Assuming we were right about the sequence repeating every 700s, we can now just “horizontally concatenate” the frequency columns from each run.  It’s easy enough to use awk to strip out the timetamp data and retain only the frequency columns.  But how to concatenate them?  paste(1)!  Did you know about paste?  I did not.  Paste is awesome for this.

After simple awk scripts to gather only the frequency columns for each csv file, we combine them like so, using a comma for the delimiter as we combine the files:

paste -d , rtl_power-910M-911M-5000-i1.csv 911M-912M.csv 912M-913M.csv 913M-914M.csv 914M-915M.csv 915M-916M.csv 916M-917M.csv 917M-918M.csv 918M-919M.csv 919M-920M.csv > all.csv

Frikkin’ trivial!  And now we’d like to visualize it.  Turns out keenerd has a great tool for this too;  Again, super easy: all.csv > all.png

and voila!

That’s the heatmap of power across frequencies and time; the yellow lines are the higher energy emissions during the signals, and the white text is my own annotation for time and frequency sequences.  Looks amazingly orderly, no?

I actually ran the capture for 3 cycles after this, and confirmed that the cycle repeats the above pattern ad infinitum.  We know that the time scale is once every 14s, but it’s hard to tell from the image above what the frequencies might be; we could count pixels, or look into the CSV files for frequency bins, but this DVB tuner isn’t super accurate… so, looking at my meter, it’s FCC ID is P2SNTGECR900DL.  And looking at that the test report for that FCC filing, we find this:

Which matches just extremely well; the lower frequency is stated in the test report as 911.06MHZ, and the upper as 919.08 MHz.  It also states that the channel separation was measured at 131.58 kHz.  So, counting the peaks above, and counting up (17 peaks) or down (33 peaks) from the extremes, correlating to the sequence we measured, and sorting – here is the table of frequencies and sequences for the Neptune e-Coder R900i RF water meter.  Now that we know where to look, we can start investigating the signals.  (Note: Table below edited 12/21, spreadsheet thinko corrected)

Seq     Frequency
1       911.06
2       915.92
3       912.38
4       917.24
5       915.26
6       918.55
7       911.72
8       916.58
9       913.03
10      917.90
11      911.19
12      916.05
13      912.51
14      917.37
15      915.40
16      918.69
17      911.85
18      916.71
19      913.17
20      918.03
21      911.32
22      916.19
23      912.64
24      917.50
25      915.53
26      918.82
27      911.98
28      916.84
29      914.87
30      918.16
31      911.45
32      916.32
33      912.77
34      917.63
35      915.66
36      918.95
37      912.11
38      916.97
39      915.00
40      918.29
41      911.59
42      916.45
43      912.90
44      917.76
45      915.79
46      919.08
47      912.24
48      917.11
49      915.13
50      918.42

20 thoughts on “Neptune RF water meter frequency hopping pattern

    • Take a look at for a framework to capture and decode the packets.
      Then I just run a bash script every 15 minutes, which does:

      CSV=`/usr/local/bin/rtlamr -msgtype=r900 -centerfreq=912000000 -filterid=XXXXXXX -quiet -format=csv -single=true -symbollength=8`
      NOUSE=$(echo $CSV | awk -F, ‘{print $6}’)
      BACKFLOW=$(echo $CSV | awk -F, ‘{print $7}’)
      NEW=$(echo $CSV | awk -F, ‘{print $8}’)
      LEAK=$(echo $CSV | awk -F, ‘{print $10}’)
      LEAKNOW=$(echo $CSV | awk -F, ‘{print $11}’)

      to parse out the comma-separated pieces.

  1. Hi, Eric; thanks for the great information, but I have a question. Another article I found states that the RF system only updates its output value once an hour, which is plenty of detail for the water company, but would seriously compromise its use for detailed water use statistics. In your experience, how often does the transmitted value change?

      • Is this 15 minute update ‘limit’ because the rtl_tcp is fixed on one channel and it takes 700 seconds for the r900 to return to that channel? If so, couldn’t I write my code to set the rtl_tcp to listen the next frequency based on your table and and see the current consumption every 14 seconds?

        BTW: My r900 followed that table precisely. Any thoughts why the company chose to put the large gap between channels 17 and 18 ?

        Thanks for your work on this.

        • As far as I know, 15 minutes is simply how long it takes for the meter to broadcast a new reading, and the hopping speed doesn’t matter. In the end, I set mine up to just listen on one frequency and didn’t worry at all about the hopping pattern. Tuning it “close enough” picks up neighboring frequencies, and I don’t see changed values.
          As for the gap, I really have no idea.

    • I think I did everything on the commandline; as far as I remember, I simply stripped out the first column; I don’t remember the details exactly, but there are lots of hints about how to do this scattered around the internet. :)

      • you just stripped out the first two columns and keep the rest. Then you concatenate all the 10 files and run heat map on the resulting file. right?

        • Yes, as I recall, that is correct – I used paste(1) to do the concatenation. For awk I may have done something as dumb as ‘{print $2,$3,$4…}’ for however many fields there are.

  2. Could someone give me some informed input. My mother owns a rental property that mysteriously now is using up to 84,000 gallons a month. We have no leak or evidence of a leak that we can find. The city water folks have told us we need to find the “leak” or they will me turn the water off….. I found some articles that talked about a CNN investigation that showed these Neptune water meters (Neptune E-Coder 900i) can give bad readings if they aren’t installed properly like if the attachment to the meter isn’t the right diameter etc…

    The city water manager has been very rude …. we have paid approximately $1500 in water bills at this point (the bill used to range from $20 to $50 /mo but now is around $300/mo…. Anyone know any ammunition that I can use to prove or demonstrate that this “leak” is no leak but a meter malfunction???? Thanks in advance !!!

    • I suppose I’d call a plumber if you can’t find any leak. At $1500 in water bills, a plumber visit starts to look cheap!
      84,000 gallons per month is about 2 gallons per minute. That’s the flow rate of an efficient water faucet on full blast. That water would have to be going somewhere, or you’d see it – unless it’s a leaking toilet, which could easily leak 2gpm. Drop some food coloring in the tank(s) and see if the color migrates to the bowl(s).
      I don’t think anyone can tell you for sure whether or not the Neptune meter is reporting incorrectly.
      Sorry if you’ve been through all this already, but might help.

    • Is the meter in the street of in the house ? You can shut the main valve off on house side of meter to see if the meter still is registering usage to look for under ground leaks if meter is in thereet if inside you can shut off house side main house valve.
      Im a retired plumber and contractor for 55 yrs and at 2 GPM you should have no issues locating a broken pipe or leak

  3. Hi.. first, thanks for all your work. I am trying to implement the solution to read r900 meters… I am running on a windows machine. I have rtl_tcp running in one shell and rtlamr running in another shell. If I run rtlamr with no parms… I see packets from what appear to be power meters near me… But if i run rtlamr with -msgtype=r900, I get nothing. I have also tried msgtype=r900bcd, still no joy. I am using an r900 register on a test bench and I can see the radio bursts following your outlined pattern across the 50 channels. But I can’t seem to get rtlamr to decode anything…

    Did you have any problems setting this up or did it go smoothly?


    • I’m also running rtl_tcp with no parameters, and running rtlamr with:
      rtlamr -msgtype=r900 -centerfreq=912000000 -filterid=(MYSERIALNR) -quiet -format=csv -single=true -symbollength=8
      (the -single=true is just because of how I’ve scripted the collection, I fire off a new one every 15 minutes)
      I didn’t really have to do anything special once rtlamr had gained the r900 decoding capability. I’ve never tried it on Windows.

    • I’m sure it’s so that there is a signal available whenever the truck happens to drive by.

      R900 device transmits for less than one minute total per day and for 7 milliseconds at a time. The exposure to radio frequency energy at a distance of 1 foot from the meter is never more than 0.08 mW/cm2 for the Fixed Network Messages. This is approximately 8 times lower than the exposure limit set by the FCC. Standard Mobile Messages are an order of magnitude lower.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.