## Monday, April 29, 2024

### Where is the ISS? [Rasberry Pi version]

-----

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
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
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
lcd_toggle_enable(bits_high)

# Low bits
lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
# Toggle enable
time.sleep(E_DELAY)
time.sleep(E_PULSE)
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

# 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'):
urllib.request.urlretrieve(tle_url, 'stations.txt')

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
iss = satellites[0]  # Accessing the first element which contains the ISS data

# Get the current time
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()
-----