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 }