Received LoRa frames can now be converted to AX25 frames.
This commit is contained in:
@@ -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.
Binary file not shown.
Binary file not shown.
52287
build/src/main.dis
52287
build/src/main.dis
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
8496
build/src/main.hex
8496
build/src/main.hex
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -92,5 +92,7 @@
|
||||
|
||||
uint8_t KissMode = OFF;
|
||||
} Status;
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
189
src/kiss.cpp
189
src/kiss.cpp
@@ -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;
|
||||
|
||||
}
|
||||
|
24
src/kiss.h
24
src/kiss.h
@@ -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
|
||||
|
131
src/main.cpp
131
src/main.cpp
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user