Arduino
ISS Monitor

ISS Monitor

Announced September 2020, the ISS has activated a 437.800 MHz cross band FM repeater. Using a uplink frequency of 145.990 MHz with an access tone [CTCSS] of 67 Hz and a downlink frequency of 437.800 MHz.

This is worth trying so need to know when the ISS was going overhead. Searching for prediction software and come across the Open Notify API which in turn calls NASA. Decided to write a routine to run on a ESP32 to show me the next couple of passes for my location exploiting said API. Of course could have used the resources at ARISS but where would the fun be in that.

I have been struggling with Strings. well seeing funny behavior with other code using Strings and blaming them so this was to be an exercise in Strings.

To keep accurate time using the NTP library and HTTPClient library to make the GET call. The API returns location data in JSON so ArduinoJSON to decode. An hour later and had operation code talking to the serial monitor. Another couple of hours and formatted results on a SSD1306.

Below is the functional code. I print the heap to watch for string issues. Yes should move the JSON decode to a function and pass a string, maybe tomorrow.

#include "config.h"
#include <string.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <HTTPClient.h>
#include <TimeLib.h>
#include <U8x8lib.h>
#include <ArduinoJson.h>

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP,ntpServer);
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(OLED_SCL, OLED_SDA, OLED_RST);

void setup() {

  Serial.begin(SERIAL_BAUD);
  
  u8x8.begin();
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.clear();
  u8x8.setCursor(0,0);
  u8x8.print(cBanner);
  Serial.println(cBanner);

  // WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { //Check for the connection
    Serial.println(F("Connecting WiFi"));
    delay(1000);
  }
  u8x8.setCursor(0,1);
  u8x8.print("WiFi Ok");
  Serial.println(F("Connected to the WiFi network"));

  // Get and show Time
  timeClient.begin();
  // timeClient.setTimeOffset(36000);
  if (!timeClient.update()) {
    timeClient.forceUpdate();
    Serial.println("Couldn't find NTP");
    Serial.flush();
  }

  if (TRUE) {
  
    //ESP32 Chip ID is essentially its MAC address(length: 6 bytes).
    uint64_t chipid;  
    chipid=ESP.getEfuseMac();
    Serial.printf("ESP32 Chip ID - %04X",(uint16_t)(chipid>>32));//print High 2 bytes
    Serial.printf("%08X\n",(uint32_t)chipid);//print Low 4bytes.

    Serial.print(F("Hall - "));
    Serial.println(hallRead());

    //Memory Status
    Serial.print(F("Free Heap - "));
    Serial.println(ESP.getFreeHeap());    
  } 
}

