VAw4HBTsJIe15camAdLyxmr6Gko2NgDKdvrlkFP2

Kodingan Simple APRS Tracker ESP32 by YD2CLX

 <pre><code>

#include <math.h>
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <TinyGPS++.h>

// Pin definitions
#define OUT_PIN 2      // AFSK output
#define LED_PIN 5      // PTT/LED transmit
#define ONBOARD_LED 2  // LED built-in ESP32 Dev Module

// Tone constants
#define _1200 1
#define _2200 0

#define _FLAG       0x7e
#define _CTRL_ID    0x03
#define _PID        0xf0
#define _DT_EXP     ','
#define _DT_STATUS  '>'
#define _DT_POS     '!'

#define _NORMAL     1
#define _BEACON     2
#define _FIXPOS     3
#define _STATUS     2
#define _FIXPOS_STATUS  1

bool nada = _2200;

const float baud_adj = 0.975f;
const float adj_1200 = 1.0f * baud_adj;
const float adj_2200 = 1.0f * baud_adj;
unsigned int tc1200 = (unsigned int)(0.5f * adj_1200 * 1000000.0f / 1200.0f);
unsigned int tc2200 = (unsigned int)(0.5f * adj_2200 * 1000000.0f / 2200.0f);

// Config variables
char mycall[10] = "YD2XXX"; //calsign anda
uint8_t myssid = 7;
char mystatus[100] = "Tracker Test Over ESP32 - Havid";
bool smartBeaconing = false;
unsigned long tx_interval = 60000UL;  // ms

const char *dest = "APRS";
const char *dest_beacon = "BEACON";

// === PATH DIGI YANG DIPERBAIKI (untuk terdigi MAKSIMAL) ===
char digi1[6] = "WIDE1";      // Local digipeater
uint8_t digissid1 = 1;
char digi2[6] = "WIDE2";      // Wide-area digipeater
uint8_t digissid2 = 2;
// ========================================================

char lat[9] = "0000.00N";
char lon[10] = "00000.00E";
const char sym_ovl = '/';
const char sym_tab = 'k';

char bit_stuff = 0;
unsigned short crc = 0xffff;

TinyGPSPlus gps;
HardwareSerial gpsSerial(1);

bool gps_valid = false;
bool gps_locked = false;

double last_lat = 0.0;
double last_lon = 0.0;
float last_speed = 0.0;

unsigned long last_tx_time = 0;

WebServer server(80);

char dummy_lat[9] = "0659.00S";
char dummy_lon[10] = "10649.00E";

Preferences prefs;  // Preferences object

// Additional for GPS status
String gps_connection_status = "Not Connected";
unsigned long last_gps_data_time = 0;
unsigned long last_status_print = 0;

void set_nada_1200() {
  digitalWrite(OUT_PIN, HIGH);
  delayMicroseconds(tc1200);
  digitalWrite(OUT_PIN, LOW);
  delayMicroseconds(tc1200);
}

void set_nada_2200() {
  digitalWrite(OUT_PIN, HIGH);
  delayMicroseconds(tc2200);
  digitalWrite(OUT_PIN, LOW);
  delayMicroseconds(tc2200);
  digitalWrite(OUT_PIN, HIGH);
  delayMicroseconds(tc2200);
  digitalWrite(OUT_PIN, LOW);
  delayMicroseconds(tc2200);
}

void set_nada(bool n) {
  if (n) set_nada_1200();
  else set_nada_2200();
}

void calc_crc(bool in_bit) {
  unsigned short xor_in = crc ^ in_bit;
  crc >>= 1;
  if (xor_in & 0x01) crc ^= 0x8408;
}

void send_crc() {
  unsigned char crc_lo = crc ^ 0xff;
  unsigned char crc_hi = (crc >> 8) ^ 0xff;
  send_char_NRZI(crc_lo, HIGH);
  send_char_NRZI(crc_hi, HIGH);
}

