Tracking Neighborhood Power Usage From The Sky
About the project
Ever wonder how the power company reads your meter without walking door-to-door? What if I told you that you can read your power meter, and your neighbors', to see how you compare?
Project info
Difficulty: Moderate
Platforms: Linux
Estimated time: 6 hours
License: GNU General Public License, version 3 or later (GPL3+)
Items used in this project
Hardware components
Software apps and online services
Story
Backstory
A couple years ago I fell hard into the world of RF communications. It's easy to forget the invisible waves all around us that make the world go 'round, from cell networks to local wifi to FM radio to long-range IoT. After getting a amateur radio license (typically known as a HAM license) I purchased a USB SDR (software defined radio) and started to scrub the virtual dial up and down the spectrum to see what I could "hear" in those invisible waves. I heard FM radio, downloaded images from the space station, eavesdropped on chatter from around the world, and set up a node to receive airplane location information as part of the FlightAware network.
This got me to wondering what else I could listen to. After coming across a blog post about how to listen to gas, water, and power meters I set up a powerful linux server to log usage of power meters all around me to see how I compared to my neighbors and how accurate the monthly shaming letter from the power company was. You know the ones. They say, "you've used X% more electric than your neighbors this month. Try to use less." (I've tried and it doesn't work.) You know how many meters my whiz-bang new set up could hear? One. Uno. A single meter -- mine. :(
Not to be discouraged I started planning how to gather more data. On my daily commute to work I noticed antennas on the highest power poles in each community. The antennas connected to a box on the side of the pole and plugged into a standard 120V outlet. Peculiar, I thought. There's no power meter. That must mean they're owned by the power company and I bet that box has a cell modem in it. With my HAM radio knowledge I knew that the higher your receiving antenna is, the more you can hear, and that's why repeaters are on towers on the tops of hills. So that's how they're reading the meters in the area -- an antenna that's high enough to hear them in the area with a cell modem for the backhaul to HQ.
How could I replicate their configuration in a valley without access to a tower? How about a temporary tower? A drone is able to get great pictures of visible light (also RF) from the sky, why couldn't it also get great pictures of invisible light from the sky as well? I couldn't send a normal computer up on a drone but I could send a tiny computer. I could send the smallest computer I own, a GIANT Board! Yeah, I have Pi Zeros but they're bigger, use more power, and don't have the gorgeous lipo connector.
The Build
My first issue was how to swap between USB OTG mode so that I could edit files for testing with SSH but also be able to switch to USB host mode for when the SDR was connected to the GIANT Board. Since /boot is FAT16 and mounts when connected to another computer (or when the SD card is put into a reader) I added a script to /boot/uboot/backdoor.sh that is run every time the board boots. This way I don't lock myself out of the board without a way to get back in. The script checks to see if the gadget service is enabled and if it's not it will set it to enabled then run the logging script (~/log.sh). It will also power off the board once the logging script is done executing.
/boot/uboot/backdoor.sh:
- #!/usr/bin/env bash
- if ! systemctl is-enabled usbgadget-serial-eth-ms
- then
- sudo systemctl enable usbgadget-serial-eth-ms
- ~/log.sh
- echo "Powering off" > ~/log/status.log
- sync
- sudo systemctl poweroff
- fi
This script is called via the debian user's cron:
- # Edit this file to introduce tasks to be run by cron.
- #
- # Each task to run has to be defined through a single line
- # indicating with different fields when the task will be run
- # and what command to run for the task
- #
- # To define the time you can provide concrete values for
- # minute (m), hour (h), day of month (dom), month (mon),
- # and day of week (dow) or use '*' in these fields (for 'any').#
- # Notice that tasks will be started based on the cron's system
- # daemon's notion of time and timezones.
- #
- # Output of the crontab jobs (including errors) is sent through
- # email to the user the crontab file belongs to (unless redirected).
- #
- # For example, you can run a backup of all your user accounts
- # at 5 a.m every week with:
- # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
- #
- # For more information see the manual pages of crontab(5) and cron(8)
- #
- # m h dom mon dow command
- @reboot sleep 60; /boot/uboot/backdoor.sh
For this to work you also need to have the debian user execute sudo without prompting for a password:
- $ sudo sudoedit -f /etc/sudoers.d/admin
- Change contents to:
- Defaults env_keep += "NODE_PATH"
- %admin ALL=(ALL:ALL) NOPASSWD: ALL
Next install the software needed to decode the radio signal and decode those results to match the format the meter is sending. There are two parts to this: rtl_tcp (part of the rtl_sdr package) and rtlamr. The former decodes the radio signal and the latter turns that output into meter values.
On Debian there's a prerequisite of libusb-1.0-0-dev for rtl_tcp and golang for rtlamr.
Full build instructions for rtl_sdr can be found at https://osmocom.org/projects/rtl-sdr/wiki/Rtl-sdr
Install instructions for rtlamr can be found at https://github.com/bemasher/rtlamr
If you aren't a linux person, installation of rtlamr is as easy as:
- $ mkdir ~/go
- $ export GOPATH=~/go/bin
- $ go get github.com/bemasher/rtlamr
Once all this is done, we need the ~/log.sh script and a logging directory to make it easier to troubleshoot since this all runs headless.
- $ cd ~
- $ mkdir log
- $ cat << EOF
- #!/usr/bin/env bash export LD_LIBRARY_PATH=/usr/local/lib/ sudo rtl_tcp >>log/rtl_tcp.out 2>>log/rtl_tcp.err & sleep 10 while [ 1 ] do pgrep rtl_tcp || sudo rtl_tcp >>log/rtl_tcp.out 2>>log/rtl_tcp.err & sudo ~/go/bin/rtlamr -format=csv -msgtype=all -symbollength=8 >>meters.csv 2>>log/rtlamr.err sleep 10 done
- EOF
- chmod +x log.sh
Let me break down this script a little.
I had issues with library paths so the first step is to set the LD_LIBRARY_PATH variable so the librtlsdr.so.0 library can be found by rtl_tcp.
Next I start rtl_tcp (the "server" that takes the RF signal over USB and sends it to a client) using sudo since the USB device file it's readable by the debian user. This is started in the background using the "&" at the end of the line. Following this is a pause to make sure it starts correctly.
Next is a while loop that runs forever. Sometimes rtl_tcp or rtlamr crash due to a lack of resources. This loop restarts them if they die. In fact, the "pgrep rtl_tcp" is what restarts rtl_tcp if it's not running.
After I know rtl_tcp is running, I start rtlamr (the "client" to rtl_tcp) to decode the meter values, again using sudo however it's probably not needed in this case.
The specific command is "rtlamr -format=csv -msgtype=all -symbollength=8"
The arguments do the following:
-format=csv -- Outputs the data in CSV format.
-msgtype=all --Listens for all formats. Different types of meters use different formats and I want to log them all.
-symbollength=8 -- Sets the data rate to the lowest possible. The GIANT Board is small and it also has limited resources so I need to have it do less work. Setting this higher causes the client to receive more data than it can process and therefore crash. This took about a day to figure out!
You'll notice that both rtl_tcp and rtlamr log both standard out and standard error as well so I can look back through the logs in case there are issues.
Execution
Once you're ready to collect data, disable the gadget service with "sudo systemctl disable usbgadget-serial-eth-ms", then power off "sudo systemctl poweroff", and once the lights go out plug in a USB OTG cable with a power input and the SDR to the GIANT Board and you're good to go!
Remember that each time the GIANT Board boots in USB host mode it will enable the usbgadget service so you can use SSH/console/mass storage again. To "pull the pin" on data collection mode run "sudo systemctl disable usbgadget-serial-eth-ms" from an SSH or console session.
Results
After running in data collection mode, with or without it gaff taped to your drone, you can review the logged data by looking at meters.csv:
2020-07-06T12:56:59.379162225Z,0,0,4,0x0,0x2,53089,0xbad1
2020-07-06T12:57:06.749211493Z,0,0,4,0x0,0x2,53089,0xbad1
2020-07-06T12:57:14.23219881Z,0,0,4,0x0,0x2,53089,0xbad1
2020-07-06T12:57:34.051995201Z,0,0,4,0x0,0x2,20446,0xff46
2020-07-06T12:57:45.84081403Z,0,0,4,0x0,0x2,20446,0xff46
2020-07-06T12:57:57.570473933Z,0,0,4,0x0,0x2,20446,0xff46
2020-07-06T12:58:16.025402421Z,0,0,4,0x0,0x2,20446,0xff46
2020-07-06T13:03:03.183740962Z,0,0,4,0x0,0x3,31166,0x26b7
2020-07-06T13:03:03.921314913Z,0,0,4,0x0,0x3,43079,0x5c39
2020-07-06T13:10:46.03005609Z,0,0,4,0x0,0x2,44198,0xa15a
2020-07-06T13:29:53.37876801Z,0,0,8,0x0,0x1,44151,0x84e4
2020-07-06T13:31:47.054006158Z,0,0,4,0x0,0x2,34213,0xf49d
2020-07-06T13:32:28.614693671Z,0,0,4,0x0,0x1,7143,0xa591
2020-07-06T13:32:45.843889964Z,0,0,4,0x0,0x2,74282,0x1def
2020-07-06T13:33:29.27331816Z,0,0,163,6,0,14000,0,0
2020-07-06T13:34:31.265539039Z,0,0,4,0x0,0x1,79599,0x2593
2020-07-06T13:34:44.239376209Z,0,0,4,0x0,0x1,79599,0x2593
2020-07-06T13:35:38.337546357Z,0,0,5,0x0,0x1,1736805,0x59c3
2020-07-06T13:35:39.184454162Z,0,0,5,0x0,0x1,1736805,0x59c3
2020-07-06T13:36:13.539333772Z,0,0,4,0x0,0x0,710,0xc306
2020-07-06T13:36:16.936279235Z,0,0,4,0x0,0x0,710,0xc306
2020-07-06T13:36:21.587471138Z,0,0,4,0x0,0x0,710,0xc306
2020-07-06T13:36:32.725334748Z,0,0,4,0x0,0x2,29572,0x5714
2020-07-06T13:37:22.581992505Z,0,0,4,0x0,0x1,12474,0xa151
2020-07-06T13:37:31.760309871Z,0,0,4,0x0,0x1,12474,0xa151
2020-07-06T13:37:40.370119529Z,0,0,4,0x0,0x1,95623,0xfbe
2020-07-06T13:37:40.903781968Z,0,0,4,0x0,0x3,14392,0x16a2
2020-07-06T13:37:41.443526456Z,0,0,4,0x0,0x1,26953,0x7907
2020-07-06T13:37:42.078560212Z,0,0,4,0x0,0x1,95623,0xfbe
2020-07-06T13:37:43.287948407Z,0,0,4,0x0,0x1,71937,0x3601
2020-07-06T13:37:43.518602163Z,0,0,4,0x0,0x1,95623,0xfbe
2020-07-06T13:37:44.856290163Z,0,0,4,0x0,0x1,26953,0x7907
2020-07-06T13:37:47.2169927Z,0,0,4,0x0,0x1,51303,0xf4d1
2020-07-06T13:38:02.696892798Z,0,0,4,0x0,0x3,19309,0xc7be
2020-07-06T13:38:08.0449167Z,0,0,4,0x0,0x2,2488,0x3911
2020-07-06T13:38:09.529019432Z,0,0,4,0x0,0x0,74264,0x8233
2020-07-06T13:38:10.289069774Z,0,0,4,0x0,0x2,32193,0x73f4
2020-07-06T13:38:12.733227822Z,0,0,4,0x0,0x3,50666,0xb395
2020-07-06T13:38:15.529263237Z,0,0,4,0x0,0x3,19309,0xc7be
2020-07-06T13:38:16.780056993Z,0,0,4,0x0,0x2,32193,0x73f4
2020-07-06T13:38:17.052826457Z,0,0,4,0x0,0x2,9946,0x8dfb
2020-07-06T13:38:18.018740408Z,0,0,4,0x0,0x1,64248,0xe906
2020-07-06T13:38:18.498968213Z,0,0,4,0x0,0x2,32193,0x73f4
2020-07-06T13:38:19.804041383Z,0,0,4,0x0,0x1,53786,0xe68
2020-07-06T13:38:19.91806314Z,0,0,4,0x0,0x2,32193,0x73f4
The format is:
timestamp, ?, ? meter ID (redacted in the above example), TamperPhysical, TamperEnclosure, Consumption (i.e. current reading), ChecksumValue
Next Steps
I didn't have a Lipo battery to use so I'm waiting for one to be delivered before taping it all to the drone and sending it sky-high!
I'd also like to design a case to attach it to the drone more easily.
Leave your feedback...