From ddb478afff2141cf0748aec65c455f4a1941b97c Mon Sep 17 00:00:00 2001 From: jgromes Date: Fri, 14 Feb 2020 08:08:59 +0100 Subject: [PATCH] [AX25] Added AX.25 support --- README.md | 1 + examples/AX25/AX25_Frames/AX25_Frames.ino | 167 ++++++++ examples/AX25/AX25_Transmit/AX25_Transmit.ino | 88 ++++ keywords.txt | 12 + src/RadioLib.h | 1 + src/TypeDef.h | 23 ++ src/protocols/AX25/AX25.cpp | 382 ++++++++++++++++++ src/protocols/AX25/AX25.h | 292 +++++++++++++ 8 files changed, 966 insertions(+) create mode 100644 examples/AX25/AX25_Frames/AX25_Frames.ino create mode 100644 examples/AX25/AX25_Transmit/AX25_Transmit.ino create mode 100644 src/protocols/AX25/AX25.cpp create mode 100644 src/protocols/AX25/AX25.h diff --git a/README.md b/README.md index af528946..b3980dee 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ RadioLib was originally created as a driver for [__RadioShield__](https://github * __HTTP__ for modules: ESP8266 * __RTTY__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101 and nRF24L01 * __Morse Code__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101 and nRF24L01 +* __AX.25__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231 and CC1101 ### Supported platforms: * __AVR__ - tested with hardware on Uno, Mega and Leonardo diff --git a/examples/AX25/AX25_Frames/AX25_Frames.ino b/examples/AX25/AX25_Frames/AX25_Frames.ino new file mode 100644 index 00000000..0d5ae5b6 --- /dev/null +++ b/examples/AX25/AX25_Frames/AX25_Frames.ino @@ -0,0 +1,167 @@ +/* + RadioLib AX.25 Frame Example + + This example shows how to send various + AX.25 frames using SX1278's FSK modem. + + Other modules that can be used for AX.25: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x + - nRF24 + + Using raw AX.25 frames requires some + knowledge of the protocol, refer to + AX25_Transmit for basic operation. + Frames shown in this example are not + exhaustive; all possible AX.25 frames + should be supported. +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 fsk = new Module(10, 2, 9, 3); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 fsk = RadioShield.ModuleA; + +// create AX.25 client instance using the FSK module +AX25Client ax25(&fsk); + +void setup() { + Serial.begin(9600); + + // initialize SX1278 + Serial.print(F("[SX1278] Initializing ... ")); + // carrier frequency: 434.0 MHz + // bit rate: 1.2 kbps (1200 baud AFSK AX.25) + // frequency deviation: 0.5 kHz (1200 baud AFSK AX.25) + int state = fsk.beginFSK(434.0, 1.2, 0.5); + + // when using one of the non-LoRa modules for AX.25 + // (RF69, CC1101, etc.), use the basic begin() method + // int state = fsk.begin(); + + if(state == 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 == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } +} + +void loop() { + // create AX.25 Unnumbered Information frame + // destination station callsign: "NJ7P" + // destination station SSID: 0 + // source station callsign: "N7LEM" + // source station SSID: 0 + // control field: UI, P/F not used, unnumbered frame + // protocol identifier: no layer 3 protocol implemented + // information field: "Hello World!" + AX25Frame frameUI("NJ7P", 0, "N7LEM", 0, AX25_CONTROL_U_UNNUMBERED_INFORMATION | + AX25_CONTROL_POLL_FINAL_DISABLED | AX25_CONTROL_UNNUMBERED_FRAME, + AX25_PID_NO_LAYER_3, "Hello World (unnumbered)!"); + + // send the frame + Serial.print(F("[AX.25] Sending UI frame ... ")); + int state = ax25.sendFrame(&frameUI); + if (state == ERR_NONE) { + // the packet was successfully transmitted + Serial.println(F("success!")); + + } else { + // some error occurred + Serial.print(F("failed, code ")); + Serial.println(state); + + } + + delay(1000); + + // create AX.25 Receive Ready frame + // destination station callsign: "NJ7P" + // destination station SSID: 0 + // source station callsign: "N7LEM" + // source station SSID: 0 + // control field: RR, P/F not used, supervisory frame + AX25Frame frameRR("NJ7P", 0, "N7LEM", 0, AX25_CONTROL_S_RECEIVE_READY | + AX25_CONTROL_POLL_FINAL_DISABLED | AX25_CONTROL_SUPERVISORY_FRAME); + + // set receive sequence number (0 - 7) + frameRR.setRecvSequence(0); + + // send the frame + Serial.print(F("[AX.25] Sending RR frame ... ")); + state = ax25.sendFrame(&frameRR); + if (state == ERR_NONE) { + // the packet was successfully transmitted + Serial.println(F("success!")); + + } else { + // some error occurred + Serial.print(F("failed, code ")); + Serial.println(state); + + } + + delay(1000); + + // create AX.25 Information frame + // destination station callsign: "NJ7P" + // destination station SSID: 0 + // source station callsign: "N7LEM" + // source station SSID: 0 + // control field: P/F not used, information frame + // protocol identifier: no layer 3 protocol implemented + // information field: "Hello World (numbered)!" + AX25Frame frameI("NJ7P", 0, "N7LEM", 0, AX25_CONTROL_POLL_FINAL_DISABLED | + AX25_CONTROL_INFORMATION_FRAME, AX25_PID_NO_LAYER_3, + "Hello World (numbered)!"); + + // set receive sequence number (0 - 7) + frameI.setRecvSequence(0); + + // set send sequence number (0 - 7) + frameI.setSendSequence(0); + + // send the frame + Serial.print(F("[AX.25] Sending I frame ... ")); + state = ax25.sendFrame(&frameI); + if (state == ERR_NONE) { + // the packet was successfully transmitted + Serial.println(F("success!")); + + } else { + // some error occurred + Serial.print(F("failed, code ")); + Serial.println(state); + + } + + delay(1000); +} diff --git a/examples/AX25/AX25_Transmit/AX25_Transmit.ino b/examples/AX25/AX25_Transmit/AX25_Transmit.ino new file mode 100644 index 00000000..fa97067d --- /dev/null +++ b/examples/AX25/AX25_Transmit/AX25_Transmit.ino @@ -0,0 +1,88 @@ +/* + RadioLib AX.25 Transmit Example + + This example sends AX.25 messages using + SX1278's FSK modem. + + Other modules that can be used for AX.25: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x + - nRF24 +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 fsk = new Module(10, 2, 9, 3); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 fsk = RadioShield.ModuleA; + +// create AX.25 client instance using the FSK module +AX25Client ax25(&fsk); + +void setup() { + Serial.begin(9600); + + // initialize SX1278 + Serial.print(F("[SX1278] Initializing ... ")); + // carrier frequency: 434.0 MHz + // bit rate: 1.2 kbps (1200 baud AFSK AX.25) + // frequency deviation: 0.5 kHz (1200 baud AFSK AX.25) + int state = fsk.beginFSK(434.0, 1.2, 0.5); + + // when using one of the non-LoRa modules for AX.25 + // (RF69, CC1101, etc.), use the basic begin() method + // int state = fsk.begin(); + + if(state == 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 == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } +} + +void loop() { + // send AX.25 unnumbered infomration frame + Serial.print(F("[AX.25] Sending UI frame ... ")); + // destination station callsign: "NJ7P" + // destination station SSID: 0 + int state = ax25.transmit("Hello World!", "NJ7P"); + if (state == ERR_NONE) { + // the packet was successfully transmitted + Serial.println(F("success!")); + + } else { + // some error occurred + Serial.print(F("failed, code ")); + Serial.println(state); + + } + + delay(1000); +} diff --git a/keywords.txt b/keywords.txt index d312388d..10f66074 100644 --- a/keywords.txt +++ b/keywords.txt @@ -39,6 +39,8 @@ HTTPClient KEYWORD1 RTTYClient KEYWORD1 MorseClient KEYWORD1 PagerClient KEYWORD1 +AX25Client KEYWORD1 +AX25Frame KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -176,6 +178,12 @@ getNumBytes KEYWORD2 sendSMS KEYWORD2 shutdown KEYWORD2 +# AX.25 +setRepeaters KEYWORD2 +setRecvSequence KEYWORD2 +setSendSequence KEYWORD2 +sendFrame KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### @@ -262,3 +270,7 @@ ERR_SPI_CMD_INVALID LITERAL1 ERR_SPI_CMD_FAILED LITERAL1 ERR_INVALID_SLEEP_PERIOD LITERAL1 ERR_INVALID_RX_PERIOD LITERAL1 + +ERR_INVALID_CALLSIGN LITERAL1 +ERR_INVALID_NUM_REPEATERS LITERAL1 +ERR_INVALID_REPEATER_CALLSIGN LITERAL1 diff --git a/src/RadioLib.h b/src/RadioLib.h index 538b9228..659ce3f8 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -67,6 +67,7 @@ // physical layer protocols #include "protocols/PhysicalLayer/PhysicalLayer.h" +#include "protocols/AX25/AX25.h" #include "protocols/Morse/Morse.h" #include "protocols/RTTY/RTTY.h" diff --git a/src/TypeDef.h b/src/TypeDef.h index dc4dcd7c..596516fa 100644 --- a/src/TypeDef.h +++ b/src/TypeDef.h @@ -540,6 +540,29 @@ */ #define ERR_INVALID_RX_PERIOD -709 +// AX.25-specific status codes + +/*! + \brief The provided callsign is invalid. + + The specified callsign is longer than 6 ASCII characters. +*/ +#define ERR_INVALID_CALLSIGN -801 + +/*! + \brief The provided repeater configuration is invalid. + + The specified number of repeaters does not match number of repeater IDs or their callsigns. +*/ +#define ERR_INVALID_NUM_REPEATERS -802 + +/*! + \brief One of the provided repeater callsigns is invalid. + + The specified callsign is longer than 6 ASCII characters. +*/ +#define ERR_INVALID_REPEATER_CALLSIGN -803 + /*! \} */ diff --git a/src/protocols/AX25/AX25.cpp b/src/protocols/AX25/AX25.cpp new file mode 100644 index 00000000..7c6a7933 --- /dev/null +++ b/src/protocols/AX25/AX25.cpp @@ -0,0 +1,382 @@ +#include "AX25.h" + +AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control) +: AX25Frame(destCallsign, destSSID, srcCallsign, srcSSID, control, 0, NULL, 0) { + +} + +AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, const char* info) + : AX25Frame(destCallsign, destSSID, srcCallsign, srcSSID, control, protocolID, (uint8_t*)info, strlen(info)) { + +} + +AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, uint8_t* info, uint16_t infoLen) { + // destination callsign/SSID + memcpy(this->destCallsign, destCallsign, strlen(destCallsign)); + this->destCallsign[strlen(destCallsign)] = '\0'; + this->destSSID = destSSID; + + // source callsign/SSID + memcpy(this->srcCallsign, srcCallsign, strlen(srcCallsign)); + this->srcCallsign[strlen(srcCallsign)] = '\0'; + this->srcSSID = srcSSID; + + // set repeaters + this->numRepeaters = 0; + #ifndef RADIOLIB_STATIC_ONLY + this->repeaterCallsigns = NULL; + this->repeaterSSIDs = NULL; + #endif + + // control field + this->control = control; + + // sequence numbers + this->rcvSeqNumber = 0; + this->sendSeqNumber = 0; + + // PID field + this->protocolID = protocolID; + + // info field + this->infoLen = infoLen; + if(infoLen > 0) { + #ifndef RADIOLIB_STATIC_ONLY + this->info = new uint8_t[infoLen]; + #endif + memcpy(this->info, info, infoLen); + } +} + +AX25Frame::~AX25Frame() { + #ifndef RADIOLIB_STATIC_ONLY + // deallocate info field + if(infoLen > 0) { + delete[] this->info; + } + + // deallocate repeaters + if(this->numRepeaters > 0) { + for(uint8_t i = 0; i < this->numRepeaters; i++) { + delete[] this->repeaterCallsigns[i]; + } + delete[] this->repeaterCallsigns; + delete[] this->repeaterSSIDs; + } + #endif +} + +int16_t AX25Frame::setRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters) { + // check number of repeaters + if((numRepeaters < 1) || (numRepeaters > 8)) { + return(ERR_INVALID_NUM_REPEATERS); + } + + // check repeater configuration + if(!(((repeaterCallsigns == NULL) && (repeaterSSIDs == NULL) && (numRepeaters == 0)) || + ((repeaterCallsigns != NULL) && (repeaterSSIDs != NULL) && (numRepeaters != 0)))) { + return(ERR_INVALID_NUM_REPEATERS); + } + for(uint16_t i = 0; i < numRepeaters; i++) { + if(strlen(repeaterCallsigns[i]) > AX25_MAX_CALLSIGN_LEN) { + return(ERR_INVALID_REPEATER_CALLSIGN); + } + } + + // create buffers + #ifndef RADIOLIB_STATIC_ONLY + this->repeaterCallsigns = new char*[numRepeaters]; + for(uint8_t i = 0; i < numRepeaters; i++) { + this->repeaterCallsigns[i] = new char[strlen(repeaterCallsigns[i])]; + } + this->repeaterSSIDs = new uint8_t[numRepeaters]; + #endif + + // copy data + this->numRepeaters = numRepeaters; + for(uint8_t i = 0; i < numRepeaters; i++) { + memcpy(this->repeaterCallsigns[i], repeaterCallsigns[i], strlen(repeaterCallsigns[i])); + } + memcpy(this->repeaterSSIDs, repeaterSSIDs, numRepeaters); + + return(ERR_NONE); +} + +void AX25Frame::setRecvSequence(uint8_t seqNumber) { + this->rcvSeqNumber = seqNumber; +} + +void AX25Frame::setSendSequence(uint8_t seqNumber) { + this->sendSeqNumber = seqNumber; +} + +AX25Client::AX25Client(PhysicalLayer* phy) { + _phy = phy; +} + +int16_t AX25Client::begin(const char* srcCallsign, uint8_t srcSSID, uint8_t preambleLen) { + // set source SSID + _srcSSID = srcSSID; + + // check source callsign length (6 characters max) + if(strlen(srcCallsign) > AX25_MAX_CALLSIGN_LEN) { + return(ERR_INVALID_CALLSIGN); + } + + // copy callsign + memcpy(_srcCallsign, srcCallsign, strlen(srcCallsign)); + _srcCallsign[strlen(srcCallsign)] = '\0'; + + // save preamble length + _preambleLen = preambleLen; + + // disable physical layer data shaping and set encoding to NRZ + int16_t state = _phy->setDataShaping(0.0); + RADIOLIB_ASSERT(state); + + state = _phy->setEncoding(0); + return(state); +} + +int16_t AX25Client::transmit(const char* str, const char* destCallsign, uint8_t destSSID) { + // create control field + uint8_t controlField = AX25_CONTROL_U_UNNUMBERED_INFORMATION | AX25_CONTROL_POLL_FINAL_DISABLED | AX25_CONTROL_UNNUMBERED_FRAME; + + // build the frame + AX25Frame frame(destCallsign, destSSID, _srcCallsign, _srcSSID, controlField, AX25_PID_NO_LAYER_3, (uint8_t*)str, strlen(str)); + + // send Unnumbered Information frame + return(sendFrame(&frame)); +} + +int16_t AX25Client::sendFrame(AX25Frame* frame) { + // check destination callsign length (6 characters max) + if(strlen(frame->destCallsign) > AX25_MAX_CALLSIGN_LEN) { + return(ERR_INVALID_CALLSIGN); + } + + // check repeater configuration + #ifndef RADIOLIB_STATIC_ONLY + if(!(((frame->repeaterCallsigns == NULL) && (frame->repeaterSSIDs == NULL) && (frame->numRepeaters == 0)) || + ((frame->repeaterCallsigns != NULL) && (frame->repeaterSSIDs != NULL) && (frame->numRepeaters != 0)))) { + return(ERR_INVALID_NUM_REPEATERS); + } + for(uint16_t i = 0; i < frame->numRepeaters; i++) { + if(strlen(frame->repeaterCallsigns[i]) > AX25_MAX_CALLSIGN_LEN) { + return(ERR_INVALID_REPEATER_CALLSIGN); + } + } + #endif + + // calculate frame length without FCS (destination address, source address, repeater addresses, control, PID, info) + size_t frameBuffLen = ((2 + frame->numRepeaters)*(AX25_MAX_CALLSIGN_LEN + 1)) + 1 + 1 + frame->infoLen; + // create frame buffer without preamble, start or stop flags + #ifndef RADIOLIB_STATIC_ONLY + uint8_t* frameBuff = new uint8_t[frameBuffLen + 2]; + #else + uint8_t frameBuff[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + uint8_t* frameBuffPtr = frameBuff; + + // set destination callsign - all address field bytes are shifted by one bit to make room for HDLC address extension bit + memset(frameBuffPtr, ' ' << 1, AX25_MAX_CALLSIGN_LEN); + for(uint8_t i = 0; i < strlen(frame->destCallsign); i++) { + *(frameBuffPtr + i) = frame->destCallsign[i] << 1; + } + frameBuffPtr += AX25_MAX_CALLSIGN_LEN; + + // set destination SSID + *(frameBuffPtr++) = AX25_SSID_COMMAND_DEST | AX25_SSID_RESERVED_BITS | (frame->destSSID & 0x0F) << 1 | AX25_SSID_HDLC_EXTENSION_CONTINUE; + + // set source callsign - all address field bytes are shifted by one bit to make room for HDLC address extension bit + memset(frameBuffPtr, ' ' << 1, AX25_MAX_CALLSIGN_LEN); + for(uint8_t i = 0; i < strlen(frame->srcCallsign); i++) { + *(frameBuffPtr + i) = frame->srcCallsign[i] << 1; + } + frameBuffPtr += AX25_MAX_CALLSIGN_LEN; + + // set source SSID + *(frameBuffPtr++) = AX25_SSID_COMMAND_SOURCE | AX25_SSID_RESERVED_BITS | (frame->srcSSID & 0x0F) << 1 | AX25_SSID_HDLC_EXTENSION_END; + + // set repeater callsigns + for(uint16_t i = 0; i < frame->numRepeaters; i++) { + memset(frameBuffPtr, ' ' << 1, AX25_MAX_CALLSIGN_LEN); + for(uint8_t j = 0; j < strlen(frame->repeaterCallsigns[i]); j++) { + *(frameBuffPtr + j) = frame->repeaterCallsigns[i][j] << 1; + } + frameBuffPtr += AX25_MAX_CALLSIGN_LEN; + *(frameBuffPtr++) = AX25_SSID_HAS_NOT_BEEN_REPEATED | AX25_SSID_RESERVED_BITS | (frame->repeaterSSIDs[i] & 0x0F) << 1 | AX25_SSID_HDLC_EXTENSION_CONTINUE; + } + + // set HDLC extension end bit + *frameBuffPtr |= AX25_SSID_HDLC_EXTENSION_END; + + // set sequence numbers of the frames that have it + uint8_t controlField = frame->control; + if((frame->control & 0x01) == 0) { + // information frame, set both sequence numbers + controlField |= frame->rcvSeqNumber << 5; + controlField |= frame->sendSeqNumber << 1; + } else if((frame->control & 0x02) == 0) { + // supervisory frame, set only receive sequence number + controlField |= frame->rcvSeqNumber << 5; + } + + // set control field + *(frameBuffPtr++) = controlField; + + // set PID field of the frames that have it + if(frame->protocolID != 0x00) { + *(frameBuffPtr++) = frame->protocolID; + } + + // set info field of the frames that have it + if(frame->infoLen > 0) { + memcpy(frameBuffPtr, frame->info, frame->infoLen); + frameBuffPtr += frame->infoLen; + } + + // flip bit order + for(size_t i = 0; i < frameBuffLen; i++) { + frameBuff[i] = flipBits(frameBuff[i]); + } + + // calculate FCS + uint16_t fcs = getFrameCheckSequence(frameBuff, frameBuffLen); + *(frameBuffPtr++) = (uint8_t)((fcs >> 8) & 0xFF); + *(frameBuffPtr++) = (uint8_t)(fcs & 0xFF); + + // prepare buffer for the final frame (stuffed, with added preamble + flags and NRZI-encoded) + #ifndef RADIOLIB_STATIC_ONLY + // worst-case scenario: sequence of 1s, will have 120% of the original length, stuffed frame also includes both flags + uint8_t* stuffedFrameBuff = new uint8_t[_preambleLen + 1 + (6*frameBuffLen)/5 + 2]; + #else + uint8_t stuffedFrameBuff[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // stuff bits (skip preamble and both flags) + uint16_t stuffedFrameBuffLenBits = 8*(_preambleLen + 1); + uint8_t count = 0; + for(uint16_t i = 0; i < frameBuffLen + 2; i++) { + for(int8_t shift = 7; shift >= 0; shift--) { + uint16_t stuffedFrameBuffPos = stuffedFrameBuffLenBits + 7 - 2*(stuffedFrameBuffLenBits%8); + if((frameBuff[i] >> shift) & 0x01) { + // copy 1 and increment counter + SET_BIT_IN_ARRAY(stuffedFrameBuff, stuffedFrameBuffPos); + stuffedFrameBuffLenBits++; + count++; + + // check 5 consecutive 1s + if(count == 5) { + // get the new position in stuffed frame + stuffedFrameBuffPos = stuffedFrameBuffLenBits + 7 - 2*(stuffedFrameBuffLenBits%8); + + // insert 0 and reset counter + CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, stuffedFrameBuffPos); + stuffedFrameBuffLenBits++; + count = 0; + } + + } else { + // copy 0 and reset counter + CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, stuffedFrameBuffPos); + stuffedFrameBuffLenBits++; + count = 0; + } + + } + } + + // deallocate memory + #ifndef RADIOLIB_STATIC_ONLY + delete[] frameBuff; + #endif + + // set preamble bytes and start flag field + for(uint16_t i = 0; i < _preambleLen + 1; i++) { + stuffedFrameBuff[i] = AX25_FLAG; + } + + // get stuffed frame length in bytes + size_t stuffedFrameBuffLen = stuffedFrameBuffLenBits/8 + 1; + uint8_t trailingLen = stuffedFrameBuffLenBits % 8; + + // set end flag field (may be split into two bytes due to misalignment caused by extra stuffing bits) + if(trailingLen != 0) { + stuffedFrameBuffLen++; + stuffedFrameBuff[stuffedFrameBuffLen - 2] = AX25_FLAG >> trailingLen; + stuffedFrameBuff[stuffedFrameBuffLen - 1] = AX25_FLAG << (8 - trailingLen); + } else { + stuffedFrameBuff[stuffedFrameBuffLen - 1] = AX25_FLAG; + } + + // convert to NRZI + for(size_t i = _preambleLen + 1; i < stuffedFrameBuffLen*8; i++) { + size_t currBitPos = i + 7 - 2*(i%8); + size_t prevBitPos = (i - 1) + 7 - 2*((i - 1)%8); + if(TEST_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos)) { + // bit is 1, no change, copy previous bit + if(TEST_BIT_IN_ARRAY(stuffedFrameBuff, prevBitPos)) { + SET_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos); + } else { + CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos); + } + + } else { + // bit is 0, transition, copy inversion of the previous bit + if(TEST_BIT_IN_ARRAY(stuffedFrameBuff, prevBitPos)) { + CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos); + } else { + SET_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos); + } + } + } + + // transmit + int16_t state = _phy->transmit(stuffedFrameBuff, stuffedFrameBuffLen); + + // deallocate memory + #ifndef RADIOLIB_STATIC_ONLY + delete[] stuffedFrameBuff; + #endif + + return(state); +} + +/* + CCITT CRC implementation based on https://github.com/kicksat/ax25 + + Licensed under Creative Commons Attribution-ShareAlike 4.0 International + https://creativecommons.org/licenses/by-sa/4.0/ +*/ +uint16_t AX25Client::getFrameCheckSequence(uint8_t* buff, size_t len) { + uint8_t outBit = 0; + uint16_t mask = 0x0000; + uint16_t shiftReg = CRC_CCITT_INIT; + + for(size_t i = 0; i < len; i++) { + for(uint8_t b = 0x80; b > 0x00; b /= 2) { + outBit = (shiftReg & 0x01) ? 0x01 : 0x00; + shiftReg >>= 1; + mask = XOR((buff[i] & b), outBit) ? CRC_CCITT_POLY_REVERSED : 0x0000; + shiftReg ^= mask; + } + } + + return(flipBits16(~shiftReg)); +} + +uint8_t AX25Client::flipBits(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +uint16_t AX25Client::flipBits16(uint16_t i) { + i = (i & 0xFF00) >> 8 | (i & 0x00FF) << 8; + i = (i & 0xF0F0) >> 4 | (i & 0x0F0F) << 4; + i = (i & 0xCCCC) >> 2 | (i & 0x3333) << 2; + i = (i & 0xAAAA) >> 1 | (i & 0x5555) << 1; + return i; +} diff --git a/src/protocols/AX25/AX25.h b/src/protocols/AX25/AX25.h new file mode 100644 index 00000000..f3e80c96 --- /dev/null +++ b/src/protocols/AX25/AX25.h @@ -0,0 +1,292 @@ +#ifndef _RADIOLIB_AX25_H +#define _RADIOLIB_AX25_H + +#include "../../TypeDef.h" +#include "../PhysicalLayer/PhysicalLayer.h" + +// macros to access bits in byte array, from http://www.mathcs.emory.edu/~cheung/Courses/255/Syllabus/1-C-intro/bit-array.html +#define SET_BIT_IN_ARRAY(A, k) ( A[(k/8)] |= (1 << (k%8)) ) +#define CLEAR_BIT_IN_ARRAY(A, k) ( A[(k/8)] &= ~(1 << (k%8)) ) +#define TEST_BIT_IN_ARRAY(A, k) ( A[(k/8)] & (1 << (k%8)) ) +#define GET_BIT_IN_ARRAY(A, k) ( (A[(k/8)] & (1 << (k%8))) ? 1 : 0 ) + +// CRC-CCITT calculation macros +#define XOR(A, B) ( ((A) || (B)) && !((A) && (B)) ) +#define CRC_CCITT_POLY 0x1021 // generator polynomial +#define CRC_CCITT_POLY_REVERSED 0x8408 // CRC_CCITT_POLY in reversed bit order +#define CRC_CCITT_INIT 0xFFFF // initial value + +// maximum callsign length in bytes +#define AX25_MAX_CALLSIGN_LEN 6 + +// flag field MSB LSB DESCRIPTION +#define AX25_FLAG 0b01111110 // 7 0 AX.25 frame start/end flag + +// address field +#define AX25_SSID_COMMAND_DEST 0b10000000 // 7 7 frame type: command (set in destination SSID) +#define AX25_SSID_COMMAND_SOURCE 0b00000000 // 7 7 command (set in source SSID) +#define AX25_SSID_RESPONSE_DEST 0b00000000 // 7 7 response (set in destination SSID) +#define AX25_SSID_RESPONSE_SOURCE 0b10000000 // 7 7 response (set in source SSID) +#define AX25_SSID_HAS_NOT_BEEN_REPEATED 0b00000000 // 7 7 not repeated yet (set in repeater SSID) +#define AX25_SSID_HAS_BEEN_REPEATED 0b10000000 // 7 7 repeated (set in repeater SSID) +#define AX25_SSID_RESERVED_BITS 0b01100000 // 6 5 reserved bits in SSID +#define AX25_SSID_HDLC_EXTENSION_CONTINUE 0b00000000 // 0 0 HDLC extension bit: next octet contains more address information +#define AX25_SSID_HDLC_EXTENSION_END 0b00000001 // 0 0 address field end + +// control field +#define AX25_CONTROL_U_SET_ASYNC_BAL_MODE 0b01101100 // 7 2 U frame type: set asynchronous balanced mode (connect request) +#define AX25_CONTROL_U_SET_ASYNC_BAL_MODE_EXT 0b00101100 // 7 2 set asynchronous balanced mode extended (connect request with module 128) +#define AX25_CONTROL_U_DISCONNECT 0b01000000 // 7 2 disconnect request +#define AX25_CONTROL_U_DISCONNECT_MODE 0b00001100 // 7 2 disconnect mode (system busy or disconnected) +#define AX25_CONTROL_U_UNNUMBERED_ACK 0b01100000 // 7 2 unnumbered acknowledge +#define AX25_CONTROL_U_FRAME_REJECT 0b10000100 // 7 2 frame reject +#define AX25_CONTROL_U_UNNUMBERED_INFORMATION 0b00000000 // 7 2 unnumbered information +#define AX25_CONTROL_U_EXHANGE_IDENTIFICATION 0b10101100 // 7 2 exchange ID +#define AX25_CONTROL_U_TEST 0b11100000 // 7 2 test +#define AX25_CONTROL_POLL_FINAL_ENABLED 0b00010000 // 4 4 control field poll/final bit: enabled +#define AX25_CONTROL_POLL_FINAL_DISABLED 0b00000000 // 4 4 disabled +#define AX25_CONTROL_S_RECEIVE_READY 0b00000000 // 3 2 S frame type: receive ready (system ready to receive) +#define AX25_CONTROL_S_RECEIVE_NOT_READY 0b00000100 // 3 2 receive not ready (TNC buffer full) +#define AX25_CONTROL_S_REJECT 0b00001000 // 3 2 reject (out of sequence or duplicate) +#define AX25_CONTROL_S_SELECTIVE_REJECT 0b00001100 // 3 2 selective reject (single frame repeat request) +#define AX25_CONTROL_INFORMATION_FRAME 0b00000000 // 0 0 frame type: information (I frame) +#define AX25_CONTROL_SUPERVISORY_FRAME 0b00000001 // 1 0 supervisory (S frame) +#define AX25_CONTROL_UNNUMBERED_FRAME 0b00000011 // 1 0 unnumbered (U frame) + +// protocol identifier field +#define AX25_PID_ISO_8208 0x01 +#define AX25_PID_TCP_IP_COMPRESSED 0x06 +#define AX25_PID_TCP_IP_UNCOMPRESSED 0x07 +#define AX25_PID_SEGMENTATION_FRAGMENT 0x08 +#define AX25_PID_TEXNET_DATAGRAM_PROTOCOL 0xC3 +#define AX25_PID_LINK_QUALITY_PROTOCOL 0xC4 +#define AX25_PID_APPLETALK 0xCA +#define AX25_PID_APPLETALK_ARP 0xCB +#define AX25_PID_ARPA_INTERNET_PROTOCOL 0xCC +#define AX25_PID_ARPA_ADDRESS_RESOLUTION 0xCD +#define AX25_PID_FLEXNET 0xCE +#define AX25_PID_NET_ROM 0xCF +#define AX25_PID_NO_LAYER_3 0xF0 +#define AX25_PID_ESCAPE_CHARACTER 0xFF + +/*! + \class AX25Frame + + \brief Abstraction of AX.25 frame format. +*/ +class AX25Frame { + public: + /*! + \brief Callsign of the destination station. + */ + char destCallsign[AX25_MAX_CALLSIGN_LEN + 1]; + + /*! + \brief SSID of the destination station. + */ + uint8_t destSSID; + + /*! + \brief Callsign of the source station. + */ + char srcCallsign[AX25_MAX_CALLSIGN_LEN + 1]; + + /*! + \brief SSID of the source station. + */ + uint8_t srcSSID; + + /*! + \brief Number of repeaters to be used. + */ + uint8_t numRepeaters; + + /*! + \brief The control field. + */ + uint8_t control; + + /*! + \brief The protocol identifier (PID) field. + */ + uint8_t protocolID; + + /*! + \brief Number of bytes in the information field. + */ + uint16_t infoLen; + + /*! + \brief Receive sequence number. + */ + uint8_t rcvSeqNumber; + + /*! + \brief Send sequence number. + */ + uint16_t sendSeqNumber; + + #ifndef RADIOLIB_STATIC_ONLY + uint8_t* info; + char** repeaterCallsigns; + uint8_t* repeaterSSIDs; + #else + uint8_t info[RADIOLIB_STATIC_ARRAY_SIZE]; + char repeaterCallsigns[8][AX25_MAX_CALLSIGN_LEN + 1]; + uint8_t repeaterSSIDs[8]; + #endif + + /*! + \brief Overloaded constructor, for frames without info field. + + \param destCallsign Callsign of the destination station. + + \param destSSID SSID of the destination station. + + \param srcCallsign Callsign of the source station. + + \param srcSSID SSID of the source station. + + \param control The control field. + */ + AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control); + + /*! + \brief Overloaded constructor, for frames with C-string info field. + + \param destCallsign Callsign of the destination station. + + \param destSSID SSID of the destination station. + + \param srcCallsign Callsign of the source station. + + \param srcSSID SSID of the source station. + + \param control The control field. + + \param protocolID The protocol identifier (PID) field. Set to zero if the frame doesn't have this field. + + \param info Information field, in the form of null-terminated C-string. + */ + AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, const char* info); + + /*! + \brief Default constructor. + + \param destCallsign Callsign of the destination station. + + \param destSSID SSID of the destination station. + + \param srcCallsign Callsign of the source station. + + \param srcSSID SSID of the source station. + + \param control The control field. + + \param protocolID The protocol identifier (PID) field. Set to zero if the frame doesn't have this field. + + \param info Information field, in the form of arbitrary binary buffer. + + \param infoLen Number of bytes in the information field. + */ + AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, uint8_t* info, uint16_t infoLen); + + /*! + \brief Default destructor. + */ + ~AX25Frame(); + + /*! + \brief Method to set the repeater callsigns and SSIDs. + + \param repeaterCallsigns Array of repeater callsigns in the form of null-terminated C-strings. + + \param repeaterSSIDs Array of repeater SSIDs. + + \param numRepeaters Number of repeaters, maximum is 8. + + \returns \ref status_codes + */ + int16_t setRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters); + + /*! + \brief Method to set receive sequence number. + + \param seqNumber Sequence number to set, 0 to 7. + */ + void setRecvSequence(uint8_t seqNumber); + + /*! + \brief Method to set send sequence number. + + \param seqNumber Sequence number to set, 0 to 7. + */ + void setSendSequence(uint8_t seqNumber); +}; + +/*! + \class AX25Client + + \brief Client for AX25 communication. +*/ +class AX25Client { + public: + /*! + \brief Default constructor. + + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + AX25Client(PhysicalLayer* phy); + + // basic methods + + /*! + \brief Initialization method. + + \param srcCallsign Callsign of the source station. + + \param srcSSID 4-bit SSID of the source station (in case there are more stations with the same callsign). Defaults to 0. + + \param preambleLen Number of "preamble" bytes (AX25_FLAG) sent ahead of the actual AX.25 frame. Does not include the first AX25_FLAG byte, which is considered part of the frame. Defaults to 8. + + \returns \ref status_codes + */ + int16_t begin(const char* srcCallsign, uint8_t srcSSID = 0x00, uint8_t preambleLen = 8); + + /*! + \brief Transmit unnumbered information (UI) frame. + + \param str Data to be sent. + + \param destCallsign Callsign of the destination station. + + \param destSSID 4-bit SSID of the destination station (in case there are more stations with the same callsign). Defaults to 0. + + \returns \ref status_codes + */ + int16_t transmit(const char* str, const char* destCallsign, uint8_t destSSID = 0x00); + + /*! + \brief Transmit arbitrary AX.25 frame. + + \param frame Frame to be sent. + + \returns \ref status_codes + */ + int16_t sendFrame(AX25Frame* frame); + +#ifndef RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* _phy; + + char _srcCallsign[AX25_MAX_CALLSIGN_LEN + 1]; + uint8_t _srcSSID; + uint16_t _preambleLen; + + uint16_t getFrameCheckSequence(uint8_t* buff, size_t len); + uint8_t flipBits(uint8_t b); + uint16_t flipBits16(uint16_t i); +}; + +#endif