void send_header(char msg_type) {
  char temp;
  const char *dest_str = (msg_type == _NORMAL) ? dest : dest_beacon;
  temp = strlen(dest_str);
  for (int j = 0; j < temp; j++) send_char_NRZI(dest_str[j] << 1, HIGH);
  for (int j = temp; j < 6; j++) send_char_NRZI(' ' << 1, HIGH);
  send_char_NRZI('0' << 1, HIGH);

  temp = strlen(mycall);
  for (int j = 0; j < temp; j++) send_char_NRZI(mycall[j] << 1, HIGH);
  for (int j = temp; j < 6; j++) send_char_NRZI(' ' << 1, HIGH);
  send_char_NRZI((myssid + '0') << 1, HIGH);

  // === PATH DIGI 2 FIELD (WIDE1-1,WIDE2-2) - HANYA BAGIAN INI YANG DIPERBAIKI ===
  // Digi 1 (WIDE1-1) - extension bit = 0
  temp = strlen(digi1);
  for (int j = 0; j < temp; j++) send_char_NRZI(digi1[j] << 1, HIGH);
  for (int j = temp; j < 6; j++) send_char_NRZI(' ' << 1, HIGH);
  send_char_NRZI((digissid1 + '0') << 1, HIGH);

  // Digi 2 (WIDE2-2) - extension bit = 1 (last address)
  temp = strlen(digi2);
  for (int j = 0; j < temp; j++) send_char_NRZI(digi2[j] << 1, HIGH);
  for (int j = temp; j < 6; j++) send_char_NRZI(' ' << 1, HIGH);
  send_char_NRZI(((digissid2 + '0') << 1) + 1, HIGH);
  // =============================================================================

  send_char_NRZI(_CTRL_ID, HIGH);
  send_char_NRZI(_PID, HIGH);
}

void send_payload(char type, bool use_dummy = false) {
  const char *use_lat = use_dummy ? dummy_lat : lat;
  const char *use_lon = use_dummy ? dummy_lon : lon;

  if (type == _FIXPOS) {
    send_char_NRZI(_DT_POS, HIGH);
    send_string_len(use_lat, strlen(use_lat));
    send_char_NRZI(sym_ovl, HIGH);
    send_string_len(use_lon, strlen(use_lon));
    send_char_NRZI(sym_tab, HIGH);
  } else if (type == _STATUS) {
    send_char_NRZI(_DT_STATUS, HIGH);
    send_string_len(mystatus, strlen(mystatus));
  } else if (type == _FIXPOS_STATUS) {
    send_char_NRZI(_DT_POS, HIGH);
    send_string_len(use_lat, strlen(use_lat));
    send_char_NRZI(sym_ovl, HIGH);
    send_string_len(use_lon, strlen(use_lon));
    send_char_NRZI(sym_tab, HIGH);
    send_char_NRZI(' ', HIGH);
    send_string_len(mystatus, strlen(mystatus));
  }
}

void send_char_NRZI(unsigned char in_byte, bool enBitStuff) {
  bool bits;
  for (int i = 0; i < 8; i++) {
    bits = in_byte & 0x01;
    calc_crc(bits);
    if (bits) {
      set_nada(nada);
      bit_stuff++;
      if (enBitStuff && bit_stuff == 5) {
        nada ^= 1;
        set_nada(nada);
        bit_stuff = 0;
      }
    } else {
      nada ^= 1;
      set_nada(nada);
      bit_stuff = 0;
    }
    in_byte >>= 1;
  }
}

void send_string_len(const char *in_string, int len) {
  for (int j = 0; j < len; j++) send_char_NRZI(in_string[j], HIGH);
}

void send_flag(unsigned char flag_len) {
  for (int j = 0; j < flag_len; j++) send_char_NRZI(_FLAG, LOW);
}

void send_packet(char packet_type, char dest_type, bool use_dummy = false) {
  print_debug(packet_type, dest_type, use_dummy);

  digitalWrite(LED_PIN, HIGH);

  send_flag(100);
  crc = 0xffff;
  send_header(dest_type);
  send_payload(packet_type, use_dummy);
  send_crc();
  send_flag(3);

  digitalWrite(LED_PIN, LOW);
}

