Received LoRa frames can now be converted to AX25 frames.

master
marcel 2 years ago
parent 414ddbd9c8
commit 64877d828e
  1. 2
      CHANGELOG.md
  2. 2
      README.md
  3. BIN
      build/src/CMakeFiles/KISS.dir/kiss.cpp.obj
  4. BIN
      build/src/CMakeFiles/main.dir/main.cpp.obj
  5. BIN
      build/src/libKISS.a
  6. BIN
      build/src/main.bin
  7. 52919
      build/src/main.dis
  8. BIN
      build/src/main.elf
  9. 1463
      build/src/main.elf.map
  10. 8496
      build/src/main.hex
  11. BIN
      build/src/main.uf2
  12. 2
      src/Config.h
  13. 189
      src/kiss.cpp
  14. 24
      src/kiss.h
  15. 131
      src/main.cpp

@ -28,3 +28,5 @@ First (more or less) working version.
### Added
- All settings (LoRa and APRS) can now be saved to FLASH.
- Command added for restarting LoRa radio when settings are alterred: "restart lora"
- Received LoRa frames can now be converted to propper AX25 frames (needed for KISS TNC functionality)

@ -1,5 +1,5 @@
# lora_aprs_node_pico
A simple LoRa APRS module which can be used as a remote data logger and/or remote I/O controller. It is an evolution of another project of mine: [https://www.meezenest.nl/mees-elektronica/aprs_telemetry.html](https://www.meezenest.nl/mees-elektronica/aprs_telemetry.html)
A simple LoRa APRS module which can be used as a remote data logger and/or remote I/O controller. See my website for more information: [https://www.meezenest.nl/mees-elektronica/RPi-pico-LoRa-APRS.html](https://www.meezenest.nl/mees-elektronica/RPi-pico-LoRa-APRS.html). It is an evolution of another project of mine: [https://www.meezenest.nl/mees-elektronica/aprs_telemetry.html](https://www.meezenest.nl/mees-elektronica/aprs_telemetry.html)
This program is written for the RP2040 C++ SDK.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

@ -92,5 +92,7 @@
uint8_t KissMode = OFF;
} Status;
#endif

@ -1,6 +1,191 @@
#include "kiss.h"
uint16_t KissClass::EncodeFrame(void)
/*
* KISS encoder uses two steps:
*
* Step 1: encodes the LoRa APRS frame into an AX.25 frame. DONE
* Step 2: encapsulates the AX.25 frame into a KISS frame. TODO
*
* struct aprs_frame {
* uint8_t source_address[10];
* uint8_t digi_path[255];
* uint8_t data_field[255];
* uint8_t message[255];
* uint8_t digis[10][10];
* uint8_t acknowledge_number[255];
* bool acknowledge_request = false;
* uint16_t server_command = 0;
* uint16_t number_of_digipeaters = 0;
*
* uint8_t valid_apsr_data = false;
* };
*
*/
uint16_t KissClass::EncodeFrame(struct aprs_frame *aprsframe, struct ax25_frame *ax25frame)
{
return 100;
uint8_t *encoded_call;
uint16_t cnt=0;
uint16_t position = 0;
uint8_t digi_cnt = 0;
// Destination call
//printf("Destination: ");
encoded_call = EncodeCall(aprsframe->digis[0]);
cnt = 0;
while (cnt < 7)
{
ax25frame->complete[position] = *(encoded_call+cnt);
////printf("0x%X ", *(encoded_call+cnt));
//printf("0x%X ", ax25frame->complete[position]);
cnt++;
position++;
}
//printf("\n");
// Source call
//printf("Source: ");
cnt=0;
encoded_call = EncodeCall(aprsframe->source_address);
while (cnt < 7)
{
ax25frame->complete[position] = *(encoded_call+cnt);
////printf("0x%X ", *(encoded_call+cnt));
//printf("0x%X ", ax25frame->complete[position]);
cnt++;
position++;
}
//printf("\n");
// No digipeaters in path, so destination is the last call and therefore the 'last' flag should be set
if (aprsframe->number_of_digipeaters == 0)
{
ax25frame->complete[position-1] |= 0x01;
}
else
{
// Path (digipeaters)
//printf("Path: ");
cnt=0;
digi_cnt = 1; // Start at position 1 as position 0 contains destination (which we already encoded)
while (aprsframe->number_of_digipeaters-- != 0) {
while (cnt < 7) {
encoded_call = EncodeCall(aprsframe->digis[digi_cnt]);
ax25frame->complete[position] = *(encoded_call+cnt);
////printf("0x%X ", *(encoded_call+cnt));
//printf("0x%X ", ax25frame->complete[position]);
cnt++;
position++;
}
digi_cnt++;
cnt = 0;
}
//printf("\n");
// Set 'last' flag
ax25frame->complete[position-1] |= 0x01;
}
// Add control fields
ax25frame->complete[position++] = 0x03;
ax25frame->complete[position++] = 0xF0;
// All the dificult bits are done, now we just add the payload.
//printf("Data:");
cnt = 0;
while (aprsframe->data_field[cnt] != 0 && cnt < 256) {
ax25frame->complete[position] = aprsframe->data_field[cnt];
//printf("0x%X ", ax25frame->complete[position]);
cnt++;
position++;
}
printf( "\n");
// Store length of AX25 frame
ax25frame->lenght = position;
/*cnt=0;
while (ax25frame->lenght-- != 0)
{
printf("0x%X ", ax25frame->complete[cnt]);
cnt++;
}
printf("\n");
*/
//fwrite(ax25frame->complete, 1, ax25frame->lenght, stdout);
return 0;
}
/*
* Encodes call according to AX.25 specs.
*
* input : string with call and ssid as readable characters
* output: pointer to memory location (7 bytes with the encoded call)
*
*/
uint8_t * KissClass::EncodeCall(uint8_t string[])
{
uint8_t position = 0;
uint8_t cnt = 0;
static uint8_t call[7] = { 0,0,0,0,0,0,0} ;
uint8_t ssid = 0;
// extract call
while( string[position] != 0 || cnt < 6)
{
call[cnt++] = string[position] << 1;
if ( string[position] == '-') {
position++;
break;
}
position++;
}
// pad with spaces to a length of 6
while( cnt < 6 )
{
call[cnt++] = ' ' << 1;
}
// extract ssid
cnt=0;
while( string[position] != 0 )
{
if (string[position] >= 48 && string[position] <= 57) {
ssid = 10*ssid + string[position]-48;
position++;
}
}
// if asterix is pressent in string, than the message has been repeated, so set the 'has been repeated' flag in the ssid register
position = 0;
while( string[position] != 0)
{
if (string[position] == '*')
ssid = 0b10000000;
position++;
}
ssid = (ssid << 1) | 0b01100000;
// add encoded ssid to encoded call array
call[6] = ssid;
/*
//printf("SSID: 0x%X\n", ssid);
cnt = 0;
while (cnt < 7)
{
//printf("0x%X ", call[cnt++]);
}
//printf("\n");
*/
return call;
}

@ -22,10 +22,32 @@
#define ERROR_TXFAILED 0x02
#define ERROR_QUEUE_FULL 0x04
struct aprs_frame {
uint8_t source_address[10];
uint8_t digi_path[255];
uint8_t data_field[255];
uint8_t message[255];
uint8_t digis[10][10];
uint8_t acknowledge_number[255];
bool acknowledge_request = false;
uint16_t server_command = 0;
uint16_t number_of_digipeaters = 0;
uint8_t valid_apsr_data = false;
};
struct ax25_frame {
uint8_t complete[512];
uint16_t lenght = 0;
uint8_t kiss_frame[512];
};
class KissClass
{
public:
uint16_t EncodeFrame(void);
uint16_t EncodeFrame(struct aprs_frame *frame, struct ax25_frame *ax25frame);
private:
uint8_t * EncodeCall(uint8_t string[]);
};
#endif

@ -258,6 +258,11 @@ void ProcessSerialInput(char string[])
else if (strcmp(command, "kiss") == 0) {
log_out("Entering KISS mode.\n");
log_out("You can exit KISS mode via KISS command <0xC0 0xFF 0xC0>\n");
// disable CRLF convertion, so we can write raw data to the USB port in KISS mode
stdio_set_translate_crlf(&stdio_usb, false);
//fwrite(buf, 1, sizeof(buf), stdout); // seems not to work
//putchar(49);
Status.KissMode = ON;
}
@ -624,58 +629,56 @@ void getPacketData(int packetLength)
rxBuffer[position] = 0;
}
/* Encode LoRa APRS frame: extract source call, digipeater path and data field. At the same time, check for data corruption
/* Decode LoRa APRS frame: extract source call, digipeater path and data field. At the same time, check for data corruption
*
* If frame is a message from the server it returns the command from this server
*
* The resulting fields are stored in struct AprsFrame and given to the kiss-routines for further processing (when in kiss-mode)
*/
uint16_t decode_packet ()
{
int position = 0;
int cnt = 0;
int number_of_digipeaters = 0;
char valid_apsr_data = false;
uint8_t aprs_source_address[10];
uint8_t aprs_digi_path[255];
uint8_t aprs_data_field[255];
uint8_t aprs_message[255];
uint8_t aprs_digis[10][10];
uint8_t aprs_acknowledge_number[255];
bool aprs_acknowledge_request = false;
uint16_t aprs_server_command = 0;
memset(aprs_source_address, 0, sizeof(aprs_source_address));
memset(aprs_digi_path, 0, sizeof(aprs_digi_path));
memset(aprs_data_field, 0, sizeof(aprs_data_field));
memset(aprs_message, 0, sizeof(aprs_message));
memset(aprs_digis, 0, sizeof(aprs_digis));
memset(aprs_acknowledge_number, 0, sizeof(aprs_acknowledge_number));
struct aprs_frame AprsFrame; //defined in kiss.h
struct ax25_frame AX25Frame; //defined in kiss.h
memset(AprsFrame.source_address, 0, sizeof(AprsFrame.source_address));
memset(AprsFrame.digi_path, 0, sizeof(AprsFrame.digi_path));
memset(AprsFrame.data_field, 0, sizeof(AprsFrame.data_field));
memset(AprsFrame.message, 0, sizeof(AprsFrame.message));
memset(AprsFrame.digis, 0, sizeof(AprsFrame.digis));
memset(AprsFrame.acknowledge_number, 0, sizeof(AprsFrame.acknowledge_number));
AprsFrame.acknowledge_request = false;
AprsFrame.server_command = 0;
AprsFrame.number_of_digipeaters = 0;
AprsFrame.valid_apsr_data = false;
// Extract from address
cnt = 0;
while( rxBuffer[position] != 0 )
{
aprs_source_address[cnt++] = rxBuffer[position];
AprsFrame.source_address[cnt++] = rxBuffer[position];
if ( rxBuffer[position] == '>' && position < 10 ) {
aprs_source_address[cnt-1] = 0;
valid_apsr_data = true;
AprsFrame.source_address[cnt-1] = 0;
AprsFrame.valid_apsr_data = true;
position++;
break;
}
position++;
}
if (valid_apsr_data == true)
if (AprsFrame.valid_apsr_data == true)
{
// Extract digi path
valid_apsr_data = false;
AprsFrame.valid_apsr_data = false;
cnt = 0;
while( rxBuffer[position] != 0 )
{
aprs_digi_path[cnt++] = rxBuffer[position];
AprsFrame.digi_path[cnt++] = rxBuffer[position];
if ( rxBuffer[position] == ':' ) {
aprs_digi_path[cnt-1] = 0;
valid_apsr_data = true;
AprsFrame.digi_path[cnt-1] = 0;
AprsFrame.valid_apsr_data = true;
position++;
break;
}
@ -684,90 +687,96 @@ uint16_t decode_packet ()
}
if (valid_apsr_data == true)
if (AprsFrame.valid_apsr_data == true)
{
// Extract data field
cnt = 0;
while( rxBuffer[position] != 0 )
{
aprs_data_field[cnt++] = rxBuffer[position];
AprsFrame.data_field[cnt++] = rxBuffer[position];
position++;
}
aprs_data_field[cnt] = 0;
AprsFrame.data_field[cnt] = 0;
}
if (valid_apsr_data == true)
if (AprsFrame.valid_apsr_data == true)
{
// Extract digis from digi-path
cnt = 0;
position = 0;
number_of_digipeaters = 0;
while( aprs_digi_path[position] != 0 )
AprsFrame.number_of_digipeaters = 0;
while( AprsFrame.digi_path[position] != 0 )
{
aprs_digis[number_of_digipeaters][cnt++] = aprs_digi_path[position];
if ( aprs_digi_path[position] == ',' && cnt < 10 ) {
aprs_digis[number_of_digipeaters][cnt-1] = 0;
if (++number_of_digipeaters > 9) {
valid_apsr_data = false;
AprsFrame.digis[AprsFrame.number_of_digipeaters][cnt++] = AprsFrame.digi_path[position];
if ( AprsFrame.digi_path[position] == ',' && cnt < 10 ) {
AprsFrame.digis[AprsFrame.number_of_digipeaters][cnt-1] = 0;
if (++AprsFrame.number_of_digipeaters > 9) {
AprsFrame.valid_apsr_data = false;
break;
}
cnt = 0;
}
position++;
}
aprs_digis[number_of_digipeaters][cnt] = 0;
AprsFrame.digis[AprsFrame.number_of_digipeaters][cnt] = 0;
}
if (valid_apsr_data) {
if (AprsFrame.valid_apsr_data) {
// Check if packet comes from our server and if so, check if it is a message for us.
if ( !compare_strings(aprs_source_address, AprsSettings.ServerCall) ) {
if ( !compare_strings(AprsFrame.source_address, AprsSettings.ServerCall) ) {
if ( is_message_for_me(aprs_data_field, AprsSettings.MyCall) )
if ( is_message_for_me(AprsFrame.data_field, AprsSettings.MyCall) )
{
// Extract aprs message from data field
position=11;
while( aprs_data_field[position] != 0 )
while( AprsFrame.data_field[position] != 0 )
{
aprs_message[position-11] = aprs_data_field[position];
AprsFrame.message[position-11] = AprsFrame.data_field[position];
position++;
}
AprsFrame.message[position-11] = 0; // Terminate string
// Extract command and acknowledge number (if present)
cnt = 0;
position = 0;
while( aprs_message[position] != 0 )
while( AprsFrame.message[position] != 0 )
{
if ( aprs_message[position] == '{' ) {
if ( AprsFrame.message[position] == '{' ) {
aprs_acknowledge_number[cnt++] = 'a';
aprs_acknowledge_number[cnt++] = 'c';
aprs_acknowledge_number[cnt++] = 'k';
aprs_acknowledge_request = true;
AprsFrame.acknowledge_number[cnt++] = 'a';
AprsFrame.acknowledge_number[cnt++] = 'c';
AprsFrame.acknowledge_number[cnt++] = 'k';
AprsFrame.acknowledge_request = true;
}
// Calculate server command
if (!aprs_acknowledge_request) {
aprs_server_command = 10*aprs_server_command + aprs_message[position]-48;
if (!AprsFrame.acknowledge_request) {
AprsFrame.server_command = 10*AprsFrame.server_command + AprsFrame.message[position]-48;
}
position++;
if (aprs_acknowledge_request) {
aprs_acknowledge_number[cnt++] = aprs_message[position];
if (AprsFrame.acknowledge_request) {
AprsFrame.acknowledge_number[cnt++] = AprsFrame.message[position];
}
}
aprs_acknowledge_number[cnt] = 0;
AprsFrame.acknowledge_number[cnt] = 0;
}
}
log_out("Source address: %s\nDigipeaters (%u): %s %s %s %s\nData: %s\n", aprs_source_address, number_of_digipeaters+1, aprs_digis[0], aprs_digis[1], aprs_digis[2], aprs_digis[3], aprs_data_field);
if (aprs_message[0])
log_out("Source address: %s\nDigipeaters (%u): %s %s %s %s\nData: %s\n", AprsFrame.source_address, AprsFrame.number_of_digipeaters+1, AprsFrame.digis[0], AprsFrame.digis[1], AprsFrame.digis[2], AprsFrame.digis[3], AprsFrame.data_field);
// If in KISS mode the struct AprsFrame is handed over to the KISS encoder
if (Status.KissMode == 0)
Kiss.EncodeFrame(&AprsFrame, &AX25Frame);
if (AprsFrame.message[0])
{
log_out("Message from server: %s (command %u)\n", aprs_message, aprs_server_command);
if (aprs_acknowledge_request) {
ComposeAprsFrame(aprs_acknowledge_number);
log_out("Acknowledge request: %s\n", aprs_acknowledge_number);
log_out("Message from server: %s (command %u)\n", AprsFrame.message, AprsFrame.server_command);
if (AprsFrame.acknowledge_request) {
ComposeAprsFrame(AprsFrame.acknowledge_number);
log_out("Acknowledge request: %s\n", AprsFrame.acknowledge_number);
}
}
@ -776,7 +785,7 @@ uint16_t decode_packet ()
else
log_out("Error decoding APRS frame.");
return (aprs_server_command);
return (AprsFrame.server_command);
}
/*

Loading…
Cancel
Save