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.

#!/bin/bash

# 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
done

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; heatmap.py.  Again, super easy:

heatmap.py 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

11 thoughts on “Neptune RF water meter frequency hopping pattern

    • Take a look at http://bemasher.github.io/rtlamr/ 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?

    • 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.

Leave a Reply

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