Table of Contents

Satellite Tracking Antenna

1. Introduction

NOAA

The National Oceanic and Atmospheric Administration (abbreviated as NOAA /ˈnoʊ.ə/ NOH-ə) is a scientific and regulatory agency within the United States Department of Commerce that forecasts weather, monitors oceanic and atmospheric conditions, charts the seas, conducts deep sea exploration, and manages fishing and protection of marine mammals and endangered species in the U.S. exclusive economic zone.

For years, NOAA’s Polar-orbiting Operational Environmental Satellites (POES) satellites have provided the backbone of the global observing system. Their current operational POES satellites include NOAA-15, NOAA-18, and NOAA-19. Today, they operate in various primary and secondary roles, providing additional full global data coverage for a broad range of weather and environmental applications, supporting both short-term weather forecasting and long-term climate and environmental data records.

APT

Automatic Picture Transmission (APT), also known as NOAA-GEOSAT, is an analog image transmission mode used to by the NOAA weather satellites and formerly some Russian weather satellites to transmit satellite weather photos.

In order to receive those pictures, different types of antennas can be used. Some of them, such as the V-Dipole, must be oriented towards the true north due to satellites' trajectories.

THIS PROJECT

The initial goal of this project was to make a device that is able to orient an antenna towards one of the 3 active NOAA satellites. Due to the fact that orienting the antenna towards satellites themselves is not necessary and due to other technical circumstances, the final project resumes to a device that collects data necessary for calibrating the software and also allows its user to align an antenna to specific angles. In the next chapters we'll talk about the used hardware, the hardly working software and how to set the device up.

2. Hardware

Mechanical Elements

Electronic Components

The following components have been used:

The components' connections can be seen in the following diagram:

3. Software

The software chapter can be split into 3 parts

Raspberry Pi

1st Functionality: Exchange Data

The RPi runs a python script that gathers data from the GPS and from Arduino UNO's IMU and uploads it to the database, alongside its own IP in the LAN. Various libraries had to be installed and a setup for GPS was needed.

#!/usr/bin/env python
 
import pyrebase
import time
from gps import *
import netifaces as ni
import signal
import serial
 
firebaseConfig = {
  "apiKey": "REDACTED",
  "authDomain": "REDACTED",
  "databaseURL": "REDACTED",
  "projectId": "REDACTED",
  "storageBucket": "REDACTED",
  "messagingSenderId": "REDACTED",
  "appId": "REDACTED"
}
 
 
USER_EMAIL = 'REDACTED'
USER_PASSWORD = 'REDACTED'
 
def configDatabase():
    firebase = pyrebase.initialize_app(firebaseConfig)
 
    auth = firebase.auth()
    login = auth.sign_in_with_email_and_password(USER_EMAIL, USER_PASSWORD)
 
    db = firebase.database()
 
    return (db, auth, login)
 
def configArduino():
    mySerial = serial.Serial('/dev/ttyACM0', 115200, timeout = 1)
    mySerial.reset_input_buffer()
 
    return mySerial
 
def getIP():
    ip_addr = 'Not Available'
 
    try:
        ip_addr = ni.ifaddresses('wlan0')[ni.AF_INET][0]['addr']
    except:
        try:
            ip_addr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
        except:
            pass
 
    return ip_addr    
 
def getPositionData(gpsd):
    nx = gpsd.next()
 
    while (nx['class'] != 'TPV'):
        nx = gpsd.next()
 
    latitude = getattr(nx,'lat', 'Not Available')
    longitude = getattr(nx,'lon', 'Not Available')
 
    return (str(longitude), str(latitude))
 
def getGPSPosition():
    gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE)
 
    try:
        (longitude, latitude) = getPositionData(gpsd)
        return (longitude, latitude)
    except:
        return ('Not Available', 'Not Available')
 
def getANTPosition(arduino_serial):
    head_x = 'Not Available'
    head_y = 'Not Available'
 
    if arduino_serial.in_waiting > 0:
        line = arduino_serial.readline().decode('utf-8').rstrip()
        headings = line.split(',')
 
        for heading in headings:
            if heading.startswith('X'):
                head_x = heading.strip('X')
            elif heading.startswith('Y'):
                head_y = heading.strip('Y')
 
    return (head_x, head_y)
 
 
def gatherData(arduino_serial):
    arduino_serial.reset_input_buffer()
    ip_addr = getIP()
    (longitude, latitude) = getGPSPosition()
    (head_x, head_y) = getANTPosition(arduino_serial)
 
    return (ip_addr, longitude, latitude, head_x, head_y)
 
