[APRS] Added APRS support
This commit is contained in:
parent
3eb831d7e1
commit
df466486aa
7 changed files with 306 additions and 1 deletions
|
@ -35,6 +35,8 @@ SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x
|
|||
SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, RFM2x and Si443x
|
||||
* [__Hellschreiber__](https://www.sigidwiki.com/wiki/Hellschreiber) using 2-FSK or AFSK for modules:
|
||||
SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x
|
||||
* [__APRS__](https://www.sigidwiki.com/wiki/APRS) using AFSK for modules:
|
||||
SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x
|
||||
|
||||
### Supported Arduino platforms:
|
||||
* __Arduino__
|
||||
|
@ -76,7 +78,6 @@ The list above is by no means exhaustive - RadioLib code is independent of the u
|
|||
### In development:
|
||||
* __AX5243__ FSK module
|
||||
* __LoRaWAN__ protocol for SX127x, RFM9x and SX126x modules
|
||||
* __APRS__ protocol for all the modules that can transmit AX.25
|
||||
* ___and more!___
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
|
123
examples/APRS/APRS_Position/APRS_Position.ino
Normal file
123
examples/APRS/APRS_Position/APRS_Position.ino
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
RadioLib APRS Position Example
|
||||
|
||||
This example sends APRS position reports
|
||||
using SX1278's FSK modem. The data is
|
||||
modulated as AFSK at 1200 baud using Bell
|
||||
202 tones.
|
||||
|
||||
DO NOT transmit in APRS bands unless
|
||||
you have a ham radio license!
|
||||
|
||||
Other modules that can be used for APRS:
|
||||
- SX127x/RFM9x
|
||||
- RF69
|
||||
- SX1231
|
||||
- CC1101
|
||||
- nRF24
|
||||
- Si443x/RFM2x
|
||||
|
||||
For default module settings, see the wiki page
|
||||
https://github.com/jgromes/RadioLib/wiki/Default-configuration
|
||||
|
||||
For full API reference, see the GitHub Pages
|
||||
https://jgromes.github.io/RadioLib/
|
||||
*/
|
||||
|
||||
// include the library
|
||||
#include <RadioLib.h>
|
||||
|
||||
// SX1278 has the following connections:
|
||||
// NSS pin: 10
|
||||
// DIO0 pin: 2
|
||||
// RESET pin: 9
|
||||
// DIO1 pin: 3
|
||||
SX1278 radio = new Module(10, 2, 9, 3);
|
||||
|
||||
// or using RadioShield
|
||||
// https://github.com/jgromes/RadioShield
|
||||
//SX1278 radio = RadioShield.ModuleA;
|
||||
|
||||
// create AFSK client instance using the FSK module
|
||||
// pin 5 is connected to SX1278 DIO2
|
||||
AFSKClient audio(&radio, 5);
|
||||
|
||||
// create AX.25 client instance using the AFSK instance
|
||||
AX25Client ax25(&audio);
|
||||
|
||||
// create APRS client isntance using the AX.25 client
|
||||
APRSClient aprs(&ax25);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
|
||||
// initialize SX1278
|
||||
// NOTE: moved to ISM band on purpose
|
||||
// DO NOT transmit in APRS bands without ham radio license!
|
||||
Serial.print(F("[SX1278] Initializing ... "));
|
||||
int state = radio.beginFSK(434.0);
|
||||
|
||||
// when using one of the non-LoRa modules for AX.25
|
||||
// (RF69, CC1101, Si4432 etc.), use the basic begin() method
|
||||
// int state = radio.begin();
|
||||
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
|
||||
// initialize AX.25 client
|
||||
Serial.print(F("[AX.25] Initializing ... "));
|
||||
// source station callsign: "N7LEM"
|
||||
// source station SSID: 0
|
||||
// preamble length: 8 bytes
|
||||
state = ax25.begin("N7LEM");
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
|
||||
// initialize APRS client
|
||||
Serial.print(F("[APRS] Initializing ... "));
|
||||
// symbol: '>' (car)
|
||||
state = aprs.begin('>');
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Serial.print(F("[APRS] Sending position ... "));
|
||||
|
||||
// send a location without message or timestamp
|
||||
int state = aprs.sendPosition("N0CALL", 0, "4911.67N", "01635.96E");
|
||||
delay(500);
|
||||
|
||||
// send a location with message and without timestamp
|
||||
state |= aprs.sendPosition("N0CALL", 0, "4911.67N", "01635.96E", "I'm here!");
|
||||
delay(500);
|
||||
|
||||
// send a location with message and timestamp
|
||||
state |= aprs.sendPosition("N0CALL", 0, "4911.67N", "01635.96E", "I'm here!", "093045z");
|
||||
delay(500);
|
||||
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
|
||||
// wait one minute before transmitting again
|
||||
delay(60000);
|
||||
}
|
|
@ -48,6 +48,7 @@ SSTVClient KEYWORD1
|
|||
HellClient KEYWORD1
|
||||
AFSKClient KEYWORD1
|
||||
FSK4Client KEYWORD1
|
||||
APRSClient KEYWORD1
|
||||
|
||||
# SSTV modes
|
||||
Scottie1 KEYWORD1
|
||||
|
@ -217,6 +218,9 @@ printGlyph KEYWORD2
|
|||
tone KEYWORD2
|
||||
noTone KEYWORD2
|
||||
|
||||
# APRS
|
||||
sendPosition KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
- SSTV (SSTVClient)
|
||||
- Hellschreiber (HellClient)
|
||||
- 4-FSK (FSK4Client)
|
||||
- APRS (APRSClient)
|
||||
|
||||
\par Quick Links
|
||||
Documentation for most common methods can be found in its reference page (see the list above).\n
|
||||
|
@ -89,6 +90,7 @@
|
|||
#include "protocols/RTTY/RTTY.h"
|
||||
#include "protocols/SSTV/SSTV.h"
|
||||
#include "protocols/FSK4/FSK4.h"
|
||||
#include "protocols/APRS/APRS.h"
|
||||
|
||||
// only create Radio class when using RadioShield
|
||||
#if defined(RADIOLIB_RADIOSHIELD)
|
||||
|
|
|
@ -245,6 +245,13 @@
|
|||
*/
|
||||
#define RADIOLIB_ERR_INVALID_OOK_RSSI_PEAK_TYPE (-108)
|
||||
|
||||
// APRS status codes
|
||||
|
||||
/*!
|
||||
\brief Supplied APRS symbol is invalid.
|
||||
*/
|
||||
#define RADIOLIB_ERR_INVALID_SYMBOL (-201)
|
||||
|
||||
// RTTY status codes
|
||||
|
||||
/*!
|
||||
|
|
67
src/protocols/APRS/APRS.cpp
Normal file
67
src/protocols/APRS/APRS.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#include "APRS.h"
|
||||
|
||||
APRSClient::APRSClient(AX25Client* ax) {
|
||||
_ax = ax;
|
||||
}
|
||||
|
||||
int16_t APRSClient::begin(char symbol, bool alt) {
|
||||
RADIOLIB_CHECK_RANGE(symbol, ' ', '}', RADIOLIB_ERR_INVALID_SYMBOL);
|
||||
_symbol = symbol;
|
||||
|
||||
if(alt) {
|
||||
_table = '\\';
|
||||
} else {
|
||||
_table = '/';
|
||||
}
|
||||
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
int16_t APRSClient::sendPosition(char* destCallsign, uint8_t destSSID, char* lat, char* lon, char* msg, char* time) {
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
size_t len = 1 + strlen(lat) + 1 + strlen(lon);
|
||||
if(msg != NULL) {
|
||||
len += 1 + strlen(msg);
|
||||
}
|
||||
if(time != NULL) {
|
||||
len += strlen(time);
|
||||
}
|
||||
char* info = new char[len];
|
||||
#else
|
||||
char info[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#endif
|
||||
|
||||
// build the info field
|
||||
if((msg == NULL) && (time == NULL)) {
|
||||
// no message, no timestamp
|
||||
sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_NO_MSG "%s%c%s%c", lat, _table, lon, _symbol);
|
||||
} else if((msg != NULL) && (time == NULL)) {
|
||||
// message, no timestamp
|
||||
sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_MSG "%s%c%s%c%s", lat, _table, lon, _symbol, msg);
|
||||
} else if((msg == NULL) && (time != NULL)) {
|
||||
// timestamp, no message
|
||||
sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_NO_MSG "%s%s%c%s%c", time, lat, _table, lon, _symbol);
|
||||
} else {
|
||||
// timestamp and message
|
||||
sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_MSG "%s%s%c%s%c%s", time, lat, _table, lon, _symbol, msg);
|
||||
}
|
||||
|
||||
// send the frame
|
||||
int16_t state = sendFrame(destCallsign, destSSID, info);
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] info;
|
||||
#endif
|
||||
return(state);
|
||||
}
|
||||
|
||||
int16_t APRSClient::sendFrame(char* destCallsign, uint8_t destSSID, char* info) {
|
||||
// get AX.25 callsign
|
||||
char srcCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1];
|
||||
_ax->getCallsign(srcCallsign);
|
||||
|
||||
AX25Frame frameUI(destCallsign, destSSID, srcCallsign, _ax->getSSID(), RADIOLIB_AX25_CONTROL_U_UNNUMBERED_INFORMATION |
|
||||
RADIOLIB_AX25_CONTROL_POLL_FINAL_DISABLED | RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME,
|
||||
RADIOLIB_AX25_PID_NO_LAYER_3, (const char*)info);
|
||||
|
||||
return(_ax->sendFrame(&frameUI));
|
||||
}
|
101
src/protocols/APRS/APRS.h
Normal file
101
src/protocols/APRS/APRS.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
#if !defined(_RADIOLIB_RADIOLIB_APRS_H)
|
||||
#define _RADIOLIB_RADIOLIB_APRS_H
|
||||
|
||||
#include "../../TypeDef.h"
|
||||
|
||||
#if !defined(RADIOLIB_EXCLUDE_APRS)
|
||||
|
||||
#include "../PhysicalLayer/PhysicalLayer.h"
|
||||
#include "../AX25/AX25.h"
|
||||
|
||||
// APRS data type identifiers
|
||||
#define RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_NO_MSG "!"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_GPS_RAW "$"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_ITEM ")"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_TEST ","
|
||||
#define RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_NO_MSG "/"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_MSG ":"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_OBJECT ";"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_STATION_CAPABILITES "<"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_MSG "="
|
||||
#define RADIOLIB_APRS_DATA_TYPE_STATUS ">"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_QUERY "?"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_MSG "@"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_TELEMETRY "T"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_MAIDENHEAD_BEACON "["
|
||||
#define RADIOLIB_APRS_DATA_TYPE_WEATHER_REPORT "_"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_USER_DEFINED "{"
|
||||
#define RADIOLIB_APRS_DATA_TYPE_THIRD_PARTY "}"
|
||||
|
||||
/*!
|
||||
\class APRSClient
|
||||
|
||||
\brief Client for APRS communication.
|
||||
*/
|
||||
class APRSClient {
|
||||
public:
|
||||
/*!
|
||||
\brief Default constructor.
|
||||
|
||||
\param ax Pointer to the instance of AX25Client to be used for APRS.
|
||||
*/
|
||||
explicit APRSClient(AX25Client* ax);
|
||||
|
||||
// basic methods
|
||||
|
||||
/*!
|
||||
\brief Initialization method.
|
||||
|
||||
\param symbol APRS symbol to be displayed.
|
||||
|
||||
\param alt Whether to use the primary (false) or alternate (true) symbol table. Defaults to primary table.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t begin(char symbol, bool alt = false);
|
||||
|
||||
/*!
|
||||
\brief Transmit position.
|
||||
|
||||
\param destCallsign Destination station callsign.
|
||||
|
||||
\param destSSID Destination station SSID.
|
||||
|
||||
\param lat Latitude as a null-terminated string.
|
||||
|
||||
\param long Longitude as a null-terminated string.
|
||||
|
||||
\param msg Message to be transmitted. Defaults to NULL (no message).
|
||||
|
||||
\param msg Position timestamp. Defaults to NULL (no timestamp).
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t sendPosition(char* destCallsign, uint8_t destSSID, char* lat, char* lon, char* msg = NULL, char* time = NULL);
|
||||
|
||||
/*!
|
||||
\brief Transmit generic APRS frame.
|
||||
|
||||
\param destCallsign Destination station callsign.
|
||||
|
||||
\param destSSID Destination station SSID.
|
||||
|
||||
\param info AX.25 info field contents.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t sendFrame(char* destCallsign, uint8_t destSSID, char* info);
|
||||
|
||||
#if !defined(RADIOLIB_GODMODE)
|
||||
private:
|
||||
#endif
|
||||
AX25Client* _ax;
|
||||
|
||||
// default APRS symbol (car)
|
||||
char _symbol = '>';
|
||||
char _table = '/';
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue