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.
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.
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.
The following components have been used:
The components' connections can be seen in the following diagram:
The software chapter can be split into 3 parts
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())
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.
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; } } }
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); });
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/
In order to use the device, one should follow those steps:
But how does it work?
Here is a list of past and current problems