-----
It all started out simple enough... We were just curious how Orbital Files and Kepler Elements are use to define the current and predicted locations of a satellite. One thing leads to another and the result was an International Space Station tracker. Sure, it's been done before but you can learn a few things when you do it yourself.
-----
For example, we wrote the Python3 program on our own and then decided we would see how ChatGPT would handle the problem. Turns out we were a lot faster at the task than the ChatGPT AI. ChatGPT would finally get there, but it seemed error prone. Still, impressively ChatGPT finally got to a solution after a TON of help from us. But... we did like some of the things ChatGPT did better. For example, the ChatGPT routine to translate compass degrees to a cardinal direction was better than ours. So, in addition to learning about Orbital Files and Kelper Elements we learned a little about what ChatGPT is good and "less good" at.
-----
The project updates the location on the ISS for your location every ten seconds. If the ISS is above the horizon a LED flashes to let you know. The whole enchilada is contained in a 3D Printed box.
Here's the result and we like it!
-----
The connection of the 16x2 LCD and the indicator LED are simple and pretty obvious from the pin names in the source code below.
-----
For those that may want to duplicate the build below is our Python3 source code:
# ISS Tracker
# Project details at: WhiskeyTangoHotel.Com
# APRIL 2024
#
# Raspberry PI 3. Runs under Python3
#
# Show current Alt and Az of the ISS
# Results are displayed in the terminal and 1602 Line LCD
# LED Blinks when ISS is above horizon.
from datetime import datetime, timedelta
from skyfield.api import Topos, load
import os
import urllib.request
import time
import pytz
import smbus
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
risefall = "Calculating..." # Is the ISS getting closer or farther from the horizon
seconds_between_screens = 5 # How long to show the position and current time screen
# Set up the 16 x 2 Line I2C LCD
I2C_ADDR = 0x27 # I2C device address
LCD_WIDTH = 16 # Maximum characters per line
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command
LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_BACKLIGHT = 0x08 # On
# LCD_BACKLIGHT = 0x00 # Off
ENABLE = 0b00000100 # Enable bit
# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
# Open I2C interface
bus = smbus.SMBus(1) # Rev 2 Pi uses 1 RasPI(SXSW)
# LED pin to blink if ISS is above horizon
LED_PIN = 17
def setup_led():
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
def led_on():
GPIO.output(LED_PIN, GPIO.HIGH)
def led_off():
GPIO.output(LED_PIN, GPIO.LOW)
def lcd_init():
# Initialise display
lcd_byte(0x33,LCD_CMD) # 110011 Initialise
lcd_byte(0x32,LCD_CMD) # 110010 Initialise
lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
lcd_byte(0x01,LCD_CMD) # 000001 Clear display
time.sleep(E_DELAY)
def lcd_byte(bits, mode):
# Send byte to data pins
# bits = the data
# mode = 1 for data
# 0 for command
bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT
# High bits
bus.write_byte(I2C_ADDR, bits_high)
lcd_toggle_enable(bits_high)
# Low bits
bus.write_byte(I2C_ADDR, bits_low)
lcd_toggle_enable(bits_low)
def lcd_toggle_enable(bits):
# Toggle enable
time.sleep(E_DELAY)
bus.write_byte(I2C_ADDR, (bits | ENABLE))
time.sleep(E_PULSE)
bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
time.sleep(E_DELAY)
def lcd_string(message,line):
# Send string to display
message = message.ljust(LCD_WIDTH," ")
lcd_byte(line, LCD_CMD)
for i in range(LCD_WIDTH):
lcd_byte(ord(message[i]),LCD_CHR)
# We set up to view the ISS with Austin, Texas coordinates
austin = Topos(latitude=30.35307, longitude=-97.85726)
# URL to fetch TLE data
tle_url = 'https://www.celestrak.com/NORAD/elements/stations.txt'
# Let's do self-test on the LCD and the LED
lcd_init()
# Center the text for line 1
alt_text = "ISS TRACKER"
print("ISS TRACKER")
spaces = (LCD_WIDTH - len(alt_text)) // 2
alt_text = " " * spaces + alt_text
lcd_string(alt_text, LCD_LINE_1)
# Flash LED self-test
setup_led()
for _ in range(5): # Flash LED
# Center the text for line 2
az_text = "Starts in " + str(5 - _) + "..."
print("Starts in " + str(5 - _) + "...")
spaces = (LCD_WIDTH - len(az_text)) // 2
az_text = " " * spaces + az_text
lcd_string(az_text, LCD_LINE_2)
led_on()
time.sleep(0.5) # LED on for 0.5 seconds
led_off()
time.sleep(0.5) # LED off for 0.5 seconds
GPIO.cleanup()
print(" ")
last_altitude = None # Variable to store the previous altitude
while True: # Loop forever...
# Initialise display
lcd_init()
# Check if the TLE data file exists
if not os.path.exists('stations.txt'):
# If it doesn't exist, download it
urllib.request.urlretrieve(tle_url, 'stations.txt')
# Get the last modified time of the TLE data file
last_modified = datetime.fromtimestamp(os.path.getmtime('stations.txt'))
# Check if 12 hours have passed since the last update
if datetime.now() - last_modified > timedelta(hours=12):
# If it is too old, update the TLE data file
urllib.request.urlretrieve(tle_url, 'stations.txt')
# Load the updated ISS TLE data
satellites = load.tle_file('stations.txt')
iss = satellites[0] # Accessing the first element which contains the ISS data
# Get the current time
ts = load.timescale()
current_time = ts.now()
# Get the position of the ISS relative to Austin, Texas
difference = iss - austin
topocentric = difference.at(current_time)
alt, az, distance = topocentric.altaz()
#alt.degrees = 73 # For debug only. Comment for normal use
# Print the current time and date in Austin
austin_time = current_time.astimezone(pytz.timezone('America/Chicago'))
print(austin_time.strftime("%A"), austin_time.strftime("%m-%d-%Y %H:%M:%S %Z"))
print("--------------------------------------")
# Print altitude and azimuth of the ISS as integers
print("ISS altitude: " + str(int(alt.degrees)) + "\u00b0")
# Convert azimuth to cardinal compass direction
compass_directions = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
compass_index = round(az.degrees / (360. / len(compass_directions)))
compass = compass_directions[int(compass_index) % len(compass_directions)]
#print("Cardinal direction: " + compass)
print(" ISS azimuth: " + str(int(az.degrees)) + "\u00b0" + " <" + compass + ">")
# Check if the altitude is increasing or decreasing
if last_altitude is not None:
if alt.degrees > last_altitude:
risefall = "RISING"
elif alt.degrees < last_altitude:
risefall = "FALLING"
else:
risefall = "STABLE"
#risefall = "RISING" # For debug only. Comment for normal use
print(" " + risefall)
last_altitude = alt.degrees # Update the last altitude
print(" ")
# Display on LCD
lcd_init()
# Center the text for line 1
alt_text = "Alt " + str(int(alt.degrees)) + " deg @"
spaces = (LCD_WIDTH - len(alt_text)) // 2
alt_text = " " * spaces + alt_text
lcd_string(alt_text, LCD_LINE_1)
# Center the text for line 2
az_text = str(int(az.degrees)) + " deg <" + compass + ">"
spaces = (LCD_WIDTH - len(az_text)) // 2
az_text = " " * spaces + az_text
lcd_string(az_text, LCD_LINE_2)
# Flash LED if altitude is positive
setup_led()
if alt.degrees > 0:
for _ in range(seconds_between_screens): # Flash LED
led_on()
time.sleep(0.5) # LED on for 0.5 seconds
led_off()
time.sleep(0.5) # LED off for 0.5 seconds
else:
led_off()
time.sleep(seconds_between_screens)
# Date/Time on LCD briefly
lcd_init()
if alt.degrees > 0:
led_on() # LED on is ISS UP while Day/Time display
# Center the text for line 1
#alt_text = austin_time.strftime("%A") # Show the DOW
alt_text = risefall # Rising or Falling?
spaces = (LCD_WIDTH - len(alt_text)) // 2
alt_text = " " * spaces + alt_text
lcd_string(alt_text, LCD_LINE_1)
# Center the text for line 2
az_text = austin_time.strftime("%H:%M:%S %Z") # Show the local time
spaces = (LCD_WIDTH - len(az_text)) // 2
az_text = " " * spaces + az_text
lcd_string(az_text, LCD_LINE_2)
# Flash LED if altitude is positive
setup_led()
if alt.degrees > 0:
for _ in range(seconds_between_screens): # Flash LED
led_on()
time.sleep(0.5) # LED on for 0.5 seconds
led_off()
time.sleep(0.5) # LED off for 0.5 seconds
else:
led_off()
time.sleep(seconds_between_screens)
time.sleep(0.1) # tiny delay before cleanup to ensure the LED is off
GPIO.cleanup()
-----