void print_debug(char type, char dest_type, bool use_dummy) {
  Serial.print(mycall);
  Serial.print('-');
  Serial.print(myssid, DEC);
  Serial.print('>');
  Serial.print((dest_type == _NORMAL) ? dest : dest_beacon);
  Serial.print(',');

  // === DEBUG PATH DITAMPILKAN BENAR (WIDE1-1,WIDE2-2) ===
  Serial.print(digi1);
  Serial.print('-');
  Serial.print(digissid1, DEC);
  Serial.print(',');
  Serial.print(digi2);
  Serial.print('-');
  Serial.print(digissid2, DEC);
  Serial.print(':');
  // ====================================================

  const char *use_lat = use_dummy ? dummy_lat : lat;
  const char *use_lon = use_dummy ? dummy_lon : lon;

  if (type == _FIXPOS) {
    Serial.print(_DT_POS);
    Serial.print(use_lat);
    Serial.print(sym_ovl);
    Serial.print(use_lon);
    Serial.print(sym_tab);
  } else if (type == _STATUS) {
    Serial.print(_DT_STATUS);
    Serial.print(mystatus);
  } else if (type == _FIXPOS_STATUS) {
    Serial.print(_DT_POS);
    Serial.print(use_lat);
    Serial.print(sym_ovl);
    Serial.print(use_lon);
    Serial.print(sym_tab);
    Serial.print(' ');
    Serial.print(mystatus);
  }
  Serial.println(' ');
}

void updateCoordinates() {
  char lat_str[9];
  char lon_str[10];

  double lat_val = gps.location.lat();
  char lat_dir = (lat_val < 0) ? 'S' : 'N';
  lat_val = fabs(lat_val);
  int lat_deg = (int)lat_val;
  double lat_min = (lat_val - lat_deg) * 60.0;
  sprintf(lat_str, "%02d%05.2f%c", lat_deg, lat_min, lat_dir);

  double lon_val = gps.location.lng();
  char lon_dir = (lon_val < 0) ? 'W' : 'E';
  lon_val = fabs(lon_val);
  int lon_deg = (int)lon_val;
  double lon_min = (lon_val - lon_deg) * 60.0;
  sprintf(lon_str, "%03d%05.2f%c", lon_deg, lon_min, lon_dir);

  strncpy(lat, lat_str, 8);
  lat[8] = '\0';
  strncpy(lon, lon_str, 9);
  lon[9] = '\0';

  Serial.print("Latitude: ");
  Serial.println(lat);
  Serial.print("Longitude: ");
  Serial.println(lon);
}

String getGPSStatus() {
  if (gps_locked) {
    return "Locked - Ready to Transmit";
  } else {
    return "Not Locked - Not Ready";
  }
}

