International Space Station Tracker
by BlackberryJamMan in Circuits > Robots
12945 Views, 178 Favorites, 0 Comments
International Space Station Tracker
This device will track the location of the International Space Station (ISS) in real time and continuously point to it as it orbits the Earth. It is battery powered by lithium-ion cells (recovered from an old laptop) so that it can be taken out into the garden on a clear night. The microprocessor is an ESP32 which will connect to wifi to download the co-ordinates of the ISS. It then does some calculations to work out the angles between the observer on Earth and the ISS in the sky so that it can direct the pointer towards it. The location of the ISS is updated every 5 seconds, so the device will accurately track it as it moves across the sky.
Supplies
- ESP32 microprocessor in development board format
- 0.91" OLED display 128 x 32
- 2S li-ion battery cell charger protection board (7.4v)
- MP1584EN DC step down power supply
- Veroboard (25 x 13 holes)
- Right angle headers (2.54mm pitch)
- Female pin headers for PCB (2.54mm pitch)
- 18650 li-ion cells x 2
- 18650 cell holders x 2
- 28BYJ-48 stepper motors x 2
- ULN2003 stepper motor driver boards x 2
- 60002RS Bearing (10x26x8mm)
- 6 channel slip ring12.5mm diameter body, 24mm diameter top ring
- Rocker switch KCD1-104
- Barrel jack socket 2.1mm
- Dupont jumper wires for connecting the circuits
- M3 x 16 Countersunk machine screws x 4 (for base plate)
- M5 x 20 Dome headed machine screw (touch switch)
- M6 washer (touch switch)
- M3 x 16 Dome headed machine screws x 2 (stepper motor)
- M3 x 16 Countersunk machine screws x 4 (spindle plate)
- M3 x 10 Dome headed machine screws x 2 (Dome locking)
- M3 x 8 Countersunk machine screws x 4 (Inner plate)
- M3 x 8 Dome headed machine screws x 4 (veroboard and stepper driver on inner plate)
- M2 x 4 self tappers x 3 (slip ring)
- M3 x 10 Dome headed machine screws x 6 (stepper motor and driver board on rotating platform)
- M3 x 12 Countersunk machine screws x 4 (stepper motor and driver mounts to rotating platform)
- M3 nuts x 8
ESP32 Microprocessor Mounting Board
The ESP32 is the microprocessor that will carry out all the calculations for the device and control the stepper motors to align the pointer to the ISS. The ESP32 is in the form of a development board, so it has a micro usb connector, making it easier to upload the software from your computer.
Prepare a piece of veroboard with 24 strips of 13 holes. Once the headers have been soldered in place, the veroboard copper strips need to be cut so each side of the ESP32 are electrically separate. I do this with a 3mm drill and apply gentle pressure until the copper strips are just cut through. You will see I have offset the cuts diagonally so they don't weaken the board by being in a straight line.
Rather than soldering the development board onto the veroboard strip, it is better to solder in some female headers, so that the ESP32 can easily be removed (without desoldering all the pins). In order to make connections to the board, an extra row of right angle header pins were added and soldered in place and the connections can be made with wires that have dupont connectors on their ends. This has the advantage that it is easy to connect and disconnect the different components of the device so that it can be tested as it is constructed.
The usb connector will now overhang the edge of the veroboard, giving some clearance inside the housing.
Schematic Wiring Diagram
The schematic shows how to connect up all of the electrical components. Each component should be added and then tested before moving on to the next section. In this project, the connections were made with dupont style connectors so they could easily be changed without the need to desolder them.
The Base Section
Print the 001 Base Section Outer Shell with supports as it has holes for the switches. I printed the 002 Inner Plate in red so you can see the contrast of the two components. The supports can quickly be removed with a pair of fine nose pliers. It's now a good time to test fit the components that will go into this section of the body including the barrel power jack, the on/off switch, the buzzer, the touch switch and the OLED display
All of the holes in the inner plate can be drilled to 2.5mm and then tapped with an M3 thread. These will be used as fixing points for the battery case and the stepper motor driver. Remember to tap the battery mounting screws from the underside (flat face) and the stepper motor holes from the topside (with recess for veroboard).
The inner plate should slot into the main body of the outer shell and it will sit on two ledges. To hold it in place, I added a few drops of superglue.
When describing the motion of an astronomical device, the left-to-right rotation of the base is know as the azimuth. The azimuth stepper motor driver is located in this section too. The pre-made ULN2003 boards are often left with long component leads after they have been soldered in place, so it is a good idea to trim these flush. The stepper motor driver board can now be fixed in place with machine screws.
Connect the ESP32 to the battery power supply, the azimuth stepper driver, the OLED display and the touch switch according the wiring diagram.
The Battery Section
The unit can be powered by a 5v mains transformer through the barrel jack connector when it is stored indoors. When it is plugged in, it will be charging the li-ion cells using a specialist charging circuit so they don't get overcharged (as this can be dangerous). If the charging cable is removed, the switch on the front can power the device through the on-board battery cells.
The li-ion cells were recovered from an old laptop power pack and new red heatshrink covers were applied. The cell holders have wires soldered to each of the positive and negative terminals and the other ends have dupont terminals crimped onto them. the cell holders are fixed to the inner plate with countersunk screws.
The power supply jack is wired into a li-ion charger circuit and the output from the cells is fed into a high frequency DC voltage converter which is adjusted with a digital multimeter to 5 volts output.
The Spindle Plate
3D print the following components:
- 003 Spindle Plate
- 003 Centre Spindle
- 003 Spacer for Rotating Platform
- 003 Involute Azimuth Gear
This is a straightforward assembly. Fix the stepper motor in place with machine screws, washers and nuts so that it is held firmly in place.
The thread of the spindle could be cleaned up with an M8 die and the hollow centre should be drilled with an 5mm drill to ensure no printer threads catch the wires as they are passed through.
The spindle locates in the plate with a key that stops it from rotating. A drop of superglue ensures it does not get pushed out.
Mathematical Calculations
The attached paper shows the mathematical calculations that were used to find the location of the ISS. You can skip this section if you want, but you might want to return to it, to understand how the solution was coded.
Downloads
Software
Here is the main program that controls the ISS tracker. Make sure you download the .ino file above, rather than copy and pasting the text from this page to ensure you have the correct formatting.
I have also included a couple of test files so you can check the operation of your touch switch and stepper motors as you build the device.
// ISS Tracker - David Hunt // Version 1.4 // Licence - Attribution Non-commercial Share Alike (by-nc-sa) // download all the necessary libraries using the library manager #include <AccelStepper.h> // to drive stepper motors #include <ezTime.h> #include <WiFi.h> #include <HTTPClient.h> // to retrieve data from API #include <ArduinoJson.h> // to extract ISS location information #include <U8g2lib.h> // search for U8g2 in library manager #include <Wire.h> time_t next_pass_timestamp = 1572652800; // 2 Nov 2019 as a reference point const char ssid[] = "Your network here"; // your network SSID (name) const char pass[] = "Router password"; // your network password const int touch_threshold = 40; // touchpin input (T0) is GPIO4 const float steps_per_degree = 4096 / 360; //4096 steps per revolution in half step mode double my_latitude = 51.89970; // change to your location here double my_longitude = -2.12084; // find your co-ordinates using google maString next_pass_JSON; // location data is retrieved in JSON format int next_risetime; int next_duration; U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0); // Motor pin definitions in GPIO numbers #define motor_pin_az1 13 // IN1 on the ULN2003 driver #define motor_pin_az2 12 // IN2 on the ULN2003 driver #define motor_pin_az3 14 // IN3 on the ULN2003 driver #define motor_pin_az4 27 // IN4 on the ULN2003 driver #define motor_pin_el1 26 // IN1 on the ULN2003 driver #define motor_pin_el2 25 // IN2 on the ULN2003 driver #define motor_pin_el3 33 // IN3 on the ULN2003 driver #define motor_pin_el4 32 // IN4 on the ULN2003 driver #define MotorInterfaceType 8 // 8 = half step mode, 4 = full step mode // Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper library with 28BYJ-48 stepper motor: AccelStepper azimuth_motor = AccelStepper(MotorInterfaceType, motor_pin_az1, motor_pin_az3, motor_pin_az2, motor_pin_az4); AccelStepper elevation_motor = AccelStepper(MotorInterfaceType, motor_pin_el1, motor_pin_el3, motor_pin_el2, motor_pin_el4); Timezone my_timezone; // use the internet to look up the next flyby time void get_next_pass(double lat, double lon, int &p_risetime, int &p_duration){ HTTPClient http; const char* api_host = "http://api.open-notify.org/iss-pass.json?lat="; char my_lat_position[10]; char my_lon_position[10]; char api_address[255]; // convert the coordinates stored as doubles to strings with 4 dp precision dtostrf(lat,7,4,my_lat_position); dtostrf(lon,7,4,my_lon_position); // create the url with get request strcpy(api_address, api_host); strcat(api_address, my_lat_position); strcat(api_address, "&lon="); strcat(api_address, my_lon_position); // only get the time and date of the next pass strcat(api_address, "&n=1"); http.begin(api_address); int httpCode = http.GET(); if (httpCode > 0) { String payload = http.getString(); // Serial.println(httpCode); // Serial.println(payload); const size_t capacity = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 200; DynamicJsonDocument doc(capacity); DeserializationError error = deserializeJson(doc, payload); p_duration = doc["response"][0]["duration"];// write this value to next_duration p_risetime = doc["response"][0]["risetime"];// write this value to next_risetime } else { Serial.println("Error on HTTP request"); } http.end(); // Free the resources } // convert this to human readable form in days, hours, minutes and seconds String time_to_next_flyby(int next_observation){ int time_remaining; int next_rise_days; int next_rise_hours; int next_rise_minutes; int next_rise_seconds; String countdown_time; time_remaining = next_observation - now(); next_rise_days = time_remaining/(3600 * 24); time_remaining %= (3600 * 24); next_rise_hours = time_remaining / 3600; time_remaining %= 3600; next_rise_minutes = time_remaining / 60; next_rise_seconds = time_remaining % 60; countdown_time += next_rise_days; countdown_time += " days "; countdown_time += next_rise_hours; if (next_rise_minutes < 10){ countdown_time += ":0";}// add leading zero for single digit else{ countdown_time += ":"; } countdown_time += next_rise_minutes; if (next_rise_seconds < 10){ countdown_time += ":0";}// add leading zero for single digit else{ countdown_time += ":"; } countdown_time += next_rise_seconds; return countdown_time; } // find the current location of the ISS over the Earth // and calculate the angles in order to point to it from our location void point_to_ISS(){ HTTPClient http; double latitude; double longitude; if (http.begin("http://api.open-notify.org/iss-now.json")){ int httpCode = http.GET(); // httpCode will be negative if there is an error if (httpCode > 0) { String ISS_Location_Data = http.getString(); //Serial.println(httpCode); // this will be 200 for a successful retrieval //Serial.println(ISS_Location_Data); // Json data const size_t capacity = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 200; DynamicJsonDocument doc(capacity); DeserializationError error = deserializeJson(doc, ISS_Location_Data); //const char* message = doc["message"]; long timestamp = doc["timestamp"]; latitude = doc["iss_position"]["latitude"]; longitude = doc["iss_position"]["longitude"]; // Print values useful for debugging to check it is working //Serial.println(timestamp); //Serial.println(latitude, 4); //Serial.println(longitude, 4); } else { Serial.print("HTTP error :"); Serial.println(http.errorToString(httpCode).c_str()); } http.end(); // Free the resources } else { Serial.print("HTTP error: unable to connect"); } // ESP32 calculates angles in radians (not degrees) double latitude_in_radians = latitude * PI / 180; double longitude_in_radians = longitude * PI / 180; double my_latitude_in_radians = my_latitude * PI / 180; double my_longitude_in_radians = my_longitude * PI / 180; // calculate azimuth bearing to ISS double x = cos(latitude_in_radians)*sin(longitude_in_radians-my_longitude_in_radians); double y = cos(my_latitude_in_radians)*sin(latitude_in_radians)-sin(my_latitude_in_radians)*cos(latitude_in_radians)*cos(longitude_in_radians-my_longitude_in_radians); double b = atan2(x,y); //Serial.print("Bearing to ISS: "); b=b*180/PI;// convert to degrees //Serial.println(b); // calculate elevation bearing to ISS double earth_radius = 6371; // mean value in km double orbit = 412; // mean value in km - perhaps look this value up too? double x1 = earth_radius * cos(my_latitude_in_radians) * cos(my_longitude_in_radians); double y1 = earth_radius * cos(my_latitude_in_radians) * sin(my_longitude_in_radians); double z1 = earth_radius * sin(my_latitude_in_radians); double x2 = earth_radius * cos(latitude_in_radians) * cos(longitude_in_radians); double y2 = earth_radius * cos(latitude_in_radians) * sin(longitude_in_radians); double z2 = earth_radius * sin(latitude_in_radians); double dist_to_nadir = sqrt(sq(x2-x1)+sq(y2-y1)+sq(z2-z1)); //Serial.print("Distance to nadir :"); //Serial.println(dist_to_nadir); double geocentric_angle_in_radians = 2 * asin(dist_to_nadir/(2*earth_radius)); //Serial.print("Geocentric angle in degrees :"); //Serial.println(geocentric_angle_in_radians * 180 / PI); double distance_to_ISS = sqrt(sq(orbit+earth_radius)+sq(earth_radius)-2*(orbit+earth_radius)*earth_radius*cos(geocentric_angle_in_radians)); //Serial.print("Distance to ISS :"); //Serial.println(distance_to_ISS); double ISS_angle = (180 / PI) * asin((orbit +earth_radius) * (sin(geocentric_angle_in_radians)/distance_to_ISS)); // azimuth stepper motor has 4096 half steps per revolution and gear ratio 113/22 azimuth_motor.moveTo(4096/360 * 113/22 * b); // elevation motor has 4096 half steps per revolution elevation_motor.moveTo(4096/ 360 * ISS_angle); // run the motor whilst waiting to update the co-ordinates every 5 seconds double holding_time = now(); while (now()-holding_time < 5){ azimuth_motor.run(); elevation_motor.run(); yield(); } } // print out messages on OLED using two lines of text void display_message(String line_one, String line_two){ u8g2.setFont(u8g2_font_8x13_tr); //u8g2.setFont(u8g2_font_fur14_tr); u8g2.firstPage(); u8g2.setCursor(0, 15); u8g2.print(line_one); u8g2.setCursor(0, 31); u8g2.print(line_two); u8g2.nextPage(); } void setup() { Serial.begin(115200); delay(3000); // try this to allow OLED to establish communication? u8g2.begin(); //for OLED u8g2.enableUTF8Print();// // Set the maximum motor steps per second: azimuth_motor.setMaxSpeed(800); // large gear ratio, so speed this up azimuth_motor.setAcceleration(100.0); elevation_motor.setMaxSpeed(400); elevation_motor.setAcceleration(100.0); // ######################### calibration section ############################### display_message("Stop arrow when","pointing down"); while (touchRead(T0)> touch_threshold){ elevation_motor.move(400); elevation_motor.run(); } // button has been pressed when elevation pointer is downwards display_message("Elevation motor","set to zero"); elevation_motor.setCurrentPosition(0); delay(1000); display_message("Stop arrow on","left hand side"); while (touchRead(T0)> touch_threshold){ azimuth_motor.move(400); azimuth_motor.run(); } // button is pressed when azimuth pointer is forwards display_message("Azimuth motor","set to zero"); delay(2000); //pause to let you know button has been pressed azimuth_motor.setCurrentPosition(0); // move the elevation to point to Polaris // this angle is the same as the latitude of the observer, reference point downwards, so +90 elevation_motor.moveTo(steps_per_degree * (my_latitude + 90)); elevation_motor.runToPosition(); display_message("Point to Polaris","Then hit button"); while (touchRead(T0)> touch_threshold){}// wait for button press display_message("Now calibrated",""); delay(2000); //pause to let you know button has been pressed // ######################### end of calibration section ############################ // ######################### Connect to WiFi ####################################### Serial.println("ISS Tracker"); display_message("Searching for",ssid); Serial.println(ssid); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("Connected to wifi"); display_message("Connected to",ssid); // ######################### Synchronise Time with UTC ############################### // Uncomment the line below to see what it does behind the scenes setDebug(INFO); waitForSync(); // don't do anything until it has the correct time Serial.println("Current UTC time: " + UTC.dateTime()); my_timezone.setLocation(F("GB"));// <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"> https://en.wikipedia.org/wiki/List_of_tz_database...> delay(3000); Serial.print(F("Local Time: ")); Serial.println(my_timezone.dateTime()); } void loop() { events();// this ensures the motors keep moving //Serial.println("COOKIE: " + UTC.dateTime(COOKIE)); // Serial.println(dateTime(next_pass_timestamp, "D d-M H:i:s")); if (now()+next_duration > next_risetime){// only get next flyby once current one has gone by get_next_pass(my_latitude,my_longitude,next_risetime,next_duration); } Serial.print("Next flyby:"); //Serial.print(dateTime(next_risetime, "D d-M H:i:s ")); Serial.println(time_to_next_flyby(next_risetime)); point_to_ISS(); delay(1000); //display_message(UTC.dateTime("H:i:s T"),my_timezone.dateTime("H:i:s")); display_message(UTC.dateTime("H:i:s T"),time_to_next_flyby(next_risetime)); }
The Rotating Platform
3D print the following components:
- 004 Rotating Platform
- 004 Long Collar
- 004 Slip Ring Holder
- 004 Elevation Motor Housing
- 004 Elevation Driver Mount
The rotating platform needs to have a 60002RS bearing inserted into the centre. It is a tight fit, so a hot air gun can be used to heat up the metal body of the bearing and the centre of the rotating platform. A gentle swirling motion of the heat gun ensures that the area is not overheated as it will quickly deform if too much heat is applied. The bearing can then be located on top of the recess and pushed into place with a flat piece of wood. As the PLA cools down, it holds the side of the bearing securely.
At this point, you might want to run the involute azimuth gear around the inside to the rotating platform to ensure that the gear teeth mesh freely and don't get caught on any printing imperfections.
The 004 Elevation Driver Mount can be secured to the rotating platform using M3 x 12mm countersunk machine screws and captive nuts held inside the driver mount. The circuit board is attached with M3 x 10mm machine screws that screw into the tapped holes of the printed driver mount.
The elevation motor housing is also held with screws and captive nuts and the stepper motor will fit snugly inside the housing. You might find it helpful to use tweezers to locate the captive nuts in the right place.
The stepper motor wiring loom can be plugged into the drive. Be careful to keep the wires clear of the central spindle as this will be rotating.
Slip the spacer ring over the spindle and then the rotating platform assembly. The long collar can be placed over the spindle. The six wires of the slip ring should be passed through the slip ring holder and then threaded through the hollow spindle - this can be a bit tricky, but is easier todo it one wire at a time. The slip ring holder may now be screwed onto the spindle. This will be tightend down finger tight to check the rotating plate can move freely on the bearing. The rotating plate will not spin around quickly, but the components mounted on top of it have been placed on opposite sides so it can be as balanced as possible. When all the parts are in place, you can secure the slip ring to the holder with three self tapping screws.
Geodesic Dome
3D print the following:
- 005 Geodesic Dome
- 006 Pointer Body Bottom Half
- 006 Pointer Body Top Half
- 006 Pointer Front Cone
- 006 Pointer Locking Ring
- Geodesic Locking Ring Jig
- 000 Base Plate
Once all of the components are in place, it is time to finish off the final parts of the project by adding the geodesic dome. You can print out Geodesic Locking Ring Jig (red component in video) to locate on top of the rotating platform and then use this to position the locking screws at just the right height, so they provide a nice locking effect to the dome. The dome is secured by simply dropping it on top of the screws and twisting to lock it in place. Make sure you locate the side hole so it lines up with the elevation stepper motor shaft.
The pointer is made up of two halves, a conical arrow at the front and a locking ring at the back. When the dome is in place, you can simply slide the pointer onto the elevation stepper motor, screw on the base plate and it is ready to test.
Calibrating the Device
When the device is first switched on, it needs to know where the pointer is pointing. At the moment, this process relies on the operator. This is the pseudocode for the basic operation:
- Align zero reference point for elevation (towards Earth is zero)
- Align zero reference point for azimuth (on left hand side of device)
- Raise elevation to pole star, Polaris.
- User manually aligns the device accurately; it is now aligned with true North.
- Connect to the internet
- Get current time
- Get the time of the next pass
- Calculate time to next pass
- Repeat
- Get current ISS location
- Calculate the angles
- Point arrow to ISS
- Until False
I originally designed a geodesic dome with integrated button compass. As I placed the compass at the top, it seemed to be far enough away from the magnetic effects of the motors to show North, but in the end, I decided to rely on celestial navigation using the pole star. If Polaris is not visible in the sky due to cloudy conditions, it is probably not a good night to see the ISS fly over anyway.
Testing, Testing.......
In reality, a complex project like this needs to be tested at each stage before moving on to the next one or else you will have a nightmare trying to fault find, just when you thought you had completed the project.
There are many things to test on this project including the mechanical fit of the components, the electrical wiring, the control of the motors and display and the calculations in the software.
This is the reason I included a slot in the case, so I could attach a usb lead to the ESP32 and upload small code snippets to test each component as I added it on.
Reflections
Of course, there are always improvements that could be made with any project but I was really pleased with how this turned out. After the extensive testing I had carried out as I constructed the project, I was thrilled when the tracker located the ISS and followed its path across a clear sky on its first real-world test.
What Worked Well.....
- The self contained design which is easily portable
- Rechargeable batteries and charging circuits
- The use of a slip ring to allow a continuously rotating dome
- Precise mechanisms for moving the pointer (almost silent in operation)
- The mathematical calculations accurately located the ISS
- Easy to calibrate using the touch switch
- Clear readout on OLED display
Even Better If.......
- the device could sense its own geo-location (GPS module or IP lookup?)
- the device could automatically calibrate itself
- an indication was given when the next flyby was clearly visible (including weather outlook?)
- it had a larger display which would allow more data to be displayed to the user
- a buzzer was added to give an audible warning for the next flyby
- the user could choose which celestial objects to track
- the device could use TLE data to predict the next viewing