GOES RX
PROLOGUE
My grandfather in law, Wendell Anderson, made the first ground receiver station for APT satellites long before the USG got theirs working; and he did it out of his kids playset. Ever since then, I’d been fighting the ability to replicate so may other folk’s work in this space because I have a lot of trees. The next best thing was to be able to receive the GOES satellites, which I was able to do. Here’s how I did it.
Let’s get started!
Material Needed/Suggested:
This Kit: https://www.amazon.com/Nooelec-GOES-Weather-Satellite-Bundle/dp/B08HGQXC7C/
Atleast one small waterproofish box like this: https://www.amazon.com/gp/product/B075DKJ3LX
Raspberry Pi 4 (Bonus, I use a PoE shield)
Mount for Mast like this: https://www.amazon.com/gp/product/B00068YUN4
Waterproof Joints like these: https://www.amazon.com/gp/product/B01MFF7FZU
Weather sealing tape like this: https://www.amazon.com/3M-88-Super-3-4x44FT-Vinyl-Electrical/dp/B0002FTGEE
A precision level like this: https://www.amazon.com/gp/product/B07NQ95Q9L
A compass like this: https://www.amazon.com/gp/product/B00QR3LOO0
Locktite
A NAS or place to offload all the images
Socket wrenches of various sorts
Step 1: Setup the Raspi You’ll need to setup a headless Raspi and do the normal setup steps of
#update the sucker
sudo apt-get update
sudo apt-get upgrade
sudo apt dist-upgrade
sudo reboot
#install deps
sudo apt install git build-essential cmake libusb-1.0 libopencv-dev libproj-dev
git clone https://github.com/steve-m/librtlsdr.git
cd librtlsdr
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX :PATH= /usr -DINSTALL_UDEV_RULES = ON ..
sudo make -j2 install
sudo cp ../rtl-sdr.rules /etc/udev/rules.d/
sudo ldconfig
echo 'blacklist dvb_usb_rtl28xxu' | sudo tee --append /etc/modprobe.d/blacklist-dvb_usb_rtl28xxu.conf
sudo reboot
#install goestools
git clone https://github.com/pietern/goestools.git
cd goestools
git submodule init
git submodule update --recursive
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX :PATH= /usr ..
sudo make -j2 install
Youll then want to create a goesrecv.conf file. Here’s mine:
[ demodulator]
mode = "hrit"
source = "rtlsdr"
[ rtlsdr]
frequency = 1694100000
sample_rate = 2000000
gain = 30
bias_tee = false
[ costas]
max_deviation = 200e3
[ decoder.packet_publisher]
bind = "tcp://0.0.0.0:5004"
send_buffer = 1048576
[ monitor]
statsd_address = "udp4://localhost:8125"
Step 2: Wire up the antenna Follow the instructions from Nooelec on how to connect the antenna to the SDR. I’d recommend first having all the components laid out and then running the GOES software first to ensure everything works on the ground before putting things into boxes, dealing with tape, and other misc hazards. Connecting everying in a simple way, you should go from the antenna, to the LNA to the long cable, to the SDR which is plugged into the Pi. The LNA’s power supple should have a GREEN LED turned on. If it doesn’t, the Bias-T power injection from your SDR may be bad; this happened to me. If you need to test that, you can use a voltmeter on the SMA connector, setting the voltmeter to DC, placing the black probe on the outside of the SMA connector, and the red probe on the middle pin. You should see 5 Volts. If not, then your SDR’s Bias-T power injector is bad, and send it in for an exchange.
Once you’ve confirmed the basic operation of the system, go to https://www.dishpointer.com/ and plug in your info. This site will tell you where to point your antenna. Take careful note!
Once you know where the dish needs to point, you can freehand ‘test point’ it if you’d like, or use a tri-pod, or whatever. However, the command you’ll want to use as you align the antenna is:
goesrecv -v -i 1 -c ~/goesrecv.conf
Pay attention to the ‘vit’ value. If it is over 2000, then there’s no signal. Mine comfortably hovers around 200. With some more work I should be able to get it to 80 or so. You’ll be able to decode data at maybe less than 400. If you can verify that before mounting it all, then wunderbar!
Step 3: Prep for install I placed the LNA inside the weather proof box, had the feed cables go into it via the waterproof joints, and used the Scotch tape on the antenna input ports. Additinally, for my specific install, my RasPi is in an outdoor waterproof box like this one.
Step 4: Install! This part sucked. You gotta get the J-Pole installed, use a lot of locktite, use the compass and elevation meter to point the dish, and then use the debug from the command line to make sure you’re pointed in the most ideal configuration. You’re on your own for this one; it’s going to be a unique experience.
Step 5: Software Glue I noticed quickly that there’s almost 4GB of data a day, and that was going to hurt the storage on my Pi. This is why I offload all the data to a NAS. So what’s next, pictures, stats and more!
/etc/systemd/system/goesdecode.service
[ Unit]
Description = goesproc decoding for the GOES-R satellites
Documentation = https://pietern.github.io/goestools/
Wants = network.target
After = network.target goesrx.service
[ Service]
WorkingDirectory = /home/pi/incoming
ExecStart = /usr/bin/goesproc -c /home/pi/goesproc-goesr.conf -m packet --subscribe tcp://127.0.0.1:5004
StandardOutput = null
Type = simple
Restart = on-failure
RestartSec = 30
[ Install]
WantedBy = default.target
/etc/systemd/system/goesrx.service
[ Unit]
Description = goesrecv reception chain for the GOES-R satellites
Documentation = https://pietern.github.io/goestools/
Wants = network.target
After = network.target
[ Service]
# EnvironmentFile=/etc/default/goestools
ExecStart = /usr/bin/goesrecv -i 10 -c /home/pi/goesrecv.conf
StandardOutput = null
Type = simple
Restart = on-failure
RestartSec = 30
Nice = -5
[ Install]
WantedBy = default.target
/etc/systemd/system/goesstats.service
[ Unit]
Description = goes statistics
Wants = network.target
After = network.target goesrx.service
[ Service]
WorkingDirectory = /home/pi/
ExecStart = /home/pi/server.py
StandardOutput = null
Type = simple
Restart = on-failure
RestartSec = 30
[ Install]
WantedBy = default.target
goesproc-goesr.conf
# Store all original GOES-16 products.
[[ handler]]
type = "image"
origin = "goes16"
directory = "/home/pi/incoming/goes/goes16/{region:short|lower}/{channel:short|lower}/{time:%Y-%m-%d}"
filename = "GOES16_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store all original GOES-17 products.
[[ handler]]
type = "image"
origin = "goes17"
directory = "/home/pi/incoming/goes/goes17/{region:short|lower}/{channel:short|lower}/{time:%Y-%m-%d}"
filename = "GOES17_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# GOES-16 ABI false color.
[[ handler]]
type = "image"
origin = "goes16"
regions = [ "fd" , "m1" , "m2" ]
channels = [ "ch02" , "ch13" ]
directory = "/home/pi/incoming/goes/goes16/{region:short|lower}/fc/{time:%Y-%m-%d}"
filename = "GOES16_{region:short}_FC_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[ handler.remap.ch02]
path = "/usr/share/goestools/wxstar/wxstar_goes16_ch02_curve.png"
[ handler.lut]
path = "/usr/share/goestools/wxstar/wxstar_goes16_lut.png"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# GOES-16 ABI RGB-enhanced
[[ handler]]
type = "image"
origin = "goes16"
regions = [ "fd" , "m1" , "m2" ]
channels = [ "ch07" , "ch08" , "ch09" , "ch13" , "ch14" , "ch15" ]
directory = "/home/pi/incoming/goes/goes16/{region:short|lower}/{channel:short|lower}_enhanced/{time:%Y-%m-%d}"
filename = "GOES16_{region:short}_{channel:short}_enhanced_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
## The following gradients are rough approximations of the
## McIDAS RGB enhancements used by NOAA/NESDIS/STAR on their site..
##
## For more info:
##
## https://www.star.nesdis.noaa.gov/GOES/GOES16_FullDisk.php
## http://cimss.ssec.wisc.edu/goes/visit/water_vapor_enhancement.html
## http://cimss.ssec.wisc.edu/goes/visit/enhanced_v_enhancements.html
## Shortwave IR (Channel 7)
[ handler.gradient.ch07]
points = [
{ units = 400, color = "#000000" } ,
{ units = 250, color = "#b9b9b9" } ,
{ units = 249.999, color = "#00ffff" } ,
{ units = 240, color = "#000080" } ,
{ units = 230, color = "#00ff00" } ,
{ units = 220, color = "#ffff00" } ,
{ units = 210, color = "#ff0000" } ,
{ units = 200, color = "#000000" } ,
{ units = 190, color = "#ffffff" }
]
## Water Vapor (Channels 8 and 9)
[ handler.gradient.ch08]
points = [
{ units = 276, color = "#000000" } ,
{ units = 275.9, color = "#ff0000" } ,
{ units = 258, color = "#ffff00" } ,
{ units = 250, color = "#000070" } ,
{ units = 233, color = "#ffffff" } ,
{ units = 195, color = "#408020" } ,
{ units = 178, color = "#00ffff" }
]
[ handler.gradient.ch09]
points = [
{ units = 276, color = "#000000" } ,
{ units = 275.9, color = "#ff0000" } ,
{ units = 258, color = "#ffff00" } ,
{ units = 250, color = "#000070" } ,
{ units = 233, color = "#ffffff" } ,
{ units = 195, color = "#408020" } ,
{ units = 178, color = "#00ffff" }
]
## Longwave IR (Channels 13, 14, and 15)
[ handler.gradient.ch13]
points = [
{ units = 333, color = "#000000" } ,
{ units = 238, color = "#b9b9b9" } ,
{ units = 237.999, color = "#00ffff" } ,
{ units = 228, color = "#000080" } ,
{ units = 218, color = "#00ff00" } ,
{ units = 208, color = "#ffff00" } ,
{ units = 198, color = "#ff0000" } ,
{ units = 188, color = "#000000" } ,
{ units = 178, color = "#ffffff" }
]
[ handler.gradient.ch14]
points = [
{ units = 333, color = "#000000" } ,
{ units = 238, color = "#b9b9b9" } ,
{ units = 237.999, color = "#00ffff" } ,
{ units = 228, color = "#000080" } ,
{ units = 218, color = "#00ff00" } ,
{ units = 208, color = "#ffff00" } ,
{ units = 198, color = "#ff0000" } ,
{ units = 188, color = "#000000" } ,
{ units = 178, color = "#ffffff" }
]
[ handler.gradient.ch15]
points = [
{ units = 333, color = "#000000" } ,
{ units = 238, color = "#b9b9b9" } ,
{ units = 237.999, color = "#00ffff" } ,
{ units = 228, color = "#000080" } ,
{ units = 218, color = "#00ff00" } ,
{ units = 208, color = "#ffff00" } ,
{ units = 198, color = "#ff0000" } ,
{ units = 188, color = "#000000" } ,
{ units = 178, color = "#ffffff" }
]
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# GOES-17 ABI false color.
[[ handler]]
type = "image"
origin = "goes17"
regions = [ "fd" , "m1" , "m2" ]
channels = [ "ch02" , "ch13" ]
directory = "/home/pi/incoming/goes/goes17/{region:short|lower}/fc/{time:%Y-%m-%d}"
filename = "GOES17_{region:short}_FC_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
# This reuses the GOES-16 contrast curve assuming it is identical
[ handler.remap.ch02]
path = "/usr/share/goestools/wxstar/wxstar_goes16_ch02_curve.png"
# This reuses the GOES-16 LUT assuming it is identical
[ handler.lut]
path = "/usr/share/goestools/wxstar/wxstar_goes16_lut.png"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Images relayed from Himawari-8.
[[ handler]]
type = "image"
origin = "himawari8"
directory = "/home/pi/incoming/goes/himawari8/{region:short|lower}/{time:%Y-%m-%d}"
filename = "Himawari8_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# NWS text (weather reports).
[[ handler]]
type = "text"
origin = "nws"
directory = "/home/pi/incoming/goes/nws/{time:%Y-%m-%d}"
filename = "{time:%Y%m%dT%H%M%SZ}_{awips:nnn}{awips:xxx}"
json = false
# NWS images.
[[ handler]]
type = "image"
origin = "nws"
directory = "/home/pi/incoming/goes/nws/{time:%Y-%m-%d}"
filename = "{time:%Y%m%dT%H%M%SZ}_{filename}"
format = "png"
json = false
# Miscellaneous text.
[[ handler]]
type = "text"
origin = "other"
directory = "/home/pi/incoming/goes/text/{time:%Y-%m-%d}"
filename = "{time:%Y%m%dT%H%M%SZ}_{filename}"
json = false
# Store relayed GOES-15 full disks
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "fd" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -2374 , 2371, -1357 , 1347 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 northern hemisphere region
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "nh" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -1864 , 1447, -1357 , -3 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 southern hemisphere region
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "sh" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -1864 , 896, -19 , 1043 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 US region
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "us" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -1312 , 1542, -1327 , -345 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 special regions
# No crop specified because it is expected to move around
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "si00" , "si01" , "si02" , "si03" , "si04" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# The following handler takes the same crop from the FD, NH, and US
# products to get more frequent imagery of a smaller area on the
# northern hemisphere. The crop region is a combination of the NH and
# US crop regions.
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "fd" , "nh" , "us" ]
crop = [ -1312 , 1447, -1327 , -345 ]
directory = "/home/pi/incoming/goes/goes15/combine-north/{time:%Y-%m-%d}"
filename = "GOES15_{channel:short}_{time:%Y%m%dT%H%M%SZ}_{region:short}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# The following handler takes the same crop from the FD and SH
# products to get more frequent imagery of a smaller area on the
# southern hemisphere.
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "fd" , "sh" ]
crop = [ -1864 , 896, -19 , 1043 ]
directory = "/home/pi/incoming/goes/goes15/combine-south/{time:%Y-%m-%d}"
filename = "GOES15_{channel:short}_{time:%Y%m%dT%H%M%SZ}_{region:short}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
#
# Store all original GOES-16 products.
[[ handler]]
type = "image"
origin = "goes16"
directory = "/home/pi/incoming/goes/goes16/{region:short|lower}/{channel:short|lower}/{time:%Y-%m-%d}"
filename = "GOES16_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store all original GOES-17 products.
[[ handler]]
type = "image"
origin = "goes17"
directory = "/home/pi/incoming/goes/goes17/{region:short|lower}/{channel:short|lower}/{time:%Y-%m-%d}"
filename = "GOES17_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# GOES-16 ABI false color.
[[ handler]]
type = "image"
origin = "goes16"
regions = [ "fd" , "m1" , "m2" ]
channels = [ "ch02" , "ch13" ]
directory = "/home/pi/incoming/goes/goes16/{region:short|lower}/fc/{time:%Y-%m-%d}"
filename = "GOES16_{region:short}_FC_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[ handler.remap.ch02]
path = "/usr/share/goestools/wxstar/wxstar_goes16_ch02_curve.png"
[ handler.lut]
path = "/usr/share/goestools/wxstar/wxstar_goes16_lut.png"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# GOES-16 ABI RGB-enhanced
[[ handler]]
type = "image"
origin = "goes16"
regions = [ "fd" , "m1" , "m2" ]
channels = [ "ch07" , "ch08" , "ch09" , "ch13" , "ch14" , "ch15" ]
directory = "/home/pi/incoming/goes/goes16/{region:short|lower}/{channel:short|lower}_enhanced/{time:%Y-%m-%d}"
filename = "GOES16_{region:short}_{channel:short}_enhanced_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
## The following gradients are rough approximations of the
## McIDAS RGB enhancements used by NOAA/NESDIS/STAR on their site..
##
## For more info:
##
## https://www.star.nesdis.noaa.gov/GOES/GOES16_FullDisk.php
## http://cimss.ssec.wisc.edu/goes/visit/water_vapor_enhancement.html
## http://cimss.ssec.wisc.edu/goes/visit/enhanced_v_enhancements.html
## Shortwave IR (Channel 7)
[ handler.gradient.ch07]
points = [
{ units = 400, color = "#000000" } ,
{ units = 250, color = "#b9b9b9" } ,
{ units = 249.999, color = "#00ffff" } ,
{ units = 240, color = "#000080" } ,
{ units = 230, color = "#00ff00" } ,
{ units = 220, color = "#ffff00" } ,
{ units = 210, color = "#ff0000" } ,
{ units = 200, color = "#000000" } ,
{ units = 190, color = "#ffffff" }
]
## Water Vapor (Channels 8 and 9)
[ handler.gradient.ch08]
points = [
{ units = 276, color = "#000000" } ,
{ units = 275.9, color = "#ff0000" } ,
{ units = 258, color = "#ffff00" } ,
{ units = 250, color = "#000070" } ,
{ units = 233, color = "#ffffff" } ,
{ units = 195, color = "#408020" } ,
{ units = 178, color = "#00ffff" }
]
[ handler.gradient.ch09]
points = [
{ units = 276, color = "#000000" } ,
{ units = 275.9, color = "#ff0000" } ,
{ units = 258, color = "#ffff00" } ,
{ units = 250, color = "#000070" } ,
{ units = 233, color = "#ffffff" } ,
{ units = 195, color = "#408020" } ,
{ units = 178, color = "#00ffff" }
]
## Longwave IR (Channels 13, 14, and 15)
[ handler.gradient.ch13]
points = [
{ units = 333, color = "#000000" } ,
{ units = 238, color = "#b9b9b9" } ,
{ units = 237.999, color = "#00ffff" } ,
{ units = 228, color = "#000080" } ,
{ units = 218, color = "#00ff00" } ,
{ units = 208, color = "#ffff00" } ,
{ units = 198, color = "#ff0000" } ,
{ units = 188, color = "#000000" } ,
{ units = 178, color = "#ffffff" }
]
[ handler.gradient.ch14]
points = [
{ units = 333, color = "#000000" } ,
{ units = 238, color = "#b9b9b9" } ,
{ units = 237.999, color = "#00ffff" } ,
{ units = 228, color = "#000080" } ,
{ units = 218, color = "#00ff00" } ,
{ units = 208, color = "#ffff00" } ,
{ units = 198, color = "#ff0000" } ,
{ units = 188, color = "#000000" } ,
{ units = 178, color = "#ffffff" }
]
[ handler.gradient.ch15]
points = [
{ units = 333, color = "#000000" } ,
{ units = 238, color = "#b9b9b9" } ,
{ units = 237.999, color = "#00ffff" } ,
{ units = 228, color = "#000080" } ,
{ units = 218, color = "#00ff00" } ,
{ units = 208, color = "#ffff00" } ,
{ units = 198, color = "#ff0000" } ,
{ units = 188, color = "#000000" } ,
{ units = 178, color = "#ffffff" }
]
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# GOES-17 ABI false color.
[[ handler]]
type = "image"
origin = "goes17"
regions = [ "fd" , "m1" , "m2" ]
channels = [ "ch02" , "ch13" ]
directory = "/home/pi/incoming/goes/goes17/{region:short|lower}/fc/{time:%Y-%m-%d}"
filename = "GOES17_{region:short}_FC_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
# This reuses the GOES-16 contrast curve assuming it is identical
[ handler.remap.ch02]
path = "/usr/share/goestools/wxstar/wxstar_goes16_ch02_curve.png"
# This reuses the GOES-16 LUT assuming it is identical
[ handler.lut]
path = "/usr/share/goestools/wxstar/wxstar_goes16_lut.png"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Images relayed from Himawari-8.
[[ handler]]
type = "image"
origin = "himawari8"
directory = "/home/pi/incoming/goes/himawari8/{region:short|lower}/{time:%Y-%m-%d}"
filename = "Himawari8_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# NWS text (weather reports).
[[ handler]]
type = "text"
origin = "nws"
directory = "/home/pi/incoming/goes/nws/{time:%Y-%m-%d}"
filename = "{time:%Y%m%dT%H%M%SZ}_{awips:nnn}{awips:xxx}"
json = false
# NWS images.
[[ handler]]
type = "image"
origin = "nws"
directory = "/home/pi/incoming/goes/nws/{time:%Y-%m-%d}"
filename = "{time:%Y%m%dT%H%M%SZ}_{filename}"
format = "png"
json = false
# Miscellaneous text.
[[ handler]]
type = "text"
origin = "other"
directory = "/home/pi/incoming/goes/text/{time:%Y-%m-%d}"
filename = "{time:%Y%m%dT%H%M%SZ}_{filename}"
json = false
# Store relayed GOES-15 full disks
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "fd" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -2374 , 2371, -1357 , 1347 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 northern hemisphere region
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "nh" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -1864 , 1447, -1357 , -3 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 southern hemisphere region
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "sh" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -1864 , 896, -19 , 1043 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 US region
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "us" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
crop = [ -1312 , 1542, -1327 , -345 ]
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# Store relayed GOES-15 special regions
# No crop specified because it is expected to move around
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "si00" , "si01" , "si02" , "si03" , "si04" ]
directory = "/home/pi/incoming/goes/goes15/{region:short|lower}/{time:%Y-%m-%d}"
filename = "GOES15_{region:short}_{channel:short}_{time:%Y%m%dT%H%M%SZ}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# The following handler takes the same crop from the FD, NH, and US
# products to get more frequent imagery of a smaller area on the
# northern hemisphere. The crop region is a combination of the NH and
# US crop regions.
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "fd" , "nh" , "us" ]
crop = [ -1312 , 1447, -1327 , -345 ]
directory = "/home/pi/incoming/goes/goes15/combine-north/{time:%Y-%m-%d}"
filename = "GOES15_{channel:short}_{time:%Y%m%dT%H%M%SZ}_{region:short}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
# The following handler takes the same crop from the FD and SH
# products to get more frequent imagery of a smaller area on the
# southern hemisphere.
[[ handler]]
type = "image"
origin = "goes15"
regions = [ "fd" , "sh" ]
crop = [ -1864 , 896, -19 , 1043 ]
directory = "/home/pi/incoming/goes/goes15/combine-south/{time:%Y-%m-%d}"
filename = "GOES15_{channel:short}_{time:%Y%m%dT%H%M%SZ}_{region:short}"
format = "jpg"
json = false
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_0_countries_lakes.json"
[[ handler.map]]
path = "/usr/share/goestools/ne/ne_50m_admin_1_states_provinces_lakes.json"
server.py
#!/usr/bin/python
import pycurl
import socket
import json
import time
udp_ip = "127.0.0.1"
udp_port = 8125
next_update = time . time () + 3
goesStats = {
"packets_ok" : 0 ,
"frequency" : 0 ,
"packets_dropped" : 0 ,
"gain" : 0 ,
"reed_solomon_errors" : 0 ,
"omega" : 0 ,
"viterbi_errors" : 0
}
goes_json = json . dumps ( goesStats )
sock = socket . socket ( socket . AF_INET , # Internet
socket . SOCK_DGRAM ) # UDP
sock . bind (( udp_ip , udp_port ))
while True :
data , addr = sock . recvfrom ( 1024 )
values = data . split ( " \n " )
if ( "packets_ok" in values [ 0 ]):
tmp = values [ 0 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "packets_ok" ] = goesStats [ "packets_ok" ] + int ( tmp2 [ 0 ])
if ( "frequency" in values [ 1 ]):
tmp = values [ 1 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "frequency" ] = tmp2 [ 0 ]
if ( "packets_dropped" in values [ 1 ]):
tmp = values [ 1 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "packets_dropped" ] = goesStats [ "packets_dropped" ] + int ( tmp2 [ 0 ])
if ( "gain" in values [ 2 ]):
tmp = values [ 2 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "gain" ] = tmp2 [ 0 ]
if ( "reed_solomon_errors" in values [ 2 ]):
tmp = values [ 2 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "reed_solomon_errors" ] = tmp2 [ 0 ]
if ( "omega" in values [ 3 ]):
tmp = values [ 3 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "omega" ] = tmp2 [ 0 ]
if ( "viterbi_errors" in values [ 3 ]):
tmp = values [ 3 ]. split ( ":" )
tmp2 = tmp [ 1 ]. split ( "|" )
goesStats [ "viterbi_errors" ] = tmp2 [ 0 ]
goes_json = json . dumps ( goesStats )
if ( time . time () >= next_update ):
next_update = time . time () + 3
#client.write_points([goes_json])
influxstr = "packets_ok value=" + str ( goesStats [ "packets_ok" ]) + " \n frequency value=" + goesStats [ "frequency" ] +
" \n packets_dropped value=" + str ( goesStats [ "packets_dropped" ]) + " \n gain value=" + goesStats [ "gain" ] + " \n reed_solomon_errors v
alue=" + goesStats [ "reed_solomon_errors" ] + " \n omega value=" + goesStats [ "omega" ] + " \n viterbi_errors value=" + goesStats [ "viterb
i_errors" ]
#print(influxstr)
try :
crl = pycurl . Curl ()
crl . setopt ( crl . URL , 'http://{ip}:8086/write?db=GOES_STATS' )
crl . setopt ( crl . POSTFIELDS , influxstr )
crl . perform ()
crl . close ()
goesStats [ "packets_ok" ] = 0
goesStats [ "packets_dropped" ] = 0
except :
print ( "failed upload" )
Command for making Gif’s
#!/bin/bash
FILE = ` find /[path to here]/fd/fc/ -mtime -1 -type f -exec dirname {} \; | sort --unique | head -n 1`
convert -resize 720x720 -delay 15 -loop 0 " $FILE /*.jpg" /[outputpath]/1.gif
Picture Frame
Next up was to have something display the pictures all the time. For this, I purchased a Waveshare E-Ink display and a Raspberry Pi Zero.
I took the materials to a professional picture frame shop, but the rest was just managed via a cronjob and the following scripts.
#!/bin/bash
FILE = ` find [ UPDATE-WHERE-YOU-SAVE-IMAGES]/goes16/fd/fc/ -cmin -100 | grep jpg | tail -n 1`
FILE2 = ` find [ UPDATE-WHERE-YOU-SAVE-IMAGES]/goes16/m1/ch13_enhanced -cmin -100 | grep jpg | tail -n 1`
convert $FILE -resize 1200x825 -format bmp -set colorspace Gray -separate -average [ UPDATE-WHERE-YOU-SAVE-IMAGES]/earth.bmp
convert $FILE2 -resize 300x300 -format bmp -set colorspace Gray -separate -average /[UPDATE-WHERE-YOU-SAVE-IMAGES]/pa.bmp
python3 ~/goes-upload/image.py
convert [ UPDATE-WHERE-YOU-SAVE-IMAGES]/goes/out.bmp -format bmp -rotate -90 -set colorspace Gray -separate -average [ UPDATE-WHERE-YOU-SAVE-IMAGES]/goes/screen.bmp
scp [ UPDATE-WHERE-YOU-SAVE-IMAGES]/goes/screen.bmp pi@goes-frame:~/
ssh pi@goes-frame "sudo /home/pi/IT8951/IT8951 0 0 /home/pi/screen.bmp"
The image.py script is below:
#!/usr/bin/python3
import re
import feedparser
import requests
from io import BytesIO
from datetime import datetime
from PIL import Image , ImageFont
#I use weewx for meaturing the temerature outside.
weatherfeed = feedparser . parse ( "http://weather/weewx/rss.xml" )
info = weatherfeed [ 'entries' ][ 0 ][ 'content' ][ 0 ][ 'value' ]. replace ( '<p> \n ' , '' ). split ( "<br /> \n " )
info2 = weatherfeed [ 'entries' ][ 1 ][ 'content' ][ 0 ][ 'value' ]. replace ( '<p> \n ' , '' ). split ( "<br /> \n " )
windchill_str = info [ 3 ][ 34 : 38 ] + "°F"
heatindex_str = info [ 4 ][ 34 : 38 ] + "°F"
humidity_str = info [ 6 ][ 34 : 38 ]
windspeed_str = re . findall ( "\d+" , info [ 8 ][ 34 : 38 ])[ 0 ] + " MPH"
maxwind_str = re . findall ( "\d+" , info2 [ 7 ][ 38 :])[ 0 ] + " MPH"
summary = weatherfeed [ 'entries' ][ 0 ][ 'summary' ]. split ( " \n " )
temp = summary [ 0 ]. split ( ": " )
outsidetmp_str = temp [ 1 ][: - 2 ]
now = datetime . now ()
dt_string = now . strftime ( "%m/%d/%Y %H:%M:%S" )
earth = Image . open ( "[UPDATE-WHERE-YOU-SAVE-IMAGES]/goes/earth.bmp" )
pa = Image . open ( "[UPDATE-WHERE-YOU-SAVE-IMAGES]/goes/pa.bmp" )
response = requests . get ( "http://handorf.org/weather-pic.php?pic=daytempdew.png" )
temp = Image . open ( BytesIO ( response . content ))
img = Image . new ( 'L' , ( 825 , 1200 ), color = 0 )
img_w , img_h = img . size
img . paste ( earth )
img . paste ( pa ,( 0 , 900 ))
img . paste ( temp ,( 315 , 1015 ))
font = ImageFont . truetype ( '~/goes-upload/arial.ttf' , 20 )
lastupdate = font . getmask ( 'Last updated: ' + dt_string , mode = 'L' )
outsidetemp = font . getmask ( 'Outside Temp: ' + outsidetmp_str , mode = 'L' )
windchill = font . getmask ( 'Wind Chill: ' + windchill_str , mode = 'L' )
heatindex = font . getmask ( 'Heat Index: ' + heatindex_str , mode = 'L' )
humidity = font . getmask ( 'Humidity: ' + humidity_str , mode = 'L' )
windspeed = font . getmask ( 'Wind Speed: ' + windspeed_str , mode = 'L' )
windgust = font . getmask ( 'Wind Gust: ' + maxwind_str , mode = 'L' )
mask_w , mask_h = lastupdate . size
mask_w = 300
mask_h = - 500
d = Image . core . draw ( img . im , 0 )
d . draw_bitmap ((( img_w - mask_w ) / 2 , ( img_h - mask_h ) / 2 ), lastupdate , 255 )
d . draw_bitmap ((( img_w + - 100 ) / 2 , ( img_h - - 625 ) / 2 ), outsidetemp , 255 )
d . draw_bitmap ((( img_w + - 100 ) / 2 , ( img_h - - 675 ) / 2 ), windchill , 255 )
d . draw_bitmap ((( img_w + - 100 ) / 2 , ( img_h - - 725 ) / 2 ), heatindex , 255 )
d . draw_bitmap ((( img_w + 400 ) / 2 , ( img_h - - 625 ) / 2 ), humidity , 255 )
d . draw_bitmap ((( img_w + 400 ) / 2 , ( img_h - - 675 ) / 2 ), windspeed , 255 )
d . draw_bitmap ((( img_w + 400 ) / 2 , ( img_h - - 725 ) / 2 ), windgust , 255 )
img . save ( '[UPDATE-WHERE-YOU-SAVE-IMAGES]/goes/out.bmp' )