String getRootHtml() {
  String gpsColor = gps_locked ? "green" : "red";
  String connColor = (gps_connection_status == "Locked") ? "green" : ((gps_connection_status == "Searching") ? "orange" : "red");
  String html = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>APRS Tracker By YD2CLX</title>
<style>
  body {font-family: Arial, sans-serif; background-color: #f0f4f8; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; color: #333;}
  .container {background-color: #fff; padding: 30px; border-radius: 15px; box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); max-width: 450px; width: 100%;}
  h1 {text-align: center; color: #007bff; margin-bottom: 25px; font-size: 26px;}
  label {display: block; margin-bottom: 10px; font-weight: bold; color: #555;}
  input[type="text"], input[type="number"], select {width: 100%; padding: 12px; margin-bottom: 20px; border: 1px solid #ddd; border-radius: 8px; box-sizing: border-box; font-size: 16px;}
  input[type="checkbox"] {margin-right: 10px;}
  .checkbox-label {display: flex; align-items: center; margin-bottom: 20px; font-size: 16px;}
  button {width: 100%; padding: 14px; background-color: #28a745; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; margin-top: 10px; transition: background-color 0.3s;}
  button:hover {background-color: #218838;}
  .status {margin-top: 25px; padding: 15px; background-color: #e9ecef; border-radius: 8px; text-align: center;}
  .status p {margin: 8px 0; font-size: 16px;}
  #transmitBtn {background-color: #007bff;}
  #transmitBtn:hover {background-color: #0056b3;}
  #dummyTransmitBtn {background-color: #ffc107;}
  #dummyTransmitBtn:hover {background-color: #e0a800;}
  #gpsStatus {color: )rawliteral" + gpsColor + R"rawliteral(;}
  #gpsConnStatus {color: )rawliteral" + connColor + R"rawliteral(;}
  @media (max-width: 600px) {.container {padding: 20px;} h1 {font-size: 22px;}}
</style>
<script>
function manualTransmit() {
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/transmit", true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
      alert(xhr.responseText);
    }
  };
  xhr.send();
}
function dummyTransmit() {
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/dummy_transmit", true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
      alert(xhr.responseText);
    }
  };
  xhr.send();
}

let refreshTimer;
function startRefresh() {
  refreshTimer = setInterval(() => {
    if (!document.activeElement ||
        (document.activeElement.tagName !== 'INPUT' &&
         document.activeElement.tagName !== 'TEXTAREA' &&
         document.activeElement.tagName !== 'SELECT')) {
      location.reload();
    }
  }, 5000);
}

document.addEventListener('DOMContentLoaded', () => {
  const smartBeacon = document.getElementById('smartbeaconing');
  const intervalSelect = document.getElementById('interval');
  smartBeacon.addEventListener('change', () => {
    intervalSelect.disabled = smartBeacon.checked;
  });

  startRefresh();

  const inputs = document.querySelectorAll('input, select');
  inputs.forEach(input => {
    input.addEventListener('focus', () => { clearInterval(refreshTimer); });
    input.addEventListener('blur', () => { startRefresh(); });
  });
});
</script>
</head>
<body>
  <div class="container">
    <h1>APRS Tracker Configuration</h1>
    <form action='/save' method='POST'>
      <label for="callsign">Callsign:</label>
      <input type="text" id="callsign" name="callsign" value=")rawliteral" + String(mycall) + R"rawliteral(" maxlength="9">
      <label for="ssid">SSID:</label>
      <input type="number" id="ssid" name="ssid" value=")rawliteral" + String(myssid) + R"rawliteral(" min="0" max="15">
      <label for="comment">APRS Status/Comment:</label>
      <input type="text" id="comment" name="comment" value=")rawliteral" + String(mystatus) + R"rawliteral(" maxlength="99">
      <div class="checkbox-label">
        <input type="checkbox" id="smartbeaconing" name="smartbeaconing" )rawliteral" + (smartBeaconing ? "checked" : "") + R"rawliteral(>
        <label for="smartbeaconing" style="display: inline; font-weight: normal;">Enable Smart Beaconing</label>
      </div>
      <label for="interval">Transmit Interval (minutes):</label>
      <select id="interval" name="interval" )rawliteral" + (smartBeaconing ? "disabled" : "") + R"rawliteral(>
        <option value="1" )rawliteral" + (tx_interval == 60000 ? "selected" : "") + R"rawliteral(>1 Minute</option>
        <option value="2" )rawliteral" + (tx_interval == 120000 ? "selected" : "") + R"rawliteral(>2 Minutes</option>
        <option value="5" )rawliteral" + (tx_interval == 300000 ? "selected" : "") + R"rawliteral(>5 Minutes</option>
        <option value="10" )rawliteral" + (tx_interval == 600000 ? "selected" : "") + R"rawliteral(>10 Minutes</option>
      </select>
      <button type="submit">Save Settings</button>
    </form>
    <button id="transmitBtn" onclick="manualTransmit()">Manual Transmit</button>
    <button id="dummyTransmitBtn" onclick="dummyTransmit()">TX Dummy Data</button>
    <div class="status">
      <p>Latitude: <span>)rawliteral" + String(lat) + R"rawliteral(</span></p>
      <p>Longitude: <span>)rawliteral" + String(lon) + R"rawliteral(</span></p>
      <p>GPS Connection: <span id="gpsConnStatus">)rawliteral" + gps_connection_status + R"rawliteral(</span></p>
      <p>GPS Status: <span id="gpsStatus">)rawliteral" + getGPSStatus() + R"rawliteral(</span></p>
    </div>
  </div>
</body>
</html>
)rawliteral";
  return html;
}

void handleRoot() {
  server.send(200, "text/html", getRootHtml());
}

void handleSave() {
  if (server.hasArg("callsign")) {
    String s = server.arg("callsign");
    if (s.length() <= 9) {
      s.toCharArray(mycall, 10);
      mycall[9] = '\0';
      Serial.print("New callsign: "); Serial.println(mycall);
    }
  }
  if (server.hasArg("ssid")) {
    myssid = server.arg("ssid").toInt();
    Serial.print("New SSID: "); Serial.println(myssid);
  }
  if (server.hasArg("comment")) {
    String s = server.arg("comment");
    if (s.length() <= 99) {
      s.toCharArray(mystatus, 100);
      mystatus[99] = '\0';
      Serial.print("New status: "); Serial.println(mystatus);
    }
  }
  smartBeaconing = server.hasArg("smartbeaconing");
  Serial.print("Smart Beaconing: "); Serial.println(smartBeaconing ? "ON" : "OFF");

  if (server.hasArg("interval")) {
    int min = server.arg("interval").toInt();
    tx_interval = (unsigned long)min * 60000UL;
    Serial.print("New interval (ms): "); Serial.println(tx_interval);
  }

  prefs.begin("aprs_config", false);
  prefs.putString("callsign", mycall);
  prefs.putUChar("ssid", myssid);
  prefs.putString("status", mystatus);
  prefs.putBool("smartbeac", smartBeaconing);
  prefs.putULong("interval", tx_interval);
  prefs.end();

  Serial.println("Settings SAVED to Preferences!");

  server.sendHeader("Location", "/");
  server.send(303);
}

void handleTransmit() {
  if (gps_valid) {
    send_packet(_FIXPOS_STATUS, _NORMAL);
    server.send(200, "text/plain", "Packet transmitted");
  } else {
    server.send(400, "text/plain", "No valid GPS data");
  }
}

void handleDummyTransmit() {
  send_packet(_FIXPOS_STATUS, _NORMAL, true);
  server.send(200, "text/plain", "Dummy packet transmitted");
}

void setup() {
  pinMode(ONBOARD_LED, OUTPUT);
  pinMode(OUT_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  Serial.begin(115200);
  delay(500);
  Serial.println("\nAPRS Tracker ESP32 - Starting... (Path: WIDE1-1,WIDE2-2)");

  gpsSerial.begin(9600, SERIAL_8N1, 16, 17);

  prefs.begin("aprs_config", false);
  String call = prefs.getString("callsign", "YD2CLX");
  call.toCharArray(mycall, 10);
  myssid = prefs.getUChar("ssid", 7);
  String stat = prefs.getString("status", "Tracker Test Over ESP32 - Havid");
  stat.toCharArray(mystatus, 100);
  smartBeaconing = prefs.getBool("smartbeac", false);
  tx_interval = prefs.getULong("interval", 60000UL);
  prefs.end();

  Serial.print("Loaded callsign: "); Serial.println(mycall);
  Serial.print("Loaded SSID: "); Serial.println(myssid);
  Serial.print("Loaded status: "); Serial.println(mystatus);
  Serial.print("Loaded smartBeaconing: "); Serial.println(smartBeaconing);
  Serial.print("Loaded tx_interval: "); Serial.println(tx_interval);

  WiFi.softAP("APRS-Tracker", "12345678");
  Serial.print("AP IP: "); Serial.println(WiFi.softAPIP());

  server.on("/", HTTP_GET, handleRoot);
  server.on("/save", HTTP_POST, handleSave);
  server.on("/transmit", HTTP_POST, handleTransmit);
  server.on("/dummy_transmit", HTTP_POST, handleDummyTransmit);

  server.begin();
  Serial.println("Web server started");
}

void loop() {
  server.handleClient();

  bool data_received = false;
  while (gpsSerial.available() > 0) {
    data_received = true;
    gps.encode(gpsSerial.read());
  }

  if (data_received) {
    last_gps_data_time = millis();
  }

  unsigned long now = millis();
  if (now - last_gps_data_time > 2000) {
    gps_connection_status = "Not Connected";
  } else if (!gps_locked) {
    gps_connection_status = "Searching";
  } else {
    gps_connection_status = "Locked";
  }

  if (gps.location.isUpdated()) {
    updateCoordinates();
  }

  gps_locked = gps.location.isValid() && gps.satellites.value() >= 3;
  gps_valid = gps_locked;

  unsigned long effective_interval = tx_interval;
  if (smartBeaconing && gps_valid) {
    last_speed = gps.speed.kmph();
    if (last_speed > 50) effective_interval /= 4;
    else if (last_speed > 10) effective_interval /= 2;
    if (effective_interval < 30000) effective_interval = 30000;
  }

  if (gps_valid && (now - last_tx_time >= effective_interval)) {
    send_packet(_FIXPOS_STATUS, _NORMAL);
    last_tx_time = now;
  }

  if (now - last_status_print >= 5000) {
    Serial.print("GPS Connection Status: "); Serial.println(gps_connection_status);
    Serial.print("GPS Lock Status: "); Serial.println(gps_locked ? "Locked" : "Not Locked");
    Serial.print("Satellites: "); Serial.println(gps.satellites.value());
    last_status_print = now;
  }
}
</code></pre>

Post a Comment