acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

APRS.cpp (7118B)

      1 #include "APRS.h"
      2 
      3 APRSClient::APRSClient(AX25Client* ax) {
      4   _ax = ax;
      5 }
      6 
      7 int16_t APRSClient::begin(char symbol, bool alt) {
      8   RADIOLIB_CHECK_RANGE(symbol, ' ', '}', RADIOLIB_ERR_INVALID_SYMBOL);
      9   _symbol = symbol;
     10 
     11   if(alt) {
     12     _table = '\\';
     13   } else {
     14     _table = '/';
     15   }
     16 
     17   return(RADIOLIB_ERR_NONE);
     18 }
     19 
     20 int16_t APRSClient::sendPosition(char* destCallsign, uint8_t destSSID, char* lat, char* lon, char* msg, char* time) {
     21   #if !defined(RADIOLIB_STATIC_ONLY)
     22     size_t len = 1 + strlen(lat) + 1 + strlen(lon);
     23     if(msg != NULL) {
     24       len += 1 + strlen(msg);
     25     }
     26     if(time != NULL) {
     27       len += strlen(time);
     28     }
     29     char* info = new char[len];
     30   #else
     31     char info[RADIOLIB_STATIC_ARRAY_SIZE];
     32   #endif
     33 
     34   // build the info field
     35   if((msg == NULL) && (time == NULL)) {
     36     // no message, no timestamp
     37     sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_NO_MSG "%s%c%s%c", lat, _table, lon, _symbol);
     38   } else if((msg != NULL) && (time == NULL)) {
     39     // message, no timestamp
     40     sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_MSG "%s%c%s%c%s", lat, _table, lon, _symbol, msg);
     41   } else if((msg == NULL) && (time != NULL)) {
     42     // timestamp, no message
     43     sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_NO_MSG "%s%s%c%s%c", time, lat, _table, lon, _symbol);
     44   } else {
     45     // timestamp and message
     46     sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_MSG "%s%s%c%s%c%s", time, lat, _table, lon, _symbol, msg);
     47   }
     48 
     49   // send the frame
     50   int16_t state = sendFrame(destCallsign, destSSID, info);
     51   #if !defined(RADIOLIB_STATIC_ONLY)
     52     delete[] info;
     53   #endif
     54   return(state);
     55 }
     56 
     57 int16_t APRSClient::sendMicE(float lat, float lon, uint16_t heading, uint16_t speed, uint8_t type, uint8_t* telem, size_t telemLen, char* grid, char* status, int32_t alt) {
     58   // sanity checks first
     59   if(((telemLen == 0) && (telem != NULL)) || ((telemLen != 0) && (telem == NULL))) {
     60     return(RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY);
     61   }
     62 
     63   if((telemLen != 0) && (telemLen != 2) && (telemLen != 5)) {
     64     return(RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY_LENGTH);
     65   }
     66 
     67   if((telemLen > 0) && ((grid != NULL) || (status != NULL) || (alt != RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED))) {
     68     // can't have both telemetry and status
     69     return(RADIOLIB_ERR_MIC_E_TELEMETRY_STATUS);
     70   }
     71 
     72   // prepare buffers
     73   char destCallsign[7];
     74   #if !defined(RADIOLIB_STATIC_ONLY)
     75     size_t infoLen = 10;
     76     if(telemLen > 0) {
     77       infoLen += 1 + telemLen;
     78     } else {
     79       if(grid != NULL) {
     80         infoLen += strlen(grid) + 2;
     81       }
     82       if(status != NULL) {
     83         infoLen += strlen(status);
     84       }
     85       if(alt > RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED) {
     86         infoLen += 4;
     87       }
     88     }
     89     char* info = new char[infoLen];
     90   #else
     91     char info[RADIOLIB_STATIC_ARRAY_SIZE];
     92   #endif
     93   size_t infoPos = 0;
     94 
     95   // the following is based on APRS Mic-E implementation by https://github.com/omegat
     96   // as discussed in https://github.com/jgromes/RadioLib/issues/430
     97 
     98   // latitude first, because that is in the destination field
     99   float lat_abs = abs(lat);
    100   int lat_deg = (int)lat_abs;
    101   int lat_min = (lat_abs - (float)lat_deg) * 60.0f;
    102   int lat_hun = (((lat_abs - (float)lat_deg) * 60.0f) - lat_min) * 100.0f;
    103   destCallsign[0] = lat_deg/10;
    104   destCallsign[1] = lat_deg%10;
    105   destCallsign[2] = lat_min/10;
    106   destCallsign[3] = lat_min%10;
    107   destCallsign[4] = lat_hun/10;
    108   destCallsign[5] = lat_hun%10;
    109 
    110   // next, add the extra bits
    111   if(type & 0x04) { destCallsign[0] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; }
    112   if(type & 0x02) { destCallsign[1] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; }
    113   if(type & 0x01) { destCallsign[2] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; }
    114   if(lat >= 0) { destCallsign[3] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; }
    115   if(lon >= 100 || lon <= -100) { destCallsign[4] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; }
    116   if(lon < 0) { destCallsign[5] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; }
    117   destCallsign[6] = '\0';
    118 
    119   // now convert to Mic-E characters to get the "callsign"
    120   for(uint8_t i = 0; i < 6; i++) {
    121     if(destCallsign[i] <= 9) {
    122       destCallsign[i] += '0';
    123     } else {
    124       destCallsign[i] += ('A' - 10);
    125     }
    126   }
    127 
    128   // setup the information field
    129   info[infoPos++] = RADIOLIB_APRS_MIC_E_GPS_DATA_CURRENT;
    130 
    131   // encode the longtitude
    132   float lon_abs = abs(lon);
    133   int32_t lon_deg = (int32_t)lon_abs;
    134   int32_t lon_min = (lon_abs - (float)lon_deg) * 60.0f;
    135   int32_t lon_hun = (((lon_abs - (float)lon_deg) * 60.0f) - lon_min) * 100.0f;
    136 
    137   if(lon_deg <= 9) {
    138     info[infoPos++] = lon_deg + 118;
    139   } else if(lon_deg <= 99) {
    140     info[infoPos++] = lon_deg + 28;
    141   } else if(lon_deg <= 109) {
    142     info[infoPos++] = lon_deg + 8;
    143   } else {
    144     info[infoPos++] = lon_deg - 72;
    145   }
    146 
    147   if(lon_min <= 9){
    148     info[infoPos++] = lon_min + 88;
    149   } else {
    150     info[infoPos++] = lon_min + 28;
    151   }
    152 
    153   info[infoPos++] = lon_hun + 28;
    154 
    155   // now the speed and heading - this gets really weird
    156   int32_t speed_hun_ten = speed/10;
    157   int32_t speed_uni = speed%10;
    158   int32_t head_hun = heading/100;
    159   int32_t head_ten_uni = heading%100;
    160 
    161   if(speed <= 199) {
    162     info[infoPos++] = speed_hun_ten + 'l';
    163   } else {
    164     info[infoPos++] = speed_hun_ten + '0';
    165   }
    166 
    167   info[infoPos++] = speed_uni*10 + head_hun + 32;
    168   info[infoPos++] = head_ten_uni + 28;
    169   info[infoPos++] = _symbol;
    170   info[infoPos++] = _table;
    171 
    172   // onto the optional stuff - check telemetry first
    173   if(telemLen > 0) {
    174     if(telemLen == 2) {
    175       info[infoPos++] = RADIOLIB_APRS_MIC_E_TELEMETRY_LEN_2;
    176     } else {
    177       info[infoPos++] = RADIOLIB_APRS_MIC_E_TELEMETRY_LEN_5;
    178     }
    179     for(uint8_t i = 0; i < telemLen; i++) {
    180       sprintf(&(info[infoPos]), "%02X", telem[i]);
    181       infoPos += 2;
    182     }
    183 
    184   } else {
    185     if(grid != NULL) {
    186       memcpy(&(info[infoPos]), grid, strlen(grid));
    187       infoPos += strlen(grid);
    188       info[infoPos++] = '/';
    189       info[infoPos++] = 'G';
    190     }
    191     if(status != NULL) {
    192       info[infoPos++] = ' ';
    193       memcpy(&(info[infoPos]), status, strlen(status));
    194       infoPos += strlen(status);
    195     }
    196     if(alt > RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED) {
    197       // altitude is offset by -10 km
    198       int32_t alt_val = alt + 10000;
    199 
    200       // ... and encoded in base 91 for some reason
    201       info[infoPos++] = (alt_val / 8281) + 33;
    202       info[infoPos++] = ((alt_val % 8281) / 91) + 33;
    203       info[infoPos++] = ((alt_val % 8281) % 91) + 33;
    204       info[infoPos++] = '}';
    205     }
    206   }
    207   info[infoPos++] = '\0';
    208 
    209   // send the frame
    210   int16_t state = sendFrame(destCallsign, 0, info);
    211   #if !defined(RADIOLIB_STATIC_ONLY)
    212     delete[] info;
    213   #endif
    214   return(state);
    215 }
    216 
    217 int16_t APRSClient::sendFrame(char* destCallsign, uint8_t destSSID, char* info) {
    218   // get AX.25 callsign
    219   char srcCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1];
    220   _ax->getCallsign(srcCallsign);
    221 
    222   AX25Frame frameUI(destCallsign, destSSID, srcCallsign, _ax->getSSID(), RADIOLIB_AX25_CONTROL_U_UNNUMBERED_INFORMATION |
    223                     RADIOLIB_AX25_CONTROL_POLL_FINAL_DISABLED | RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME,
    224                     RADIOLIB_AX25_PID_NO_LAYER_3, (const char*)info);
    225 
    226   return(_ax->sendFrame(&frameUI));
    227 }