def main():
    print("Client has started!")
 
    (db, auth, login) = configDatabase()
    mySerial = configArduino()
 
    while True:
        new_data = gatherData(mySerial)
 
        data = {}
 
        if (new_data[0] != 'Not Available'):
            data['IP'] = new_data[0]
        if (new_data[1] != 'Not Available'):
            data['LONGITUDE'] = new_data[1]
        if (new_data[2] != 'Not Available'):
            data['LATITUDE'] = new_data[2]
        if (new_data[3] != 'Not Available'):
            data['HEADING_X'] = new_data[3]
        if (new_data[4] != 'Not Available'):
            data['HEADING_Y'] = new_data[4]
 
        # Reading from DB
        read_data = db.child('UsersData').child(login['localId']).child('Data').child('MOVEMENT').get(login['idToken'])
        movement_x = read_data.val()
        if (movement_x != 0):
            data['MOVEMENT'] = 0
            message_to_send = str(movement_x) + '\n'
            message_to_send = message_to_send.encode('ASCII')
            mySerial.write(message_to_send)
 
        # Writing to DB
        db.child('UsersData').child(login['localId']).child('Data').update(data, login['idToken'])
        time.sleep(1.0)
 
    return 0
 
if __name__ == "__main__":
    raise SystemExit(main())
2nd Functionality: TCP Server for RTL-SDR

RTL-SDR can now be used remotely in the LAN (hence why its IP is needed). This was done by installing and configuring rtl_tcp.

Arduino UNO

The Arduino UNO reads data from the IMU and from Serial and outputs data to the Serial and to the motors (makes them spin). This is the code it runs:

// Include the AccelStepper Library
#include <AccelStepper.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
 
// Define pin connections
const int dirPin = 6;
const int stepPin = 3;
const int enablePin = 8;
// Define motor interface type
#define motorInterfaceType 1
 
// Creates an instance
AccelStepper myStepper(motorInterfaceType, stepPin, dirPin);
 
 
double xPos = 0, yPos = 0, headingVel = 0;
uint16_t BNO055_SAMPLERATE_DELAY_MS = 10; //how often to read data from the board
uint16_t PRINT_DELAY_MS = 100; // how often to print the data
uint16_t printCount = 0; //counter to avoid printing every 10MS sample
 
 
//velocity = accel*dt (dt in seconds)
//position = 0.5*accel*dt^2
double ACCEL_VEL_TRANSITION =  (double)(BNO055_SAMPLERATE_DELAY_MS) / 1000.0;
double ACCEL_POS_TRANSITION = 0.5 * ACCEL_VEL_TRANSITION * ACCEL_VEL_TRANSITION;
double DEG_2_RAD = 0.01745329251; //trig functions require radians, BNO055 outputs degrees
 
// Check I2C device address and correct line below (by default address is 0x29 or 0x28)
//                                   id, address
Adafruit_BNO055 bno = Adafruit_BNO055(55, 0x28);
 
unsigned long tStart;
 
void setup() {
    Serial.begin(115200);
 
  while (!Serial) delay(10);  // wait for serial port to open!
 
  if (!bno.begin())
  {
    Serial.print("No BNO055 detected");
    while (1);
  }
 
  delay(1000);
 
  // set the maximum speed, acceleration factor,
  // initial speed and the target position
  myStepper.setMaxSpeed(1000);
  myStepper.setAcceleration(50);
  myStepper.setSpeed(200);
 
  pinMode(enablePin, OUTPUT);
  digitalWrite(enablePin, LOW);
 
  tStart = micros();
}
 
void loop() {
  if (Serial.available() > 0){
    String myData = Serial.readStringUntil('\n');
    myStepper.move(myData.toInt());
    Serial.println(myData);
  }
  // Change direction once the motor reaches target position
  if (myStepper.distanceToGo() != 0) 
    myStepper.run();
 
 
  if ((micros() - tStart) > (BNO055_SAMPLERATE_DELAY_MS * 1000)) {
    tStart = micros();
 
    sensors_event_t orientationData , linearAccelData;
    bno.getEvent(&orientationData, Adafruit_BNO055::VECTOR_EULER);
    //  bno.getEvent(&angVelData, Adafruit_BNO055::VECTOR_GYROSCOPE);
    bno.getEvent(&linearAccelData, Adafruit_BNO055::VECTOR_LINEARACCEL);
 
    xPos = xPos + ACCEL_POS_TRANSITION * linearAccelData.acceleration.x;
    yPos = yPos + ACCEL_POS_TRANSITION * linearAccelData.acceleration.y;
 
    // velocity of sensor in the direction it's facing
    headingVel = ACCEL_VEL_TRANSITION * linearAccelData.acceleration.x / cos(DEG_2_RAD * orientationData.orientation.x);
 
    if (printCount * BNO055_SAMPLERATE_DELAY_MS >= PRINT_DELAY_MS) {
      //enough iterations have passed that we can print the latest data
      Serial.print("X");
      Serial.print(orientationData.orientation.x);
      Serial.print(",Y");
      Serial.println(orientationData.orientation.y);
 
      printCount = 0;
    }
    else {
      printCount = printCount + 1;
    }
  }
}