void loop() {
  // put your main code here, to run repeatedly:

    char buff[128];
    unsigned long t;
    unsigned long d;
                
    u8x8.clearLine(1);
    u8x8.setCursor(0,1);
    t = timeClient.getEpochTime();
    sprintf(buff, "%02d/%02d %02d:%02d", day(t), month(t), hour(t), minute(t));
    Serial.println(buff);
    u8x8.print(buff);
    
    if (WiFi.status() == WL_CONNECTED) { //Check WiFi connection status
  
      HTTPClient http;
      http.begin("http://api.open-notify.org/iss-now.json");  
      http.setTimeout(20000);  
      int httpCode = http.GET();
      if (httpCode > 0) {
        String JSONData = http.getString();
        Serial.println(JSONData);
        
        // Decode the JSON
        StaticJsonDocument<256> doc;
        const char* latitude;
        const char* longitude;
        DeserializationError err = deserializeJson(doc,JSONData);
        switch (err.code()) {
          case DeserializationError::Ok:
            latitude = doc["iss_position"]["latitude"];
            longitude = doc["iss_position"]["longitude"];
            Serial.println(latitude);
            Serial.println(longitude);

            u8x8.clearLine(3);
            u8x8.setCursor(0,3);
            u8x8.print(F("Lat "));
            u8x8.setCursor(6,3);
            u8x8.print(latitude);

            u8x8.clearLine(4);
            u8x8.setCursor(0,4); 
            u8x8.print(F("Lon "));
            u8x8.setCursor(6,4);
            u8x8.print(longitude);
       
            break;
          case DeserializationError::InvalidInput:
            Serial.println(F("Invalid input!"));
            u8x8.print(F("Invalid input!"));
            break;
          case DeserializationError::NoMemory:
            Serial.println(F("Not enough memory"));
            u8x8.print(F("Memory Err"));
            break;
          default:
            Serial.println(F("Deserialization failed"));
            u8x8.print(F("JSON Err"));
            break;
        }      
      } else {
        Serial.print(F("Error on sending GET: Return - "));
        String payload = http.getString();
        Serial.println(httpCode);
        Serial.println(payload);
      }

      String address = "http://api.open-notify.org/iss-pass.json?lat=" + myLat + "&lon=" + myLong + "&alt=" + myHeight + "&n=3";
      Serial.println(address);
      http.begin(address); 
      http.setTimeout(20000);  
      httpCode = http.GET();
      if (httpCode > 0) {
        String JSONData = http.getString();
        Serial.println(JSONData);
        // Decode the JSON
        StaticJsonDocument<2048> doc;
        DeserializationError err = deserializeJson(doc,JSONData);
        switch (err.code()) {
          case DeserializationError::Ok:
            for (int i = 0; i <= 2 ; i++) {
              t = doc["response"][i]["risetime"];
              d = doc["response"][i]["duration"];
              Serial.println(t);
              sprintf(buff, "%02d/%02d %02d:%02d", day(t), month(t), hour(t), minute(t));
              Serial.println(buff);
              u8x8.clearLine(5+i);
              u8x8.setCursor(0,5+i);
              u8x8.print(buff);
              u8x8.setCursor(12,5+i);
              u8x8.print(d/60);
            }
            
            break;
          case DeserializationError::InvalidInput:
            Serial.println(F("Invalid input!"));
            u8x8.print(F("Invalid input!"));
            break;
          case DeserializationError::NoMemory:
            Serial.println(F("Not enough memory"));
            u8x8.print(F("Memory Err"));
            break;
          default:
            Serial.println(F("Deserialization failed"));
            u8x8.print(F("JSON Err"));
            break;
        }      
      }
  
      http.end();  //Free resources
    }else{
      u8x8.clearLine(1);
      u8x8.setCursor(0,1);
      u8x8.print("WiFi Error");
    }

    u8x8.clearLine(2);
    u8x8.setCursor(0,2);
    u8x8.print(F("Heap"));
    u8x8.setCursor(6,2);
    u8x8.print(ESP.getFreeHeap());

    delay(1000*60*.8);  
}

and supporting config.h where your WIFI and location details are stored;

#define SERIAL_BAUD 115200
const bool TRUE = 1;
const bool FALSE = 0;

const char* ssid = "WIFISSID";
const char* password =  "WIFIPASS";
const char* ntpServer = "192.168.1.1";

const String myLat = "-37.6910";
const String myLong = "144.0100";
const String myHeight  = "420";

const char* cBanner = "ISS Monitor v1\0";

#define bESPDev32 0           // Device ID 3
#define bFireBeetle32 1       // Device ID 4

#if (bESPDev32)
  
  // OLED pins
  #define OLED_SDA 4
  #define OLED_SCL 15 
  #define OLED_RST 16
  #define SCREEN_WIDTH 128 // OLED display width, in pixels
  #define SCREEN_HEIGHT 64 // OLED display height, in pixels
 
#elif (bFireBeetle32)

  //OLED pins
  #define OLED_BTNA   27    // D4         May have to move LoRa CS to D9 
  #define OLED_BTNB   5     // D8
  #define OLED_SWITCH 36    // A0
  #define OLED_SDA 21       // GPIO4
  #define OLED_SCL 22       // GPIO15 
  #define OLED_RST -1       // GPIO16
  #define SCREEN_WIDTH 128 // OLED display width, in pixels
  #define SCREEN_HEIGHT 64 // OLED display height, in pixels

#else
  // error Unsupported board selection.
  #error "Unsupported board selection"
#endif

So armed with pass predictions it was time to program the frequencies into the HT. I have worked the APRS repeater on ISS previously so its not unreasonable to work the ISS FM repeater from a HT. Have to do more than cross your fingers. Below are frequencies I used program my device to allow for Doppler Shift. Check AH6RH blog for more details and recommended setups.

Anyway, the HT is programmed and charged and sitting next to me so lets see how it goes…