Firebase

Code and Configuration

The database has been configured the same way it was done at school, but the field in the database are rather rewritten than appended. A firebase-hosted web app has also been added as the GUI for the project (again, same as at school). It uses a simple html page and the following javascript code that deals with database communication:

  // MANAGE LOGIN/LOGOUT UI
  const setupUI = (user) => {
    if (user) {
      //toggle UI elements
      loginElement.style.display = 'none';
      contentElement.style.display = 'block';
      authBarElement.style.display ='block';
      userDetailsElement.style.display ='block';
      userDetailsElement.innerHTML = user.email;
 
      // get user UID to get data from database
      var uid = user.uid;
 
      // Database paths (with user UID)
      var dbPath = 'UsersData/' + uid.toString();
 
      // Database references
      var dbRef = firebase.database().ref(dbPath);
 
      //CHECKBOXES
      // Checbox (cards for sensor readings)
      cardsCheckboxElement.addEventListener('change', (e) =>{
        if (cardsCheckboxElement.checked) {
          cardsReadingsElement.style.display = 'block';
        }
        else{
          cardsReadingsElement.style.display = 'none';
        }
      });
 
 
      var intervalId = window.setInterval(function(){
        // CARDS
        // Get the latest readings and display on cards
        dbRef.orderByKey().on('child_added', snapshot =>{
          var jsonData = snapshot.toJSON();
 
          var ip = jsonData.IP;
          var heading_x = jsonData.HEADING_X;
          var heading_y = jsonData.HEADING_Y;
          var latitude = jsonData.LATITUDE;
          var longitude = jsonData.LONGITUDE;
          // Update DOM elements
          ipElement.innerHTML = ip;
          yawElement.innerHTML = heading_x;
          pitchElement.innerHTML = heading_y;
          latElement.innerHTML = latitude;
          longElement.innerHTML = longitude;
        });
      }, 2000);
 
      turnCWButtonElement.addEventListener('click', (e) =>{
        var updates = {};
        updates['/UsersData/' + uid.toString() + '/Data/MOVEMENT'] = "10";
        firebase.database().ref().update(updates);
      });
 
      turnCCWButtonElement.addEventListener('click', (e) => {
        var updates = {};
        updates['/UsersData/' + uid.toString() + '/Data/MOVEMENT'] = "-10";
        firebase.database().ref().update(updates);
      });
GUI

The GUI can be seen in the picture below. The web app outputs antenna's current position, the longitude and latitude of the device and the RPi's IP and expects one of the two buttons pressed as input (those will move the antenna on one of the axis).

The address of the web app is: https://noaa-tracker.web.app/

4. Using the Device

In order to use the device, one should follow those steps:

  1. Connect the Raspberry Pi to the LAN via Wi-Fi or Ethernet (the later might not be possible do to the rotating nature of the device);
  2. Install SDR#, GPredict, SDRSharp.GpredictConnector, WXtoIMG and VBAudio Cable (Internal Audio Routing);
  3. Start SDR#
  4. Select RTL-SDR TCP as source → Click on Gear Icon → Add the IP address of the RPi (that can be seen in the GUI) in the IP field;
  5. Select Settings (3 lines) → go to Plugins → GPredictConnect → Check the enable checkbox;
  6. Select Settings (3 lines) → go to Audio → click on Output → Select [Windows DirectSound] Cable Input (VB-Audio Cable)
  7. Select Settings (3 lines) → go to Radio → check WFM radio button (how funny) and set the Bandwidth to 40KHz
  8. Start GPredict
  9. Go to Edit → Preferences → Ground Station and add a new ground station using the information from the GUI (latitude and longitude)
  10. Go to Edit → Preferences → Interfaces → Add new Interface (SDRSharp with default port)
  11. Go to Edit → Update TLE data from network
  12. Go to Edit → Update transponder data
  13. Go to File → New module → choose a name; choose your ground station name; choose the 3 NOAA satellites (15 18 and 19) and add them to the right → Click OK
  14. Go to the little triangle to the right (Module options / shortcuts) → Radio Control → Select your Radio defined earlier and click Engage
  15. From the Radio Control panel → Choose a satellite that's in your range → Choose APT Downlink → Click Track and T buttons
  16. Go to the little triangle and also check the “Autotrack” checkbox
  17. Open WXtoImg
  18. Go to Options → Ground Station Location → Set latitude and longitude → Click Ok
  19. Go to Options → Recording Options → Make sure the soundcard is the VB-Audio Virtual Cable → Click Ok
  20. Go to File → Update Keplers (deprecated, must find another way)
  21. Go to File → Record → Auto Record
  22. Wait & Hope

But how does it work?

5. Challenges

Here is a list of past and current problems