Habitat-lora-gateway/gateway.c

3010 wiersze
92 KiB
C

#include <features.h>
#include <features.h>
#define __USE_XOPEN
#include <stdio.h>
#include <stdio.h>
#include <curl/curl.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <stdint.h>
#include <stdarg.h>
#include <pthread.h>
#include <curses.h>
#include <math.h>
#include <dirent.h>
#include <wiringPi.h>
#include <wiringPiSPI.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ifaddrs.h>
#include "urlencode.h"
#include "base64.h"
#include "ssdv.h"
#include "ftp.h"
#include "sondehub.h"
#include "mqtt.h"
#include "network.h"
#include "network.h"
#include "global.h"
#include "server.h"
#include "gateway.h"
#include "config.h"
#include "gui.h"
#include "habpack.h"
#include "udpclient.h"
#include "lifo_buffer.h"
#define VERSION "V1.10.6"
bool run = TRUE;
// RFM98
uint8_t currentMode = 0x81;
#define REG_FIFO 0x00
#define REG_FIFO_ADDR_PTR 0x0D
#define REG_FIFO_TX_BASE_AD 0x0E
#define REG_FIFO_RX_BASE_AD 0x0F
#define REG_RX_NB_BYTES 0x13
#define REG_OPMODE 0x01
#define REG_FIFO_RX_CURRENT_ADDR 0x10
#define REG_IRQ_FLAGS 0x12
#define REG_PACKET_SNR 0x19
#define REG_PACKET_RSSI 0x1A
#define REG_CURRENT_RSSI 0x1B
#define REG_DIO_MAPPING_1 0x40
#define REG_DIO_MAPPING_2 0x41
#define REG_MODEM_CONFIG 0x1D
#define REG_MODEM_CONFIG2 0x1E
#define REG_MODEM_CONFIG3 0x26
#define REG_PAYLOAD_LENGTH 0x22
#define REG_IRQ_FLAGS_MASK 0x11
#define REG_HOP_PERIOD 0x24
#define REG_FREQ_ERROR 0x28
#define REG_DETECT_OPT 0x31
#define REG_DETECTION_THRESHOLD 0x37
#define REG_VERSION 0x42
// MODES
#define RF98_MODE_RX_CONTINUOUS 0x85
#define RF98_MODE_TX 0x83
#define RF98_MODE_SLEEP 0x80
#define RF98_MODE_STANDBY 0x81
#define PAYLOAD_LENGTH 255
// Modem Config 1
#define EXPLICIT_MODE 0x00
#define IMPLICIT_MODE 0x01
#define ERROR_CODING_4_5 0x02
#define ERROR_CODING_4_6 0x04
#define ERROR_CODING_4_7 0x06
#define ERROR_CODING_4_8 0x08
#define BANDWIDTH_7K8 0x00
#define BANDWIDTH_10K4 0x10
#define BANDWIDTH_15K6 0x20
#define BANDWIDTH_20K8 0x30
#define BANDWIDTH_31K25 0x40
#define BANDWIDTH_41K7 0x50
#define BANDWIDTH_62K5 0x60
#define BANDWIDTH_125K 0x70
#define BANDWIDTH_250K 0x80
#define BANDWIDTH_500K 0x90
// Modem Config 2
#define SPREADING_6 0x60
#define SPREADING_7 0x70
#define SPREADING_8 0x80
#define SPREADING_9 0x90
#define SPREADING_10 0xA0
#define SPREADING_11 0xB0
#define SPREADING_12 0xC0
#define CRC_OFF 0x00
#define CRC_ON 0x04
// POWER AMPLIFIER CONFIG
#define REG_PA_CONFIG 0x09
#define PA_MAX_BOOST 0x8F
#define PA_LOW_BOOST 0x81
#define PA_MED_BOOST 0x8A
#define PA_MAX_UK 0x88
#define PA_OFF_BOOST 0x00
#define RFO_MIN 0x00
// LOW NOISE AMPLIFIER
#define REG_LNA 0x0C
#define LNA_MAX_GAIN 0x23 // 0010 0011
#define LNA_OFF_GAIN 0x00
#define LNA_LOW_GAIN 0xC0 // 1100 0000
struct TLoRaMode
{
int ImplicitOrExplicit;
int ErrorCoding;
int Bandwidth;
int SpreadingFactor;
int LowDataRateOptimize;
int BaudRate;
char *Description;
} LoRaModes[] =
{
{EXPLICIT_MODE, ERROR_CODING_4_8, BANDWIDTH_20K8, SPREADING_11, 1, 60, "Telemetry"}, // 0: Normal mode for telemetry
{IMPLICIT_MODE, ERROR_CODING_4_5, BANDWIDTH_20K8, SPREADING_6, 0, 1400, "SSDV"}, // 1: Normal mode for SSDV
{EXPLICIT_MODE, ERROR_CODING_4_8, BANDWIDTH_62K5, SPREADING_8, 0, 2000, "Repeater"}, // 2: Normal mode for repeater network
{EXPLICIT_MODE, ERROR_CODING_4_6, BANDWIDTH_250K, SPREADING_7, 0, 8000, "Turbo"}, // 3: Normal mode for high speed images in 868MHz band
{IMPLICIT_MODE, ERROR_CODING_4_5, BANDWIDTH_250K, SPREADING_6, 0, 16828, "TurboX"}, // 4: Fastest mode within IR2030 in 868MHz band
{EXPLICIT_MODE, ERROR_CODING_4_8, BANDWIDTH_41K7, SPREADING_11, 0, 200, "Calling"}, // 5: Calling mode
// {EXPLICIT_MODE, ERROR_CODING_4_5, BANDWIDTH_20K8, SPREADING_7, 0, 2800, "Uplink"}, // 6: Uplink explicit mode (variable length)
{IMPLICIT_MODE, ERROR_CODING_4_5, BANDWIDTH_41K7, SPREADING_6, 0, 2800, "Uplink"}, // 6: Uplink mode for 868
{EXPLICIT_MODE, ERROR_CODING_4_5, BANDWIDTH_20K8, SPREADING_7, 0, 910, "Telnet"}, // 7: Telnet-style comms with HAB on 434
{IMPLICIT_MODE, ERROR_CODING_4_5, BANDWIDTH_62K5, SPREADING_6, 0, 4500, "SSDV Repeater"} // 8: Fast (SSDV) repeater network
};
struct TConfig Config;
struct TBandwidth
{
int LoRaValue;
double Bandwidth;
char *ConfigString;
} Bandwidths[] =
{
{BANDWIDTH_7K8, 7.8, "7K8"},
{BANDWIDTH_10K4, 10.4, "10K4"},
{BANDWIDTH_15K6, 15.6, "15K6"},
{BANDWIDTH_20K8, 20.8, "20K8"},
{BANDWIDTH_31K25, 31.25, "31K25"},
{BANDWIDTH_41K7, 41.7, "41K7"},
{BANDWIDTH_62K5, 62.5, "62K5"},
{BANDWIDTH_125K, 125.0, "125K"},
{BANDWIDTH_250K, 250.0, "250K"},
{BANDWIDTH_500K, 500.0, "500K"}
};
int LEDCounts[2];
int help_win_displayed = 0;
pthread_mutex_t var = PTHREAD_MUTEX_INITIALIZER;
#pragma pack(push,1)
struct TBinaryPacket {
uint8_t PayloadIDs;
uint16_t Counter;
uint16_t BiSeconds;
float Latitude;
float Longitude;
uint16_t Altitude;
};
#pragma pack(pop)
lifo_buffer_t MQTT_Upload_Buffer;
// Create pipes for inter proces communication
// GLOBAL AS CALLED FROM INTERRRUPT
int ssdv_pipe_fd[2];
// Create a structure to share some variables with the ssdv child process
// GLOBAL AS CALLED FROM INTERRRUPT
thread_shared_vars_t stsv;
WINDOW *mainwin=NULL; // Curses window
// Create a structure for saving calling mode settings
rx_metadata_t callingModeSettings[2];
void CloseDisplay( WINDOW * mainwin )
{
/* Clean up after ourselves */
delwin( mainwin );
endwin( );
refresh( );
}
void bye(void)
{
if (mainwin != NULL)
{
CloseDisplay( mainwin);
mainwin = NULL;
}
}
void exit_error(char *msg)
{
bye(); // Close ncurses window, plus any future tidy-ups
fprintf(stderr, msg);
exit(1);
}
void
hexdump_buffer( const char *title, const char *buffer, const int len_buffer )
{
int i, j = 0;
char message[200];
FILE *fp;
fp = fopen( "pkt.txt", "a" );
fprintf( fp, "Title = %s\n", title );
for ( i = 0; i < len_buffer; i++ )
{
sprintf( &message[3 * j], "%02x ", buffer[i] );
j++;
if ( i % 16 == 15 )
{
j = 0;
fprintf( fp, "%s\n", message );
message[0] = '\0';
}
}
fprintf( fp, "%s\n", message );
fclose( fp );
}
void
writeRegister( int Channel, uint8_t reg, uint8_t val )
{
unsigned char data[2];
data[0] = reg | 0x80;
data[1] = val;
wiringPiSPIDataRW( Channel, data, 2 );
}
uint8_t
readRegister( int Channel, uint8_t reg )
{
unsigned char data[2];
uint8_t val;
data[0] = reg & 0x7F;
data[1] = 0;
wiringPiSPIDataRW( Channel, data, 2 );
val = data[1];
return val;
}
void LogPacket( rx_metadata_t *Metadata, int Bytes, unsigned char MessageType )
{
if ( Config.EnablePacketLogging )
{
FILE *fp;
if ( ( fp = fopen( "packets.txt", "at" ) ) != NULL )
{
struct tm *tm;
tm = localtime( &Metadata->Timestamp );
fprintf( fp,
"%04d-%02d-%02d"
" %02d:%02d:%02d"
" - Ch %d"
", SNR %d"
", RSSI %d"
", Freq %.1lf"
", FreqErr %.1lf"
", BW %.2lf"
", EC 4:%d"
", SF %d"
", LDRO %d"
", Impl %d"
", Bytes %d"
", Type %02Xh\n",
(tm->tm_year + 1900), (tm->tm_mon + 1), tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
Metadata->Channel,
Metadata->SNR,
Metadata->RSSI,
Metadata->Frequency*1000, /* NB: in KHz */
Metadata->FrequencyError*1000, /* NB: in KHz */
Metadata->Bandwidth,
Metadata->ErrorCoding,
Metadata->SpreadingFactor,
Metadata->LowDataRateOptimize,
Metadata->ImplicitOrExplicit,
Bytes,
MessageType );
fclose( fp );
}
}
}
void LogTelemetryPacket(int Channel, char *Telemetry)
{
// if (Config.EnableTelemetryLogging)
{
FILE *fp;
if ( ( fp = fopen( "telemetry.txt", "at" ) ) != NULL )
{
time_t now;
struct tm *tm;
now = time( 0 );
tm = localtime( &now );
fprintf( fp, "%02d:%02d:%02d - %d - %s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, Telemetry);
fclose( fp );
}
}
}
void LogError(int ErrorCode, char *Message1, char *Message2)
{
// if (Config.EnableTelemetryLogging)
{
FILE *fp;
if ( ( fp = fopen( "errors.txt", "at" ) ) != NULL )
{
time_t now;
struct tm *tm;
now = time( 0 );
tm = localtime( &now );
fprintf( fp, "%02d:%02d:%02d: Error %d: %s%s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, ErrorCode, Message1, Message2);
fclose( fp );
}
}
}
void LogMessage( const char *format, ... )
{
static WINDOW *Window = NULL;
char Buffer[512];
pthread_mutex_lock( &var ); // lock the critical section
if ( Window == NULL )
{
// Window = newwin(25, 30, 0, 50);
Window = newwin( LINES - 16, COLS, 16, 0 );
scrollok( Window, TRUE );
}
va_list args;
va_start( args, format );
vsprintf( Buffer, format, args );
va_end( args );
if ( strlen( Buffer ) > COLS - 1 )
{
Buffer[COLS - 3] = '.';
Buffer[COLS - 2] = '.';
Buffer[COLS - 1] = '\n';
Buffer[COLS] = 0;
}
waddstr( Window, Buffer );
wrefresh( Window );
if (Config.DumpBuffer) {
FILE *dumpFilePtr;
dumpFilePtr = fopen((char*)Config.DumpFile, "a");
if (dumpFilePtr != NULL) {
fputs(Buffer, dumpFilePtr);
fclose(dumpFilePtr);
}
else {
fprintf( stderr, "Failed to open dump file %s\n", Config.DumpFile);
}
}
pthread_mutex_unlock( &var ); // unlock once you are done
}
void ChannelPrintf(int Channel, int row, int column, const char *format, ... )
{
char Buffer[80];
va_list args;
pthread_mutex_lock( &var ); // lock the critical section
va_start( args, format );
vsprintf( Buffer, format, args );
va_end( args );
mvwaddstr( Config.LoRaDevices[Channel].Window, row, column, Buffer );
if (! help_win_displayed)
{
wrefresh( Config.LoRaDevices[Channel].Window );
}
pthread_mutex_unlock( &var ); // unlock once you are done
}
void
setMode( int Channel, uint8_t newMode )
{
if ( newMode == currentMode )
return;
switch ( newMode )
{
case RF98_MODE_TX:
writeRegister( Channel, REG_LNA, LNA_OFF_GAIN ); // TURN LNA OFF FOR TRANSMITT
writeRegister( Channel, REG_PA_CONFIG, Config.LoRaDevices[Channel].Power ); // PA_MAX_UK
writeRegister( Channel, REG_OPMODE, newMode );
currentMode = newMode;
break;
case RF98_MODE_RX_CONTINUOUS:
writeRegister( Channel, REG_PA_CONFIG, PA_OFF_BOOST ); // TURN PA OFF FOR RECIEVE??
writeRegister( Channel, REG_LNA, LNA_MAX_GAIN ); // MAX GAIN FOR RECEIVE
writeRegister( Channel, REG_OPMODE, newMode );
currentMode = newMode;
// LogMessage("Changing to Receive Continuous Mode\n");
break;
case RF98_MODE_SLEEP:
writeRegister( Channel, REG_OPMODE, newMode );
currentMode = newMode;
// LogMessage("Changing to Sleep Mode\n");
break;
case RF98_MODE_STANDBY:
writeRegister( Channel, REG_OPMODE, newMode );
currentMode = newMode;
// LogMessage("Changing to Standby Mode\n");
break;
default:
return;
}
if ( newMode != RF98_MODE_SLEEP )
{
if (Config.LoRaDevices[Channel].DIO5 >= 0)
{
while (digitalRead(Config.LoRaDevices[Channel].DIO5) == 0)
{
}
}
else
{
delay(1);
}
}
// LogMessage("Mode Change Done\n");
return;
}
void setFrequency( int Channel, double Frequency )
{
unsigned long FrequencyValue;
char FrequencyString[10];
// Format frequency as xxx.xxx.x Mhz
sprintf( FrequencyString, "%8.4lf ", Frequency );
FrequencyString[8] = FrequencyString[7];
FrequencyString[7] = '.';
FrequencyValue = ( unsigned long ) (Frequency * (1.0 - Config.LoRaDevices[Channel].PPM/1000000.0) * 7110656 / 434 );
writeRegister( Channel, 0x06, ( FrequencyValue >> 16 ) & 0xFF ); // Set frequency
writeRegister( Channel, 0x07, ( FrequencyValue >> 8 ) & 0xFF );
writeRegister( Channel, 0x08, FrequencyValue & 0xFF );
ChannelPrintf( Channel, 1, 1, "Channel %d %s MHz ", Channel, FrequencyString );
}
void displayFrequency ( int Channel, double Frequency )
{
char FrequencyString[10];
// Format frequency as xxx.xxx.x Mhz
sprintf( FrequencyString, "%8.4lf ", Frequency );
FrequencyString[8] = FrequencyString[7];
FrequencyString[7] = '.';
ChannelPrintf( Channel, 1, 1, "Channel %d %s MHz ", Channel, FrequencyString );
}
void setLoRaMode( int Channel )
{
setMode( Channel, RF98_MODE_SLEEP );
writeRegister( Channel, REG_OPMODE, 0x80 );
setMode( Channel, RF98_MODE_SLEEP );
setFrequency( Channel, Config.LoRaDevices[Channel].Frequency + Config.LoRaDevices[Channel].FrequencyOffset);
}
int IntToSF(int Value)
{
return Value << 4;
}
int SFToInt(int SpreadingFactor)
{
return SpreadingFactor >> 4;
}
int IntToEC(int Value)
{
return (Value - 4) << 1;
}
int ECToInt(int ErrorCoding)
{
return (ErrorCoding >> 1) + 4;
}
int DoubleToBandwidth(double Bandwidth)
{
int i;
for (i=0; i<10; i++)
{
if (abs(Bandwidth - Bandwidths[i].Bandwidth) < (Bandwidths[i].Bandwidth/10))
{
return Bandwidths[i].LoRaValue;
}
}
return BANDWIDTH_20K8;
}
double BandwidthToDouble(int LoRaValue)
{
int i;
for (i=0; i<10; i++)
{
if (LoRaValue == Bandwidths[i].LoRaValue)
{
return Bandwidths[i].Bandwidth;
}
}
return 20.8;
}
int IntToLowOpt(int Value)
{
return Value ? 0x08 : 0;
}
int LowOptToInt(int LowOpt)
{
return LowOpt ? 1 : 0;
}
void SetLoRaParameters( int Channel, int ImplicitOrExplicit, int ErrorCoding, double Bandwidth, int SpreadingFactor, int LowDataRateOptimize )
{
writeRegister( Channel, REG_MODEM_CONFIG, (ImplicitOrExplicit ? IMPLICIT_MODE : EXPLICIT_MODE) | IntToEC(ErrorCoding) | DoubleToBandwidth(Bandwidth));
writeRegister( Channel, REG_MODEM_CONFIG2, IntToSF(SpreadingFactor) | CRC_ON );
writeRegister( Channel, REG_MODEM_CONFIG3, 0x04 | IntToLowOpt(LowDataRateOptimize)); // 0x04: AGC sets LNA gain
writeRegister( Channel, REG_DETECT_OPT, ( readRegister( Channel, REG_DETECT_OPT ) & 0xF8 ) | ( ( SpreadingFactor == 6 ) ? 0x05 : 0x03 ) ); // 0x05 For SF6; 0x03 otherwise
writeRegister( Channel, REG_DETECTION_THRESHOLD, ( SpreadingFactor == 6 ) ? 0x0C : 0x0A ); // 0x0C for SF6, 0x0A otherwise
Config.LoRaDevices[Channel].CurrentBandwidth = Bandwidth; // Used for AFC - current bandwidth may be different to that configured (i.e. because we're using calling mode)
ChannelPrintf( Channel, 2, 1, "%s, %.2lf, SF%d, EC4:%d %s",
ImplicitOrExplicit ? "Implicit" : "Explicit",
Bandwidth,
SpreadingFactor,
ErrorCoding,
LowDataRateOptimize ? "LDRO" : "" );
}
void displayLoRaParameters( int Channel, int ImplicitOrExplicit, int ErrorCoding, double Bandwidth, int SpreadingFactor, int LowDataRateOptimize )
{
ChannelPrintf( Channel, 2, 1, "%s, %.2lf, SF%d, EC4:%d %s",
ImplicitOrExplicit ? "Implicit" : "Explicit",
Bandwidth,
SpreadingFactor,
ErrorCoding,
LowDataRateOptimize ? "LDRO" : "" );
}
void SetDefaultLoRaParameters( int Channel )
{
// LogMessage("Set Default Parameters\n");
SetLoRaParameters( Channel,
Config.LoRaDevices[Channel].ImplicitOrExplicit,
Config.LoRaDevices[Channel].ErrorCoding,
Config.LoRaDevices[Channel].Bandwidth,
Config.LoRaDevices[Channel].SpreadingFactor,
Config.LoRaDevices[Channel].LowDataRateOptimize );
}
/////////////////////////////////////
// Method: Setup to receive continuously
//////////////////////////////////////
void startReceiving(int Channel)
{
writeRegister( Channel, REG_DIO_MAPPING_1, 0x00 ); // 00 00 00 00 maps DIO0 to RxDone
writeRegister( Channel, REG_PAYLOAD_LENGTH, 255 );
writeRegister( Channel, REG_RX_NB_BYTES, 255 );
writeRegister( Channel, REG_FIFO_RX_BASE_AD, 0 );
writeRegister( Channel, REG_FIFO_ADDR_PTR, 0 );
// Setup Receive Continous Mode
setMode( Channel, RF98_MODE_RX_CONTINUOUS );
}
void ReTune( int Channel, double FreqShift )
{
setMode( Channel, RF98_MODE_SLEEP );
LogMessage( "Ch%d: Retune by %.1lfkHz\n", Channel, FreqShift * 1000 );
Config.LoRaDevices[Channel].FrequencyOffset += FreqShift;
setFrequency(Channel, Config.LoRaDevices[Channel].Frequency + Config.LoRaDevices[Channel].FrequencyOffset);
startReceiving(Channel);
}
void SendLoRaData(int Channel, char *buffer, int Length)
{
unsigned char data[257];
int i;
// Change frequency for the uplink ?
if (Config.LoRaDevices[Channel].UplinkFrequency > 0)
{
LogMessage("Ch%d: Change frequency to %.3lfMHz\n", Channel, Config.LoRaDevices[Channel].UplinkFrequency + Config.LoRaDevices[Channel].FrequencyOffset);
setFrequency(Channel, Config.LoRaDevices[Channel].UplinkFrequency + Config.LoRaDevices[Channel].FrequencyOffset);
}
// Change mode for the uplink ?
if (Config.LoRaDevices[Channel].UplinkMode >= 0)
{
int UplinkMode;
UplinkMode = Config.LoRaDevices[Channel].UplinkMode;
LogMessage("Ch%d: Change LoRa mode to %d\n", Channel, Config.LoRaDevices[Channel].UplinkMode);
SetLoRaParameters(Channel,
LoRaModes[UplinkMode].ImplicitOrExplicit,
ECToInt(LoRaModes[UplinkMode].ErrorCoding),
BandwidthToDouble(LoRaModes[UplinkMode].Bandwidth),
SFToInt(LoRaModes[UplinkMode].SpreadingFactor),
0);
// Adjust length if necessary - for implicit mode we always use 255-byte packets
}
LogMessage("LoRa Channel %d Sending %d bytes\n", Channel, Length );
/* prints the message's content for debug purposes
int n;
i = Length;
if (i > 24)
i = 24;
char str[5];
char superstr[100];
sprintf(superstr, "Ch%d: TX %d bytes, ", Channel, Length);
for(n = 0; n < i; ++n)
{
sprintf(str, "%02X ", buffer[n]);
strcat(superstr, str);
}
strcat(superstr, "\n");
LogMessage(superstr);
*/
Config.LoRaDevices[Channel].Sending = 1;
setMode( Channel, RF98_MODE_STANDBY );
writeRegister( Channel, REG_DIO_MAPPING_1, 0x40 ); // 01 00 00 00 maps DIO0 to TxDone
writeRegister( Channel, REG_FIFO_TX_BASE_AD, 0x00 ); // Update the address ptr to the current tx base address
writeRegister( Channel, REG_FIFO_ADDR_PTR, 0x00 );
data[0] = REG_FIFO | 0x80;
for ( i = 0; i < Length; i++ )
{
data[i + 1] = buffer[i];
}
// Set the length. For implicit mode, since the length needs to match what the receiver expects, we have to set the length to our fixed 255 bytes
if (Config.LoRaDevices[Channel].UplinkMode >= 0)
{
if (LoRaModes[Config.LoRaDevices[Channel].UplinkMode].ImplicitOrExplicit)
{
if (Length+1 < sizeof(data)) // places a NULL at end of data to allow the receiver to skip any garbage
data[Length+1] = 0;
Length = 255;
LogMessage("Ch%d: length set to 255 bytes (Uplink implicit mode tx)\n", Channel);
}
}
else if (Config.LoRaDevices[Channel].ImplicitOrExplicit)
{
if (Length+1 < sizeof(data)) // places a NULL at end of data to allow the receiver to skip any garbage
data[Length+1] = 0;
Length = 255;
LogMessage("Ch%d: length set to 255 bytes (implicit mode tx)\n", Channel);
}
wiringPiSPIDataRW( Channel, data, Length + 1 ); // SPI write moved here (after NULL termination)
// Now send the (possibly updated) length in the LoRa chip
writeRegister( Channel, REG_PAYLOAD_LENGTH, Length );
// go into transmit mode
setMode( Channel, RF98_MODE_TX );
}
void ShowPacketCounts(int Channel)
{
if (Config.LoRaDevices[Channel].InUse)
{
ChannelPrintf( Channel, 7, 1, "Telem Packets = %d (%us) ",
Config.LoRaDevices[Channel].TelemetryCount,
Config.LoRaDevices[Channel].LastTelemetryPacketAt ? (unsigned int) (time(NULL) - Config.LoRaDevices[Channel].LastTelemetryPacketAt) : 0);
ChannelPrintf( Channel, 8, 1, "Image Packets = %d (%us) ",
Config.LoRaDevices[Channel].SSDVCount,
Config.LoRaDevices[Channel].
LastSSDVPacketAt ? ( unsigned int ) ( time( NULL ) -
Config.
LoRaDevices
[Channel].
LastSSDVPacketAt )
: 0 );
ChannelPrintf( Channel, 9, 1, "Bad CRC = %d Bad Type = %d",
Config.LoRaDevices[Channel].BadCRCCount,
Config.LoRaDevices[Channel].UnknownCount );
}
}
void ProcessUploadMessage(int Channel, char *Message)
{
// LogMessage("Ch %d: Gateway Uplink Message %s\n", Channel, Message);
}
void ProcessCallingMessage(int Channel, char *Message)
{
char Payload[32];
double Frequency;
int ImplicitOrExplicit, ErrorCoding, Bandwidth, SpreadingFactor, LowDataRateOptimize;
ChannelPrintf( Channel, 3, 1, "Calling message %d bytes ",
strlen( Message ) );
if ( sscanf( Message + 2, "%31[^,],%lf,%d,%d,%d,%d,%d",
Payload,
&Frequency,
&ImplicitOrExplicit,
&ErrorCoding,
&Bandwidth, &SpreadingFactor, &LowDataRateOptimize ) == 7 )
{
if (Config.LoRaDevices[Channel].AFC)
{
Frequency += Config.LoRaDevices[Channel].FrequencyOffset;
}
LogMessage( "Ch %d: Calling message, new frequency %7.3lf\n", Channel,
Frequency );
// Decoded OK
setMode( Channel, RF98_MODE_SLEEP );
setFrequency( Channel, Frequency );
SetLoRaParameters( Channel, ImplicitOrExplicit, ECToInt(ErrorCoding), BandwidthToDouble(Bandwidth), SFToInt(SpreadingFactor), LowOptToInt(LowDataRateOptimize));
setMode( Channel, RF98_MODE_RX_CONTINUOUS );
Config.LoRaDevices[Channel].InCallingMode = 1;
// save the new settings so that we can restore them after an UpLink cycle, instead of going back to calling mode listening and having to wait for
// a calling mode packet before being able to decode tracker's messages.
// note - when booting, the tracker may send a dummy calling mode packet using the standard frequency/mode (instead the calling_mode settings).
// As a result, if received by a gateway running normally (not set for calling mode) it will trigger the calling mode state even if not really necessary
// (if the gateway received that packet, it's already set on correct parameters).
// This poses no problems except for the message displayed when exiting the uplink mode, that will be "Restoring saved calling_mode Rx Settings"
// instead of the (proper) message "Restoring default Rx Settings".
//
callingModeSettings[Channel].Channel = Channel; // this field is used just to validate the data
callingModeSettings[Channel].Frequency = Frequency; // saved frequency is already AFC corrected
callingModeSettings[Channel].ImplicitOrExplicit = ImplicitOrExplicit;
callingModeSettings[Channel].ErrorCoding = ECToInt(ErrorCoding);
callingModeSettings[Channel].Bandwidth = BandwidthToDouble(Bandwidth);
callingModeSettings[Channel].SpreadingFactor = SFToInt(SpreadingFactor);
callingModeSettings[Channel].LowDataRateOptimize = LowOptToInt(LowDataRateOptimize);
}
}
void ProcessCallingHABpack(int Channel, received_t *Received)
{
double Frequency;
ChannelPrintf( Channel, 3, 1, "Calling message (HABpack)");
Frequency = (double)Received->Telemetry.DownlinkFrequency / 1000000;
if (Config.LoRaDevices[Channel].AFC)
{
Frequency += Config.LoRaDevices[Channel].FrequencyOffset;
}
// Decoded OK
setMode( Channel, RF98_MODE_SLEEP );
setFrequency( Channel, Frequency );
if(Received->Telemetry.DownlinkLoraMode >= 0)
{
SetLoRaParameters(Channel,
LoRaModes[Received->Telemetry.DownlinkLoraMode].ImplicitOrExplicit,
ECToInt(LoRaModes[Received->Telemetry.DownlinkLoraMode].ErrorCoding),
BandwidthToDouble(LoRaModes[Received->Telemetry.DownlinkLoraMode].Bandwidth),
SFToInt(LoRaModes[Received->Telemetry.DownlinkLoraMode].SpreadingFactor),
LowOptToInt(LoRaModes[Received->Telemetry.DownlinkLoraMode].LowDataRateOptimize)
);
}
else
{
SetLoRaParameters(Channel,
Received->Telemetry.DownlinkLoraImplicit,
Received->Telemetry.DownlinkLoraErrorCoding,
Received->Telemetry.DownlinkLoraBandwidth,
Received->Telemetry.DownlinkLoraSpreadingFactor,
Received->Telemetry.DownlinkLoraLowDatarateOptimise
);
}
setMode( Channel, RF98_MODE_RX_CONTINUOUS );
LogMessage( "Ch %d: Calling message, new frequency %7.3lf\n", Channel,
Frequency );
Config.LoRaDevices[Channel].InCallingMode = 1;
}
void RemoveOldPayloads(void)
{
int i;
for (i=0; i<MAX_PAYLOADS; i++)
{
if (Config.Payloads[i].InUse)
{
if ((time(NULL) - Config.Payloads[i].LastPacketAt) > 10800)
{
// More than 3 hours old, so remove it
Config.Payloads[i].InUse = 0;
}
}
}
}
int FindFreePayload(char *Payload)
{
int i, Oldest;
// First pass - find match for payload
for (i=0; i<MAX_PAYLOADS; i++)
{
if (Config.Payloads[i].InUse)
{
if (strcmp(Payload, Config.Payloads[i].Payload) == 0)
{
return i;
}
}
}
// Second pass - just find a free position
for (i=0; i<MAX_PAYLOADS; i++)
{
if (!Config.Payloads[i].InUse)
{
Config.Payloads[i].InUse = 1;
strcpy(Config.Payloads[i].Payload, Payload);
return i;
}
}
// Third pass - find oldest payload
Oldest = 0;
for (i=1; i<MAX_PAYLOADS; i++)
{
if (Config.Payloads[i].LastPositionAt < Config.Payloads[Oldest].LastPositionAt)
{
Oldest = i;
}
}
strcpy(Config.Payloads[Oldest].Payload, Payload);
return Oldest;
}
void DoPositionCalcs(int PayloadIndex)
{
unsigned long Now;
struct tm tm;
float Climb, Period;
strptime(Config.Payloads[PayloadIndex].Time, "%H:%M:%S", &tm);
Now = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec;
if ((Config.Payloads[PayloadIndex].LastPositionAt > 0 )
&& ( Now > Config.Payloads[PayloadIndex].LastPositionAt ) )
{
Climb = (float)Config.Payloads[PayloadIndex].Altitude - (float)Config.Payloads[PayloadIndex].PreviousAltitude;
Period = (float)Now - (float)Config.Payloads[PayloadIndex].LastPositionAt;
Config.Payloads[PayloadIndex].AscentRate = Climb / Period;
}
else
{
Config.Payloads[PayloadIndex].AscentRate = 0;
}
Config.Payloads[PayloadIndex].LastPositionAt = Now;
Config.Payloads[PayloadIndex].PreviousAltitude = Config.Payloads[PayloadIndex].Altitude;
}
void ProcessLineUKHAS(int Channel, char *Line)
{
int PayloadIndex;
char Payload[32];
// Find free position for this payload
sscanf(Line + 2, "%31[^,]", Payload);
PayloadIndex = FindFreePayload(Payload);
// Store sentence against this payload
strcpy(Config.Payloads[PayloadIndex].Telemetry, Line);
// Fill in source channel
Config.Payloads[PayloadIndex].Channel = Channel;
// Parse key fields from sentence
sscanf( Line + 2, "%31[^,],%u,%8[^,],%lf,%lf,%d",
(Config.Payloads[PayloadIndex].Payload),
&(Config.Payloads[PayloadIndex].Counter),
(Config.Payloads[PayloadIndex].Time),
&(Config.Payloads[PayloadIndex].Latitude),
&(Config.Payloads[PayloadIndex].Longitude),
&(Config.Payloads[PayloadIndex].Altitude));
// Mark when this was received, so we can time-out old payloads
Config.Payloads[PayloadIndex].LastPacketAt = time(NULL);
// Mark for server socket to send to client
Config.Payloads[PayloadIndex].SendToClients = 1;
// Ascent rate
DoPositionCalcs(PayloadIndex);
// Update display
ChannelPrintf(Channel, 4, 1, "%8.5lf, %8.5lf, %05d ",
Config.Payloads[PayloadIndex].Latitude,
Config.Payloads[PayloadIndex].Longitude,
Config.Payloads[PayloadIndex].Altitude);
// Send out to any OziPlotter clients
if (Config.OziPlotterPort > 0)
{
char OziSentence[200];
sprintf(OziSentence, "TELEMETRY,%s,%lf,%lf,%d\n",
Config.Payloads[PayloadIndex].Time,
Config.Payloads[PayloadIndex].Latitude,
Config.Payloads[PayloadIndex].Longitude,
Config.Payloads[PayloadIndex].Altitude);
UDPSend(OziSentence, Config.OziPlotterPort);
}
// Send out to any OziMux clients
if (Config.OziMuxPort > 0)
{
char OziSentence[512];
snprintf(OziSentence, 511,
"{\"type\":\"PAYLOAD_TELEMETRY\""
",\"callsign\":\"%s\""
",\"time_string\":\"%s\""
",\"latitude\":\"%lf\""
",\"longitude\":\"%lf\""
",\"altitude\":\"%d\"}",
Config.Payloads[PayloadIndex].Payload,
Config.Payloads[PayloadIndex].Time,
Config.Payloads[PayloadIndex].Latitude,
Config.Payloads[PayloadIndex].Longitude,
Config.Payloads[PayloadIndex].Altitude
);
UDPSend(OziSentence, Config.OziMuxPort);
}
}
void ProcessLineHABpack(int Channel, received_t *Received)
{
int PayloadIndex;
PayloadIndex = FindFreePayload(Received->Telemetry.Callsign);
// Store sentence against this payload
strcpy(Config.Payloads[PayloadIndex].Telemetry, Received->UKHASstring);
// Fill in source channel
Config.Payloads[PayloadIndex].Channel = Channel;
strncpy(Config.Payloads[PayloadIndex].Payload, Received->Telemetry.Callsign, 15);
Config.Payloads[PayloadIndex].Counter = Received->Telemetry.SentenceId;
strncpy(Config.Payloads[PayloadIndex].Time, Received->Telemetry.TimeString, 8);
Config.Payloads[PayloadIndex].Latitude = Received->Telemetry.Latitude;
Config.Payloads[PayloadIndex].Longitude = Received->Telemetry.Longitude;
Config.Payloads[PayloadIndex].Altitude = Received->Telemetry.Altitude;
// Mark when this was received, so we can time-out old payloads
Config.Payloads[PayloadIndex].LastPacketAt = time(NULL);
// Mark for server socket to send to client
Config.Payloads[PayloadIndex].SendToClients = 1;
// Ascent rate
DoPositionCalcs(PayloadIndex);
// Update display
ChannelPrintf(Channel, 4, 1, "%8.5lf, %8.5lf, %05d ",
Config.Payloads[PayloadIndex].Latitude,
Config.Payloads[PayloadIndex].Longitude,
Config.Payloads[PayloadIndex].Altitude);
// Send out to any OziPlotter clients
if (Config.OziPlotterPort > 0)
{
char OziSentence[200];
sprintf(OziSentence, "TELEMETRY,%s,%lf,%lf,%d\n",
Config.Payloads[PayloadIndex].Time,
Config.Payloads[PayloadIndex].Latitude,
Config.Payloads[PayloadIndex].Longitude,
Config.Payloads[PayloadIndex].Altitude);
UDPSend(OziSentence, Config.OziPlotterPort);
}
// Send out to any OziMux clients
if (Config.OziMuxPort > 0)
{
char OziSentence[512];
Habpack_Telem_JSON(Received, OziSentence, 511);
UDPSend(OziSentence, Config.OziMuxPort);
}
}
int ProcessTelemetryMessage(int Channel, received_t *Received)
{
int Repeated = 0;
if (strlen(Received->UKHASstring) < 250)
{
char *startmessage, *endmessage;
char telem[40];
char buffer[40];
sprintf(telem, "Telemetry %d bytes", strlen( Received->UKHASstring ));
// Pad the string with spaces to the size of the window
sprintf(buffer,"%-37s", telem );
ChannelPrintf( Channel, 3, 1, buffer);
startmessage = Received->UKHASstring + strspn(Received->UKHASstring, "$%") - 2;
endmessage = strchr( startmessage, '\n' );
if (endmessage == NULL) endmessage = strchr(startmessage, 0);
while ( endmessage != NULL )
{
struct tm *tm;
tm = localtime( &Received->Metadata.Timestamp );
*endmessage = '\0';
if (ValidCRC16(startmessage))
{
LogTelemetryPacket(Channel, startmessage);
ProcessLineUKHAS(Channel, startmessage);
if ((Repeated = (*startmessage == '%')))
{
*startmessage = '$';
}
strcpy(Config.LoRaDevices[Channel].Telemetry, startmessage);
Config.LoRaDevices[Channel].TelemetryCount++;
if (Config.EnableMQTT)
{
// Add to MQTT upload queue
received_t *queueReceived = malloc(sizeof(received_t));
if(queueReceived != NULL)
{
memcpy(queueReceived, Received, sizeof(received_t));
/* Push pointer onto upload queue */
lifo_buffer_push(&MQTT_Upload_Buffer, (void *)queueReceived);
}
}
if (Config.EnableSondehub)
{
SetSondehubSentence(Channel, startmessage);
}
LogMessage("%02d:%02d:%02d Ch%d: %s%s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, startmessage, Repeated ? " (repeated)" : "");
}
else
{
LogMessage("%02d:%02d:%02d Ch%d: %s%s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, "Bad UKHAS CRC: ", startmessage);
}
startmessage = endmessage + 1;
endmessage = strchr( startmessage, '\n' );
}
Config.LoRaDevices[Channel].LastTelemetryPacketAt = Received->Metadata.Timestamp;
}
return Repeated;
}
void CheckForChatContent(int Channel, int Repeated, char *Line)
{
if (Config.LoRaDevices[Channel].ChatMode)
{
char PayloadID[32], Message[200];
int RxMask, RxMessageID, MessageID;
if (Repeated)
{
LogMessage("Repeated sentence [%s]\n", Line);
Message[0] = 0;
sscanf(Line+2, "%31[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%x,%d,%d,%[^*]", PayloadID, &RxMask, &RxMessageID, &MessageID, Message);
LogMessage("PayloadID=%s RxMask=%x RxMessageID=%d MessageID=%d Message=%s\n", PayloadID, RxMask, RxMessageID, MessageID, Message);
if (strcmp(PayloadID, Config.LoRaDevices[Channel].ChatPayloadID) != 0)
{
// Repeated from a payload other than "ours"
// Get message/ID from remote gateway
Config.LoRaDevices[Channel].RxMessageID = MessageID;
strcpy(Config.LoRaDevices[Channel].RxChatMessage, Message);
if (*Message)
{
LogMessage("READY FOR TERMINAL\n");
}
// Check to see if remote gateway has seen our last message
if (RxMessageID == Config.LoRaDevices[Channel].TxMessageID)
{
// Yes, it has, so now clear the message we uplink
Config.LoRaDevices[Channel].TxChatMessage[0] = 0;
}
}
}
else
{
// LogMessage("Normal sentence [%s]\n", Line);
// Message[0] = 0;
// sscanf(Line+2, "%31[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%d,%d,%[^*]", PayloadID, &RxMask, &MessageID, Message);
// LogMessage("PayloadID=%s RxMask=%d MessageID=%d Message=%s\n", PayloadID, RxMask, MessageID, Message);
}
}
}
void ProcessTelnetMessage(int Channel, char *Message, int Bytes)
{
char FirstByte;
FirstByte = Message[1];
Config.LoRaDevices[Channel].GotHABReply = 1;
LogMessage( "Telnet Downlink message channel %d %c[%02X]'%s' bytes = %d\n", Channel, Message[0], FirstByte, Message+2, Bytes);
// Store header details which are used in next Tx
Config.LoRaDevices[Channel].HABConnected = (FirstByte & 0x80) ? 1 : 0;
Config.LoRaDevices[Channel].HABAck = (FirstByte & 0x40) ? 1 : 0;
if (Bytes > 2)
{
strcpy(Config.LoRaDevices[Channel].ToTelnetBuffer, Message+2);
Config.LoRaDevices[Channel].ToTelnetBufferCount = Bytes-2;
}
}
static char *decode_callsign( char *callsign, uint32_t code )
{
char *c, s;
*callsign = '\0';
/* Is callsign valid? */
if ( code > 0xF423FFFF )
return ( callsign );
for ( c = callsign; code; c++ )
{
s = code % 40;
if ( s == 0 )
*c = '-';
else if ( s < 11 )
*c = '0' + s - 1;
else if ( s < 14 )
*c = '-';
else
*c = 'A' + s - 14;
code /= 40;
}
*c = '\0';
return ( callsign );
}
int
FileExists( char *filename )
{
struct stat st;
return stat( filename, &st ) == 0;
}
void ProcessSSDVMessage( int Channel, char *Message_in, int Repeated)
{
// SSDV packet
char Message[257];
uint32_t CallsignCode;
char Callsign[7], *FileMode;
int ImageNumber, PacketNumber;
char filename[100];
FILE *fp;
time_t now;
struct tm *tm;
now = time( 0 );
tm = localtime( &now );
// Move into new buffer with preceding char
memcpy((Message + 1), Message_in, 256);
Message[0] = 0x55;
CallsignCode = Message[2];
CallsignCode <<= 8;
CallsignCode |= Message[3];
CallsignCode <<= 8;
CallsignCode |= Message[4];
CallsignCode <<= 8;
CallsignCode |= Message[5];
decode_callsign( Callsign, CallsignCode );
ImageNumber = Message[6];
PacketNumber = Message[7] * 256 + Message[8];
LogMessage("%02d:%02d:%02d Ch%d: SSDV Packet, Callsign %s, Image %d, Packet %d%s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, Callsign, Message[6], PacketNumber, Repeated ? " (repeated)" : "");
ChannelPrintf( Channel, 3, 1, "SSDV Packet " );
ChannelPrintf( Channel, 5, 1, "SSDV %s: Image %d, Packet %d", Callsign,
Message[6], PacketNumber );
// Create new file ?
sprintf( filename, "/tmp/%s_%d.bin", Callsign, ImageNumber );
if ( !FileExists( filename ) )
{
// New image so new file
LogMessage( "Started image %d\n", ImageNumber );
// AddImageNumberToLog(Channel, ImageNumber);
FileMode = "wb";
}
else
{
FileMode = "r+b";
}
// Save to file
if ( ( fp = fopen( filename, FileMode ) ) )
{
fseek( fp, PacketNumber * 256, SEEK_SET );
if ( fwrite( Message, 1, 256, fp ) == 256 )
{
// AddImagePacketToLog(Channel, ImageNumber, PacketNumber);
}
else
{
LogMessage( "** FAILED TO WRITE TO SSDV FILE\n" );
}
fclose( fp );
}
// ShowMissingPackets(Channel);
if ( Config.EnableSSDV )
{
// Create a SSDV packet
ssdv_t s;
s.Channel = Channel;
s.Packet_Number = Config.LoRaDevices[Channel].SSDVCount;
memcpy( s.SSDV_Packet, Message, 256 );
// Add the SSDV packet to the pipe
int result = write( ssdv_pipe_fd[1], &s, sizeof( s ) );
if ( result == -1 )
{
exit_error("Error writing to the issdv pipe\n");
}
if ( result == 0 )
{
LogMessage( "Nothing written to ssdv pipe \n" );
}
if ( result > 1 )
{
stsv.packet_count++;
}
}
Config.LoRaDevices[Channel].SSDVCount++;
Config.LoRaDevices[Channel].LastSSDVPacketAt = time( NULL );
}
void
TestMessageForSMSAcknowledgement( int Channel, char *Message )
{
if ( Config.SMSFolder[0] )
{
if ( strlen( Message ) < 250 )
{
char Line[256];
char *token, *value1, *value2;
int FileNumber;
value1 = NULL;
value2 = NULL;
strcpy( Line, Message );
token = strtok( Line, "," );
while ( token != NULL )
{
value1 = value2;
value2 = token;
token = strtok( NULL, "," );
}
// Rename the file matching this parameter
if ( value1 )
{
char OldFileName[256], NewFileName[256];
FileNumber = atoi( value1 );
if ( FileNumber > 0 )
{
sprintf( OldFileName, "%s/%d.sms", Config.SMSFolder, FileNumber );
if ( FileExists( OldFileName ) )
{
sprintf( NewFileName, "%s/%d.ack", Config.SMSFolder, FileNumber );
if ( FileExists( NewFileName ) )
{
remove( NewFileName );
}
rename( OldFileName, NewFileName );
LogMessage( "Renamed %s as %s\n", OldFileName,
NewFileName );
}
}
}
}
}
}
int FixRSSI(int Channel, int RawRSSI, int SNR)
{
int RSSI;
if (Config.LoRaDevices[Channel].Frequency > 525)
{
// HF port (band 1)
RSSI = RawRSSI - 157;
}
else
{
// LF port (Bands 2/3)
RSSI = RawRSSI - 164;
}
if (SNR < 0)
{
RSSI += SNR;
}
return RSSI;
}
int CurrentRSSI(int Channel)
{
return FixRSSI(Channel, readRegister(Channel, REG_CURRENT_RSSI), 0);
}
int PacketSNR(int Channel)
{
int8_t SNR;
SNR = readRegister(Channel, REG_PACKET_SNR);
SNR /= 4;
return (int)SNR;
}
int PacketRSSI(int Channel)
{
int SNR;
SNR = PacketSNR(Channel);
return FixRSSI(Channel, readRegister(Channel, REG_PACKET_RSSI), SNR);
}
int GetTextMessageToUpload( int Channel, char *Message )
{
DIR *dp;
struct dirent *ep;
int Result;
Result = 0;
if ( Config.SMSFolder[0] )
{
LogMessage("Ch%d: Checking for SMS file in '%s' folder ...\n", Channel, Config.SMSFolder);
dp = opendir( Config.SMSFolder );
if ( dp != NULL )
{
while ( ( ep = readdir( dp ) ) && !Result )
{
if ( strstr( ep->d_name, ".sms" ) )
{
FILE *fp;
char Line[256], FileName[256];
int FileNumber;
sprintf( FileName, "%s/%s", Config.SMSFolder, ep->d_name );
sscanf( ep->d_name, "%d", &FileNumber );
if ( ( fp = fopen( FileName, "rt" ) ) != NULL )
{
if ( fscanf( fp, "%[^\r]", Line ) )
{
// #001,@daveake: Good Luck Tim !!\n
sprintf( Message, "#%d,%s\n", FileNumber, Line );
LogMessage( "UPLINK: %s", Message );
Result = 1;
}
else
{
LogMessage( "FAIL\n" );
}
fclose( fp );
}
}
}
closedir( dp );
}
else
{
LogMessage("Failed to open folder - error code %d\n", errno);
}
}
return Result;
}
int GetExternalListOfMissingSSDVPackets( int Channel, char *Message )
{
// First, create request file
FILE *fp;
if (Config.LoRaDevices[Channel].SSDVUplink)
{
int i;
// Now wait for uplink.txt file to appear.
// Timeout before the end of our Tx slot if no file appears
// Timeout reduced from 2sec to 300ms. This allows using two channels for UpLink transmission (more chances to reach the tracker).
// A 2sec timeout would probably delay the second channel's transmission out of the uplink time slot.
// for ( i = 0; i < 20; i++ )
for ( i = 0; i < 3; i++ )
{
if ( ( fp = fopen( "uplink.txt", "r" ) ) )
{
Message[0] = '\0';
fgets( Message, 256, fp );
fclose( fp );
LogMessage( "Got uplink.txt %d bytes\n", strlen( Message ) );
// remove("get_list.txt");
// To allow uplink transmission with both channels, the file is only deleted if:
// - we are transmitting with the second (last) channel;
// - we are transmitting with first channel and second channel is not configured for uplink mode.
// If the file is created just between ch0 and ch1 transmission, only ch1 transmission will take place.
// This is acceptable.
if (Channel == 1 || Config.LoRaDevices[1].UplinkTime < 0)
{
remove( "uplink.txt" );
LogMessage( "uplink.txt deleted\n" );
}
else
LogMessage( "uplink.txt not deleted to allow Ch1 transmission\n" );
return strlen( Message );
}
usleep( 100000 );
}
// LogMessage("Timed out waiting for file\n");
// remove("get_list.txt");
}
return 0;
}
void SendUplinkMessage(int Channel)
{
char Message[512];
time_t now;
struct tm *tm;
now = time(0);
tm = localtime(&now);
// Decide what type of message we need to send
if (*Config.LoRaDevices[Channel].UplinkMessage)
{
LogMessage("%02d:%02d:%02d Ch%d - Send uplink message '%s'\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, Config.LoRaDevices[Channel].UplinkMessage);
SendLoRaData(Channel, Config.LoRaDevices[Channel].UplinkMessage, strlen(Config.LoRaDevices[Channel].UplinkMessage)+1);
if (!Config.LoRaDevices[Channel].ChatMode)
{
// Not re-sending
*Config.LoRaDevices[Channel].UplinkMessage = 0;
}
}
else if (GetTextMessageToUpload(Channel, Message))
{
SendLoRaData(Channel, Message, strlen(Message));
}
else if (GetExternalListOfMissingSSDVPackets( Channel, Message))
{
SendLoRaData(Channel, Message, strlen(Message));
}
else if (Config.LoRaDevices[Channel].IdleUplink)
{
SendLoRaData(Channel, "", 1);
}
}
void ProcessSyncMessage(int Channel, char *Message, int Bytes)
{
if (Config.LoRaDevices[Channel].ChatMode)
{
if (strcmp(Message+1, Config.LoRaDevices[Channel].ChatPayloadID) == 0)
{
// Sync for us
sprintf(Config.LoRaDevices[Channel].UplinkMessage, "!%s,%d,%d,%s", Config.LoRaDevices[Channel].ChatPayloadID,
Config.LoRaDevices[Channel].RxMessageID,
Config.LoRaDevices[Channel].TxMessageID,
Config.LoRaDevices[Channel].TxChatMessage);
SendUplinkMessage(Channel);
UDPSend(Config.LoRaDevices[Channel].UplinkMessage, Config.UDPPort);
}
}
}
void DIO0_Interrupt( int Channel )
{
if ( Config.LoRaDevices[Channel].Sending )
{
Config.LoRaDevices[Channel].Sending = 0;
// LogMessage( "Ch%d: End of Tx\n", Channel );
// restoring radio settings after uplink transmission:
// If 'InCallingMode' is true, the receiver was set by a calling mode packet. In this case, the radio is returned to that settings
// instead of going back to calling mode (and having to wait for a calling mode packet before being able to receive data).
//
if (Config.LoRaDevices[Channel].InCallingMode && callingModeSettings[Channel].Channel == Channel)
{
// these four rows are equivalent to 'setLoRaMode', but with custom frequency
setMode( Channel, RF98_MODE_SLEEP );
writeRegister( Channel, REG_OPMODE, 0x80 );
setMode( Channel, RF98_MODE_SLEEP );
setFrequency( Channel, callingModeSettings[Channel].Frequency);
SetLoRaParameters( Channel,
callingModeSettings[Channel].ImplicitOrExplicit,
callingModeSettings[Channel].ErrorCoding,
callingModeSettings[Channel].Bandwidth,
callingModeSettings[Channel].SpreadingFactor,
callingModeSettings[Channel].LowDataRateOptimize );
LogMessage( "Ch%d: Restoring saved calling_mode Rx Settings\n", Channel );
}
else
{
setLoRaMode( Channel );
SetDefaultLoRaParameters( Channel );
LogMessage( "Ch%d: Restoring default Rx Settings\n", Channel );
}
startReceiving( Channel );
}
else
{
received_t Received = { .Telemetry = { .habpack_extra = NULL } };
Received.Metadata.Channel = Channel;
Received.Bytes = receiveMessage( Channel, Received.Message, &Received.Metadata );
Config.LoRaDevices[Channel].GotReply = 1;
if ( Received.Bytes > 0 )
{
if ( Config.LoRaDevices[Channel].ActivityLED >= 0 )
{
digitalWrite( Config.LoRaDevices[Channel].ActivityLED, 1 );
LEDCounts[Channel] = 5;
}
if ( Received.Message[0] == '!' )
{
ProcessUploadMessage( Channel, Received.Message);
}
else if ( Received.Message[0] == '^' )
{
ProcessCallingMessage( Channel, Received.Message);
}
else if ((Received.Message[0] == '$') || (Received.Message[0] == '%'))
{
int Repeated;
// ASCII telemetry ($ = normal; % = repeated)
strncpy(Received.UKHASstring, Received.Message, Received.Bytes);
UDPSend(Received.UKHASstring, Config.UDPPort);
Repeated = ProcessTelemetryMessage(Channel, &Received);
// ProcessLineUKHAS(Channel, Config.LoRaDevices[Channel].Telemetry);
TestMessageForSMSAcknowledgement( Channel, Received.UKHASstring);
CheckForChatContent(Channel, Repeated, Config.LoRaDevices[Channel].Telemetry);
strcpy(Config.LoRaDevices[Channel].LocalDataBuffer, Received.UKHASstring);
strcat(Config.LoRaDevices[Channel].LocalDataBuffer, "\r\n");
Config.LoRaDevices[Channel].LocalDataCount = strlen(Config.LoRaDevices[Channel].LocalDataBuffer);
}
else if ( Received.Message[0] == '>' )
{
LogMessage( "Flight Controller message %d bytes = %s\n", Received.Bytes, Received.Message );
}
else if ( Received.Message[0] == '<' )
{
LogMessage("Local Data %d bytes = %s", Received.Bytes, Received.Message);
strcpy(Config.LoRaDevices[Channel].LocalDataBuffer, Received.Message);
strcat(Config.LoRaDevices[Channel].LocalDataBuffer, "\r\n");
Config.LoRaDevices[Channel].LocalDataCount = strlen(Config.LoRaDevices[Channel].LocalDataBuffer);
}
else if ( Received.Message[0] == '*' )
{
LogMessage( "Uplink Command message %d bytes = %s\n", Received.Bytes, Received.Message );
}
else if ( Received.Message[0] == '+')
{
LogMessage( "Telnet Uplink message %d packet ID %d bytes = '%s'\n", Received.Bytes, Received.Message[1], Received.Message + 2);
}
else if ( Received.Message[0] == '-' )
{
ProcessTelnetMessage(Channel, Received.Message, Received.Bytes);
}
else if (Received.Message[0] == '.')
{
ProcessSyncMessage(Channel, Received.Message, Received.Bytes);
}
else if (((Received.Message[0] & 0x7F) == 0x66) || // SSDV JPG format
((Received.Message[0] & 0x7F) == 0x67) || // SSDV other formats
((Received.Message[0] & 0x7F) == 0x68) ||
((Received.Message[0] & 0x7F) == 0x69))
{
int Repeated;
// Handle repeater bit
Repeated = Received.Message[0] & 0x80;
Received.Message[0] &= 0x7F;
ProcessSSDVMessage( Channel, Received.Message, Repeated);
}
else if ( Received.Message[0] == 0x00)
{
time_t now;
struct tm *tm;
now = time( 0 );
tm = localtime( &now );
LogMessage("%02d:%02d:%02d Ch%d: Null uplink packet\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel);
}
else if ( ((Received.Message[0] & 0xF0) == 0x80) || (Received.Message[0] == 0xde))
{
/* HABpack Binary Message - https://ukhas.org.uk/communication:habpack */
Habpack_Process_Message(&Received);
if(Received.isCallingBeacon == false)
{
ProcessTelemetryMessage(Channel, &Received);
ProcessLineHABpack(Channel, &Received);
TestMessageForSMSAcknowledgement(Channel, Received.UKHASstring);
}
else
{
ProcessCallingHABpack(Channel, &Received);
}
}
else
{
LogMessage("Unknown packet type is %02Xh, RSSI %d\n", Received.Message[0], PacketRSSI(Channel));
ChannelPrintf( Channel, 3, 1, "Unknown Packet %d, %d bytes", Received.Message[0], Received.Bytes);
Config.LoRaDevices[Channel].UnknownCount++;
}
// Config.LoRaDevices[Channel].LastPacketAt = time( NULL );
if (Config.LoRaDevices[Channel].InCallingMode && (Config.CallingTimeout > 0))
{
Config.LoRaDevices[Channel].ReturnToCallingModeAt = time( NULL ) + Config.CallingTimeout;
}
if (!Config.LoRaDevices[Channel].InCallingMode && (Config.LoRaDevices[Channel].AFCTimeout > 0))
{
Config.LoRaDevices[Channel].ReturnToOriginalFrequencyAt = time(NULL) + Config.LoRaDevices[Channel].AFCTimeout;
}
ShowPacketCounts( Channel );
}
/* Free habpack linked list */
Habpack_Telem_Destroy(&Received);
}
}
void DIO_Ignore_Interrupt_0( void )
{
// nothing, obviously!
}
void
DIO0_Interrupt_0( void )
{
DIO0_Interrupt( 0 );
}
void
DIO0_Interrupt_1( void )
{
DIO0_Interrupt( 1 );
}
void setupRFM98( int Channel )
{
if ( Config.LoRaDevices[Channel].InUse )
{
// initialize the pins
pinMode( Config.LoRaDevices[Channel].DIO0, INPUT );
if (Config.LoRaDevices[Channel].DIO5 >= 0)
{
pinMode(Config.LoRaDevices[Channel].DIO5, INPUT);
}
wiringPiISR( Config.LoRaDevices[Channel].DIO0, INT_EDGE_RISING,
Channel > 0 ? &DIO0_Interrupt_1 : &DIO0_Interrupt_0 );
if ( wiringPiSPISetup( Channel, 500000 ) < 0 )
{
exit_error("Failed to open SPI port. Try loading spi library with 'gpio load spi'" );
}
if ( readRegister( Channel, REG_VERSION ) == 0x00 )
{
LogMessage("Error: RFM not found on Channel %d, Disabling.\n", Channel);
Config.LoRaDevices[Channel].InUse = 0;
return;
}
// LoRa mode
setLoRaMode( Channel );
SetDefaultLoRaParameters( Channel );
startReceiving( Channel );
}
}
double FrequencyError( int Channel )
{
int32_t Temp;
Temp = ( int32_t ) readRegister( Channel, REG_FREQ_ERROR ) & 7;
Temp <<= 8L;
Temp += ( int32_t ) readRegister( Channel, REG_FREQ_ERROR + 1 );
Temp <<= 8L;
Temp += ( int32_t ) readRegister( Channel, REG_FREQ_ERROR + 2 );
if ( readRegister( Channel, REG_FREQ_ERROR ) & 8 )
{
Temp = Temp - 524288;
}
return -( ( double ) Temp * ( 1 << 24 ) / 32000000.0 ) * (Config.LoRaDevices[Channel].CurrentBandwidth / 500.0);
}
int receiveMessage( int Channel, char *message, rx_metadata_t *Metadata )
{
int i, Bytes, currentAddr, x;
unsigned char data[257];
double FreqError;
Bytes = 0;
x = readRegister( Channel, REG_IRQ_FLAGS );
// LogMessage("Message status = %02Xh\n", x);
// clear the rxDone flag
writeRegister( Channel, REG_IRQ_FLAGS, 0x40 );
// check for payload crc issues (0x20 is the bit we are looking for
if ( ( x & 0x20 ) == 0x20 )
{
LogMessage( "Ch%d: CRC Failure, RSSI %d\n", Channel, PacketRSSI(Channel));
// reset the crc flags
writeRegister( Channel, REG_IRQ_FLAGS, 0x20 );
ChannelPrintf( Channel, 3, 1, "CRC Failure %02Xh!!\n", x );
Config.LoRaDevices[Channel].BadCRCCount++;
ShowPacketCounts( Channel );
}
else
{
currentAddr = readRegister( Channel, REG_FIFO_RX_CURRENT_ADDR );
Bytes = readRegister( Channel, REG_RX_NB_BYTES );
Metadata->SNR = PacketSNR(Channel);
Metadata->RSSI = PacketRSSI(Channel);
ChannelPrintf( Channel, 10, 1, "Packet SNR = %d, RSSI = %d ", Metadata->SNR, Metadata->RSSI);
FreqError = FrequencyError( Channel ) / 1000;
ChannelPrintf( Channel, 11, 1, "Freq. Error = %5.1lfkHz ", FreqError);
Config.LoRaDevices[Channel].PacketSNR = Metadata->SNR;
Config.LoRaDevices[Channel].PacketRSSI = Metadata->RSSI;
Config.LoRaDevices[Channel].FrequencyError = FreqError;
writeRegister( Channel, REG_FIFO_ADDR_PTR, currentAddr );
data[0] = REG_FIFO;
wiringPiSPIDataRW( Channel, data, Bytes + 1 );
for ( i = 0; i <= Bytes; i++ )
{
message[i] = data[i + 1];
}
message[Bytes] = '\0';
Metadata->Timestamp = time( NULL );
Metadata->Frequency = Config.LoRaDevices[Channel].Frequency + Config.LoRaDevices[Channel].FrequencyOffset;
Metadata->FrequencyError = FreqError / 1000;
Metadata->ImplicitOrExplicit = Config.LoRaDevices[Channel].ImplicitOrExplicit;
Metadata->Bandwidth = Config.LoRaDevices[Channel].CurrentBandwidth;
Metadata->ErrorCoding = Config.LoRaDevices[Channel].ErrorCoding;
Metadata->SpreadingFactor = Config.LoRaDevices[Channel].SpreadingFactor;
Metadata->LowDataRateOptimize = Config.LoRaDevices[Channel].LowDataRateOptimize;
LogPacket(Metadata, Bytes, message[1]);
if (Config.LoRaDevices[Channel].AFC && (fabs( FreqError ) > 0.5))
{
if (Config.LoRaDevices[Channel].MaxAFCStep > 0)
{
// Limit step to MaxAFCStep
if (FreqError > Config.LoRaDevices[Channel].MaxAFCStep)
{
FreqError = Config.LoRaDevices[Channel].MaxAFCStep;
}
else if (FreqError < -Config.LoRaDevices[Channel].MaxAFCStep)
{
FreqError = -Config.LoRaDevices[Channel].MaxAFCStep;
}
}
ReTune( Channel, FreqError / 1000 );
}
}
// Clear all flags
writeRegister( Channel, REG_IRQ_FLAGS, 0xFF );
return Bytes;
}
void RemoveTrailingSlash(char *Value)
{
int Len;
if ((Len = strlen(Value)) > 0)
{
if ((Value[Len-1] == '/') || (Value[Len-1] == '\\'))
{
Value[Len-1] = '\0';
}
}
}
void LoRaCallback(int Index)
{
// Check that this device is enabled otherwise, if it's missing, we'll get stuck trying to talk to the device
if (Config.LoRaDevices[Index].Enabled)
{
setLoRaMode(Index);
SetDefaultLoRaParameters(Index);
startReceiving(Index);
}
}
void MiscCallback(int Index)
{
displayChannel(Index);
}
void LoadConfigFile(void)
{
FILE *fp;
char *filename = "gateway.txt";
char *sample_filename = "gateway-sample.txt";
int Channel, MainSection;
if (access(filename, F_OK) != 0)
{
LogMessage("%s missing\n", filename);
if (access(sample_filename, F_OK) == 0)
{
LogMessage("Renaming %s as %s\n", sample_filename, filename);
rename(sample_filename, filename);
}
}
// Default configuration
Config.latitude = -999;
Config.longitude = -999;
Config.CallingTimeout = 300;
Config.NetworkLED = -1;
Config.InternetLED = -1;
Config.LoRaDevices[0].ActivityLED = -1;
Config.LoRaDevices[1].ActivityLED = -1;
strcpy(Config.radio, "Uputronics LoRa HAT");
// Default pin allocations
Config.LoRaDevices[0].DIO0 = 6;
Config.LoRaDevices[0].DIO5 = 5;
Config.LoRaDevices[1].DIO0 = 27;
Config.LoRaDevices[1].DIO5 = 26;
Config.LoRaDevices[0].Frequency = -1;
Config.LoRaDevices[1].Frequency = -1;
Config.LoRaDevices[0].FrequencyOffset = 0;
Config.LoRaDevices[1].FrequencyOffset = 0;
if ( ( fp = fopen( filename, "r" ) ) == NULL )
{
exit_error("Failed to open config file\n");
}
RegisterConfigFile(filename);
// Get reference to main settings section
MainSection = RegisterConfigSection("");
// Receiver config
RegisterConfigString(MainSection, -1, "tracker", Config.Tracker, sizeof(Config.Tracker), NULL);
LogMessage( "Tracker = '%s'\n", Config.Tracker );
// Enable uploads
RegisterConfigBoolean(MainSection, -1, "EnableSSDV", &Config.EnableSSDV, NULL);
RegisterConfigBoolean(MainSection, -1, "EnableSondehub", &Config.EnableSondehub, NULL);
// Enable telemetry logging
RegisterConfigBoolean(MainSection, -1, "LogTelemetry", &Config.EnableTelemetryLogging, NULL);
// Enable packet logging
RegisterConfigBoolean(MainSection, -1, "LogPackets", &Config.EnablePacketLogging, NULL);
// Calling mode
RegisterConfigInteger(MainSection, -1, "CallingTimeout", &Config.CallingTimeout, NULL);
// LED allocations
RegisterConfigInteger(MainSection, -1, "NetworkLED", &Config.NetworkLED, NULL);
RegisterConfigInteger(MainSection, -1, "InternetLED", &Config.InternetLED, NULL);
RegisterConfigInteger(MainSection, -1, "ActivityLED_0", &Config.LoRaDevices[0].ActivityLED, NULL);
RegisterConfigInteger(MainSection, -1, "ActivityLED_1", &Config.LoRaDevices[1].ActivityLED, NULL);
// Sockets
RegisterConfigInteger(MainSection, -1, "ServerPort", &Config.ServerPort, NULL); // JSON server
RegisterConfigInteger(MainSection, -1, "HABPort", &Config.HABPort, NULL); // Telnet server
RegisterConfigInteger(MainSection, -1, "DataPort", &Config.DataPort, NULL); // Raw data server
RegisterConfigInteger(MainSection, -1, "ChatPort", &Config.ChatPort, NULL); // Chat server
RegisterConfigInteger(MainSection, -1, "UDPPort", &Config.UDPPort, NULL); // UDP Broadcast socket (raw data)
RegisterConfigInteger(MainSection, -1, "OziPlotterPort", &Config.OziPlotterPort, NULL); // UDP Broadcast socket (OziPlotter format)
RegisterConfigInteger(MainSection, -1, "OziMuxPort", &Config.OziMuxPort, NULL); // UDP Broadcast socket (OziMux format)
if (Config.UDPPort > 0) LogMessage("UDP Broadcast of raw packets on port %d\n", Config.UDPPort);
if (Config.OziPlotterPort > 0) LogMessage("UDP Broadcast of OziPlotter packets on port %d\n", Config.OziPlotterPort);
if (Config.OziMuxPort > 0) LogMessage("UDP Broadcast of OziMux packets on port %d\n", Config.OziMuxPort);
// Timeout for HAB Telnet uplink
Config.HABTimeout = 4000;
RegisterConfigInteger(MainSection, -1, "HABTimeout", &Config.HABTimeout, NULL);
// LoRa Channel for HAB Telnet uplink
Config.HABChannel = 0;
RegisterConfigInteger(MainSection, -1, "HABChannel", &Config.HABChannel, NULL);
// Uplink encryption
*Config.UplinkCode = '\0';
RegisterConfigString(MainSection, -1, "UplinkCode", Config.UplinkCode, sizeof(Config.UplinkCode), NULL);
if (*Config.UplinkCode)
{
LogMessage("Uplink Code = '%s'\n", Config.UplinkCode);
}
// SSDV Settings
RegisterConfigString(MainSection, -1, "JPGFolder", Config.SSDVJpegFolder, sizeof(Config.SSDVJpegFolder), NULL);
if ( Config.SSDVJpegFolder[0] )
{
// Create SSDV Folder
struct stat st = { 0 };
if ( stat( Config.SSDVJpegFolder, &st ) == -1 )
{
mkdir( Config.SSDVJpegFolder, 0777 );
}
}
// ftp images
RegisterConfigString(MainSection, -1, "ftpserver", Config.ftpServer, sizeof(Config.ftpServer), NULL);
RegisterConfigString(MainSection, -1, "ftpUser", Config.ftpUser, sizeof(Config.ftpUser), NULL);
RegisterConfigString(MainSection, -1, "ftpPassword", Config.ftpPassword, sizeof(Config.ftpPassword), NULL);
RegisterConfigString(MainSection, -1, "ftpFolder", Config.ftpFolder, sizeof(Config.ftpFolder), NULL);
// Listener
RegisterConfigDouble(MainSection, -1, "Latitude", &Config.latitude, NULL);
RegisterConfigDouble(MainSection, -1, "Longitude", &Config.longitude, NULL);
RegisterConfigDouble(MainSection, -1, "Altitude", &Config.altitude, NULL);
RegisterConfigString(MainSection, -1, "radio", Config.radio, sizeof(Config.radio), NULL);
RegisterConfigString(MainSection, -1, "antenna", Config.antenna, sizeof(Config.antenna), NULL);
// Dev mode
RegisterConfigBoolean(MainSection, -1, "EnableDev", &Config.EnableDev, NULL);
// SMS upload to tracker
RegisterConfigString(MainSection, -1, "SMSFolder", Config.SMSFolder, sizeof(Config.SMSFolder), NULL);
if (Config.SMSFolder[0])
{
RemoveTrailingSlash(Config.SMSFolder);
LogMessage("Folder %s will be scanned for messages to upload\n", Config.SMSFolder);
}
// Dump buffer
RegisterConfigBoolean(MainSection, -1, "DumpBuffer", &Config.DumpBuffer, NULL);
RegisterConfigString(MainSection, -1, "DumpFile", Config.DumpFile, sizeof(Config.DumpFile), NULL);
// MQTT
RegisterConfigBoolean(MainSection, -1, "EnableMQTT", &Config.EnableMQTT, NULL);
RegisterConfigString(MainSection, -1, "MQTTHost", Config.MQTTHost, sizeof(Config.MQTTHost), NULL);
RegisterConfigString(MainSection, -1, "MQTTPort", Config.MQTTPort, sizeof(Config.MQTTPort), NULL);
RegisterConfigString(MainSection, -1, "MQTTUser", Config.MQTTUser, sizeof(Config.MQTTUser), NULL);
RegisterConfigString(MainSection, -1, "MQTTPass", Config.MQTTPass, sizeof(Config.MQTTPass), NULL);
RegisterConfigString(MainSection, -1, "MQTTTopic", Config.MQTTTopic, sizeof(Config.MQTTTopic), NULL);
for (Channel = 0; Channel <= 1; Channel++)
{
RegisterConfigDouble(MainSection, Channel, "frequency", &Config.LoRaDevices[Channel].Frequency, LoRaCallback);
RegisterConfigDouble(MainSection, Channel, "PPM", &Config.LoRaDevices[Channel].PPM, LoRaCallback);
Config.LoRaDevices[Channel].Enabled = Config.LoRaDevices[Channel].Frequency > 100;
if (Config.LoRaDevices[Channel].Enabled)
{
// Defaults
Config.LoRaDevices[Channel].ImplicitOrExplicit = EXPLICIT_MODE;
Config.LoRaDevices[Channel].ErrorCoding = ECToInt(ERROR_CODING_4_8);
Config.LoRaDevices[Channel].Bandwidth = BandwidthToDouble(BANDWIDTH_20K8);
Config.LoRaDevices[Channel].SpreadingFactor = SFToInt(SPREADING_11);
Config.LoRaDevices[Channel].LowDataRateOptimize = 0;
Config.LoRaDevices[Channel].AFC = FALSE;
Config.LoRaDevices[Channel].Power = PA_MAX_UK;
Config.LoRaDevices[Channel].UplinkMode = -1;
Config.LoRaDevices[Channel].UplinkTime = -1;
Config.LoRaDevices[Channel].UplinkCycle = -1;
Config.LoRaDevices[Channel].IdleUplink = FALSE;
Config.LoRaDevices[Channel].ChatMode = 0;
LogMessage( "Channel %d frequency set to %.3lfMHz\n", Channel, Config.LoRaDevices[Channel].Frequency);
Config.LoRaDevices[Channel].InUse = 1;
// DIO0 / DIO5 overrides
RegisterConfigInteger(MainSection, Channel, "DIO0", &Config.LoRaDevices[Channel].DIO0, NULL);
RegisterConfigInteger(MainSection, Channel, "DIO5", &Config.LoRaDevices[Channel].DIO5, NULL);
LogMessage( "LoRa Channel %d DIO0=%d DIO5=%d\n", Channel, Config.LoRaDevices[Channel].DIO0, Config.LoRaDevices[Channel].DIO5 );
// Uplink
RegisterConfigInteger(MainSection, Channel, "UplinkTime", &Config.LoRaDevices[Channel].UplinkTime, NULL);
RegisterConfigInteger(MainSection, Channel, "UplinkCycle", &Config.LoRaDevices[Channel].UplinkCycle, NULL);
if ((Config.LoRaDevices[Channel].UplinkTime >= 0) && (Config.LoRaDevices[Channel].UplinkCycle > Config.LoRaDevices[Channel].UplinkTime))
{
LogMessage( "Channel %d UplinkTime %d Uplink Cycle %d\n", Channel, Config.LoRaDevices[Channel].UplinkTime, Config.LoRaDevices[Channel].UplinkCycle);
RegisterConfigInteger(MainSection, Channel, "Power", &Config.LoRaDevices[Channel].Power, NULL);
LogMessage( "Channel %d power set to %02Xh\n", Channel, Config.LoRaDevices[Channel].Power );
RegisterConfigBoolean(MainSection, Channel, "SSDVUplink", &Config.LoRaDevices[Channel].SSDVUplink, NULL);
if (Config.LoRaDevices[Channel].SSDVUplink)
{
LogMessage( "Channel %d SSDV Uplink Enabled\n", Channel);
}
RegisterConfigBoolean(MainSection, Channel, "IdleUplink", &Config.LoRaDevices[Channel].IdleUplink, NULL);
if (Config.LoRaDevices[Channel].IdleUplink)
{
LogMessage( "Channel %d Idle Uplink Enabled\n", Channel);
}
}
RegisterConfigBoolean(MainSection, Channel, "ChatMode", &Config.LoRaDevices[Channel].ChatMode, NULL);
if (Config.LoRaDevices[Channel].ChatMode)
{
RegisterConfigString(MainSection, Channel, "ChatPayload", Config.LoRaDevices[Channel].ChatPayloadID, sizeof(Config.LoRaDevices[Channel].ChatPayloadID), NULL);
LogMessage( "Channel %d Chat Mode Enabled to Payload %s\n", Channel, Config.LoRaDevices[Channel].ChatPayloadID);
}
RegisterConfigInteger(MainSection, Channel, "Power", &Config.LoRaDevices[Channel].Power, NULL);
RegisterConfigInteger(MainSection, Channel, "UplinkMode", &Config.LoRaDevices[Channel].UplinkMode, NULL);
if (Config.LoRaDevices[Channel].UplinkMode >= 0)
{
LogMessage( "Channel %d uplink mode %d\n", Channel, Config.LoRaDevices[Channel].UplinkMode);
}
RegisterConfigDouble(MainSection, Channel, "UplinkFrequency", &Config.LoRaDevices[Channel].UplinkFrequency, NULL);
if (Config.LoRaDevices[Channel].UplinkFrequency > 0)
{
LogMessage( "Channel %d uplink frequency %.3lfMHz\n", Channel, Config.LoRaDevices[Channel].UplinkFrequency);
}
Config.LoRaDevices[Channel].SpeedMode = 0;
RegisterConfigInteger(MainSection, Channel, "mode", &Config.LoRaDevices[Channel].SpeedMode, NULL);
if ((Config.LoRaDevices[Channel].SpeedMode < 0) || (Config.LoRaDevices[Channel].SpeedMode >= sizeof(LoRaModes)/sizeof(LoRaModes[0]))) Config.LoRaDevices[Channel].SpeedMode = 0;
// Defaults for this LoRa Mode
Config.LoRaDevices[Channel].ImplicitOrExplicit = LoRaModes[Config.LoRaDevices[Channel].SpeedMode].ImplicitOrExplicit;
Config.LoRaDevices[Channel].ErrorCoding = ECToInt(LoRaModes[Config.LoRaDevices[Channel].SpeedMode].ErrorCoding);
Config.LoRaDevices[Channel].Bandwidth = BandwidthToDouble(LoRaModes[Config.LoRaDevices[Channel].SpeedMode].Bandwidth);
Config.LoRaDevices[Channel].SpreadingFactor = SFToInt(LoRaModes[Config.LoRaDevices[Channel].SpeedMode].SpreadingFactor);
Config.LoRaDevices[Channel].LowDataRateOptimize = LowOptToInt(LoRaModes[Config.LoRaDevices[Channel].SpeedMode].LowDataRateOptimize);
// Overrides
if (RegisterConfigInteger(MainSection, Channel, "sf", &Config.LoRaDevices[Channel].SpreadingFactor, LoRaCallback))
{
LogMessage( "Setting SF=%d\n", Config.LoRaDevices[Channel].SpreadingFactor);
}
if (RegisterConfigDouble(MainSection, Channel, "bandwidth", &Config.LoRaDevices[Channel].Bandwidth, LoRaCallback))
{
LogMessage( "Setting Bandwidth=%.2lfkHz\n", Config.LoRaDevices[Channel].Bandwidth);
}
RegisterConfigBoolean(MainSection, Channel, "implicit", &Config.LoRaDevices[Channel].ImplicitOrExplicit, LoRaCallback);
if (RegisterConfigInteger(MainSection, Channel, "coding", &Config.LoRaDevices[Channel].ErrorCoding, LoRaCallback))
{
LogMessage( "Setting Error Coding=%d\n", Config.LoRaDevices[Channel].ErrorCoding);
}
RegisterConfigBoolean(MainSection, Channel, "lowopt", &Config.LoRaDevices[Channel].LowDataRateOptimize, LoRaCallback);
RegisterConfigBoolean(MainSection, Channel, "AFC", &Config.LoRaDevices[Channel].AFC, MiscCallback);
if (Config.LoRaDevices[Channel].AFC)
{
ChannelPrintf( Channel, 11, 24, "AFC" );
RegisterConfigDouble(MainSection, Channel, "MaxAFCStep", &Config.LoRaDevices[Channel].MaxAFCStep, NULL);
if (Config.LoRaDevices[Channel].MaxAFCStep > 0)
{
LogMessage("Maximum AFC Step = %.0lfkHz\n", Config.LoRaDevices[Channel].MaxAFCStep);
}
RegisterConfigInteger(MainSection, Channel, "AFCTimeout", &Config.LoRaDevices[Channel].AFCTimeout, NULL);
if (Config.LoRaDevices[Channel].AFCTimeout > 0)
{
LogMessage("AFC Timeout = %.0ds\n", Config.LoRaDevices[Channel].AFCTimeout);
}
}
// Clear any flags left over from a previous run
writeRegister( Channel, REG_IRQ_FLAGS, 0xFF );
}
}
fclose(fp);
}
WINDOW *InitDisplay(void)
{
WINDOW *mainwin;
int Channel;
/* Initialize ncurses */
if ( ( mainwin = initscr( ) ) == NULL )
{
fprintf( stderr, "Error initialising ncurses.\n" );
exit( EXIT_FAILURE );
}
start_color( ); /* Initialize colours */
init_pair(1, COLOR_WHITE, COLOR_BLUE );
init_pair(2, COLOR_WHITE, COLOR_BLACK );
color_set(1, NULL );
char buffer[80];
sprintf( buffer, "LoRa HAB Sondehub, SSDV and MQTT Gateway by M0RPI, M0DNY, M0RJX - " VERSION);
// Title bar
mvaddstr(0, ( 80 - strlen( buffer ) ) / 2, buffer );
// Help
sprintf( buffer, "Press (H) for Help");
color_set(2, NULL );
mvaddstr(15, ( 80 - strlen( buffer ) ) / 2, buffer );
color_set(1, NULL );
refresh();
// Windows for LoRa live data
for ( Channel = 0; Channel <= 1; Channel++ )
{
Config.LoRaDevices[Channel].Window =
newwin( 14, 38, 1, Channel ? 41 : 1 );
wbkgd( Config.LoRaDevices[Channel].Window, COLOR_PAIR(1));
wrefresh( Config.LoRaDevices[Channel].Window );
}
curs_set( 0 );
return mainwin;
}
int ValidCRC16(char *ptr)
{
uint16_t CRC;
int j;
int Valid;
char CRCString[5];
Valid = 0;
CRC = 0xffff; // Seed
// Skip $'s
while (*++ptr == '$');
for ( ; *ptr && (*ptr != '*'); ptr++)
{ // For speed, repeat calculation instead of looping for each bit
CRC ^= ( ( ( unsigned int ) *ptr ) << 8 );
for ( j = 0; j < 8; j++ )
{
if ( CRC & 0x8000 )
CRC = ( CRC << 1 ) ^ 0x1021;
else
CRC <<= 1;
}
}
if (*ptr == '*')
{
// CRC follows
sprintf(CRCString, "%04X", CRC);
Valid = memcmp(ptr+1, CRCString, 4) == 0;
}
return Valid;
}
void
ProcessKeyPress( int ch )
{
int Channel = 0;
/* shifted keys act on channel 1 */
if ( ch >= 'A' && ch <= 'Z' )
{
Channel = 1;
/* change from upper to lower case */
ch += ( 'a' - 'A' );
}
if ( ch == 'q' )
{
run = FALSE;
return;
}
/* ignore if channel is not in use */
if ( !Config.LoRaDevices[Channel].InUse && ch !='h' )
{
return;
}
switch ( ch )
{
case 'f':
Config.LoRaDevices[Channel].AFC =
!Config.LoRaDevices[Channel].AFC;
ChannelPrintf( Channel, 11, 24, "%s",
Config.LoRaDevices[Channel].AFC ? "AFC" : " " );
break;
case 'a':
ReTune( Channel, 0.1 );
break;
case 'z':
ReTune( Channel, -0.1 );
break;
case 's':
ReTune( Channel, 0.01 );
break;
case 'x':
ReTune( Channel, -0.01 );
break;
case 'd':
ReTune( Channel, 0.001 );
break;
case 'c':
ReTune( Channel, -0.001 );
break;
case 'p':
break;
case 'h':
help_win_displayed = 1;
gui_show_help();
for (Channel=0; Channel<=1; Channel++)
{
if ( Config.LoRaDevices[Channel].InUse ) displayChannel (Channel);
}
help_win_displayed = 0;
break;
default:
// LogMessage("KeyPress %d\n", ch);
return;
}
}
int prog_count( char *name )
{
DIR *dir;
struct dirent *ent;
char buf[512];
long pid;
char pname[100] = { 0, };
char state;
FILE *fp = NULL;
int Count = 0;
if ( !( dir = opendir( "/proc" ) ) )
{
perror( "can't open /proc" );
return 0;
}
while ( ( ent = readdir( dir ) ) != NULL )
{
long lpid = atol( ent->d_name );
if ( lpid < 0 )
continue;
snprintf( buf, sizeof( buf ), "/proc/%ld/stat", lpid );
fp = fopen( buf, "r" );
if ( fp )
{
if ( ( fscanf( fp, "%ld (%[^)]) %c", &pid, pname, &state ) ) !=
3 )
{
printf( "fscanf failed \n" );
fclose( fp );
closedir( dir );
return 0;
}
if ( !strcmp( pname, name ) )
{
Count++;
}
fclose( fp );
}
}
closedir( dir );
return Count;
}
void SendTelnetMessage(int Channel, struct TServerInfo *TelnetInfo, int TimedOut)
{
// Send message regardless of if we have content to send
// This is so that that HAB gets a chance to send anything it needs (further replies from earlier messages, telemetry, etc)
char Message[256], FirstByte;
int Length;
// If HAB Acked us last time, we can go on to the next message; if not we should re-send
if (Config.LoRaDevices[Channel].HABAck)
{
// Prepare new packet
Config.LoRaDevices[Channel].PacketID++; // Packet acked, so onto next packet
Config.LoRaDevices[Channel].HABUplinkCount = 0; // Remove existing packet contents
if (TelnetInfo->Connected)
{
if (Config.LoRaDevices[Channel].FromTelnetBufferCount > 0)
{
memcpy(Config.LoRaDevices[Channel].HABUplink, Config.LoRaDevices[Channel].FromTelnetBuffer, Config.LoRaDevices[Channel].FromTelnetBufferCount);
}
Config.LoRaDevices[Channel].HABUplinkCount = Config.LoRaDevices[Channel].FromTelnetBufferCount;
Config.LoRaDevices[Channel].HABUplink[Config.LoRaDevices[Channel].HABUplinkCount] = 0;
Config.LoRaDevices[Channel].FromTelnetBufferCount = 0;
if (Config.LoRaDevices[Channel].HABUplinkCount > 0)
{
LogMessage("Received %d bytes from HAB client\n", Config.LoRaDevices[Channel].HABUplinkCount);
}
// echo
// sprintf(sendBuff, "%c", *line);
}
}
else if (TimedOut)
{
LogMessage("TIMED OUT\n");
}
else if (!Config.LoRaDevices[Config.HABChannel].GotHABReply)
{
LogMessage("Other Reply - RESEND\n");
}
else
{
// Resend last packet
LogMessage("NAK - RESEND\n");
}
FirstByte = (TelnetInfo->Connected ? 0x80 : 0x00) | // Bit 7: CONN - 1 = Telnet client is connected (telling HAB that it ought to connect to telnetd now)
0x00 | // Bit 6: ACK bit, not used in uplink
(Config.LoRaDevices[Channel].PacketID & 0x3F); // Bits 5-0: Packet number
if (Config.LoRaDevices[Channel].HABUplinkCount > 0)
{
Length = sprintf(Message, "+%c%s", FirstByte, Config.LoRaDevices[Channel].HABUplink);
LogMessage("SENDING %d BYTES +[%02X]%s\n", Length, FirstByte, Config.LoRaDevices[Channel].HABUplink);
}
else
{
Length = sprintf(Message, "+%c", FirstByte);
LogMessage("SENDING +[%02X]\n", FirstByte);
}
Config.LoRaDevices[Channel].HABAck = 0;
SendLoRaData(Channel, Message, Length);
}
void displayChannel (int Channel) {
displayFrequency ( Channel, Config.LoRaDevices[Channel].Frequency + Config.LoRaDevices[Channel].FrequencyOffset);
displayLoRaParameters(
Channel,
Config.LoRaDevices[Channel].ImplicitOrExplicit,
Config.LoRaDevices[Channel].ErrorCoding,
Config.LoRaDevices[Channel].Bandwidth,
Config.LoRaDevices[Channel].SpreadingFactor,
Config.LoRaDevices[Channel].LowDataRateOptimize
);
if (Config.LoRaDevices[Channel].AFC)
ChannelPrintf( Channel, 11, 24, "AFC" );
else
ChannelPrintf( Channel, 11, 24, " " );
}
char *Hostname(void)
{
static char Buffer[80];
strcpy(Buffer, "PI");
gethostname(Buffer, sizeof(Buffer));
return Buffer;
}
char *ChannelInfo(void)
{
static char Result[100];
char Temp[2][50];
int i;
for (i=0; i<=1; i++)
{
if (Config.LoRaDevices[i].Frequency > 0)
{
sprintf(Temp[i], ",FREQ%d=%.3lf,MODE%d=%d", i, Config.LoRaDevices[i].Frequency, i, Config.LoRaDevices[i].SpeedMode);
}
else
{
Temp[i][0] = 0;
}
}
strcpy(Result, Temp[0]);
strcat(Result, Temp[1]);
return Result;
}
char *GetIPAddress(void)
{
static char IPAddress[100];
struct ifaddrs *ifap, *ifa;
struct sockaddr_in *sa;
char *addr;
IPAddress[0] = '\0';
if (getifaddrs(&ifap) == 0)
{
for (ifa = ifap; ifa; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr != NULL)
{
// Family is known (which it isn't for a VPN)
if (ifa->ifa_addr->sa_family==AF_INET)
{
// Exclude docker bridges
if (strstr(ifa->ifa_name, "docker") == NULL)
{
sa = (struct sockaddr_in *) ifa->ifa_addr;
addr = inet_ntoa(sa->sin_addr);
if (strcmp(addr, "127.0.0.1") != 0)
{
strcpy(IPAddress, addr);
}
}
}
}
}
}
freeifaddrs(ifap);
return IPAddress;
}
int main( int argc, char **argv )
{
int ch;
int LoopPeriod, MSPerLoop;
int Channel;
pthread_t SSDVThread, FTPThread, NetworkThread, SondehubThread, ServerThread, TelnetThread, DataportThread, ChatportThread, MQTTThread;
struct TServerInfo JSONInfo, TelnetInfo, DataportInfo, ChatportInfo;
atexit(bye);
if ( wiringPiSetup( ) < 0 )
{
exit_error("Failed to open wiringPi\n");
}
// Clear config to zeroes so we only have to set non-zero defaults
memset((void *)&Config, 0, sizeof(Config));
strcpy(Config.Version, VERSION);
if ( prog_count( "gateway" ) > 1 )
{
printf( "\nThe gateway program is already running!\n\n" );
exit( 1 );
}
curl_global_init( CURL_GLOBAL_ALL ); // RJH thread safe
mainwin = InitDisplay();
// Settings for character input
noecho( );
cbreak( );
nodelay( stdscr, TRUE );
keypad( stdscr, TRUE );
LEDCounts[0] = 0;
LEDCounts[1] = 0;
LoadConfigFile();
int result;
result = pipe( ssdv_pipe_fd );
if ( result < 0 )
{
exit_error("Error creating ssdv pipe\n" );
}
if ( Config.LoRaDevices[0].ActivityLED >= 0 )
pinMode( Config.LoRaDevices[0].ActivityLED, OUTPUT );
if ( Config.LoRaDevices[1].ActivityLED >= 0 )
pinMode( Config.LoRaDevices[1].ActivityLED, OUTPUT );
if ( Config.InternetLED >= 0 )
pinMode( Config.InternetLED, OUTPUT );
if ( Config.NetworkLED >= 0 )
pinMode( Config.NetworkLED, OUTPUT );
setupRFM98( 0 );
setupRFM98( 1 );
if(Config.LoRaDevices[0].InUse == 0 && Config.LoRaDevices[1].InUse == 0)
{
LogMessage("Warning: No Receiver Channels enabled!\n");
}
ShowPacketCounts( 0 );
ShowPacketCounts( 1 );
LoopPeriod = 0;
MSPerLoop = 10;
// Initialise the vars
stsv.parent_status = RUNNING;
stsv.packet_count = 0;
if (Config.EnableSSDV)
{
if ( pthread_create( &SSDVThread, NULL, SSDVLoop, ( void * ) &stsv ) )
{
fprintf( stderr, "Error creating SSDV thread\n" );
return 1;
}
}
if ( pthread_create( &FTPThread, NULL, FTPLoop, NULL ) )
{
fprintf( stderr, "Error creating FTP thread\n" );
return 1;
}
if (Config.EnableMQTT)
{
lifo_buffer_init(&MQTT_Upload_Buffer, 1024);
mqtt_connect_t *mqttConnection = malloc(sizeof *mqttConnection);
strcpy(mqttConnection->host, Config.MQTTHost);
strcpy(mqttConnection->port, Config.MQTTPort);
strcpy(mqttConnection->user, Config.MQTTUser);
strcpy(mqttConnection->pass, Config.MQTTPass);
strcpy(mqttConnection->topic, Config.MQTTTopic);
strcpy(mqttConnection->clientId, Config.Tracker);
if ( pthread_create (&MQTTThread, NULL, MQTTLoop, mqttConnection))
{
fprintf( stderr, "Error creating MQTT thread\n" );
free(mqttConnection);
return 1;
}
}
if (Config.EnableSondehub)
{
if (pthread_create (&SondehubThread, NULL, SondehubLoop, NULL))
{
fprintf( stderr, "Error creating Sondehub thread\n" );
return 1;
}
}
if (Config.ServerPort > 0)
{
JSONInfo.Port = Config.ServerPort;
JSONInfo.ServerIndex = 0;
JSONInfo.Connected = 0;
if (pthread_create(&ServerThread, NULL, ServerLoop, (void *)(&JSONInfo)))
{
fprintf( stderr, "Error creating JSON server thread\n" );
return 1;
}
}
if (Config.HABPort > 0)
{
TelnetInfo.Port = Config.HABPort;
TelnetInfo.ServerIndex = 1;
TelnetInfo.Connected = 0;
if (pthread_create(&TelnetThread, NULL, ServerLoop, (void *)(&TelnetInfo)))
{
fprintf( stderr, "Error creating HAB server thread\n" );
return 1;
}
}
if (Config.DataPort > 0)
{
DataportInfo.Port = Config.DataPort;
DataportInfo.ServerIndex = 2;
DataportInfo.Connected = 0;
if (pthread_create(&DataportThread, NULL, ServerLoop, (void *)(&DataportInfo)))
{
fprintf( stderr, "Error creating DATA server thread\n" );
return 1;
}
}
if (Config.ChatPort > 0)
{
ChatportInfo.Port = Config.ChatPort;
ChatportInfo.ServerIndex = 3;
ChatportInfo.Connected = 0;
if (pthread_create(&ChatportThread, NULL, ServerLoop, (void *)(&ChatportInfo)))
{
fprintf( stderr, "Error creating CHAT server thread\n" );
return 1;
}
}
if ( ( Config.NetworkLED >= 0 ) && ( Config.InternetLED >= 0 ) )
{
if ( pthread_create( &NetworkThread, NULL, NetworkLoop, NULL ) )
{
fprintf( stderr, "Error creating Network thread\n" );
return 1;
}
}
// Initializes the structure used for storing calling mode settings
callingModeSettings[0].Channel = -1;
callingModeSettings[1].Channel = -1;
LogMessage( "Starting now ...\n" );
while ( run )
{
// Keypress tests
if ((ch = getch()) != ERR )
{
ProcessKeyPress( ch );
}
// Telnet uplink to HAB
if (Config.HABPort > 0)
{
Config.LoRaDevices[Config.HABChannel].TimeSinceLastTx += MSPerLoop;
if ((Config.LoRaDevices[Config.HABChannel].TimeSinceLastTx >= Config.HABTimeout) || Config.LoRaDevices[Config.HABChannel].GotReply)
{
SendTelnetMessage(Config.HABChannel, &TelnetInfo, Config.LoRaDevices[Config.HABChannel].TimeSinceLastTx >= Config.HABTimeout);
Config.LoRaDevices[Config.HABChannel].GotReply = 0;
Config.LoRaDevices[Config.HABChannel].GotHABReply = 0;
Config.LoRaDevices[Config.HABChannel].TimeSinceLastTx = 0;
}
}
if (LoopPeriod > 1000)
{
// Every 1 second
static int Seconds=55;
time_t now;
struct tm *tm;
now = time( 0 );
tm = localtime( &now );
LoopPeriod = 0;
for (Channel=0; Channel<=1; Channel++)
{
if (Config.LoRaDevices[Channel].InUse)
{
ShowPacketCounts( Channel );
Config.LoRaDevices[Channel].CurrentRSSI = CurrentRSSI(Channel);
ChannelPrintf( Channel, 12, 1, "Current RSSI = %4d ", Config.LoRaDevices[Channel].CurrentRSSI);
// Calling mode timeout?
if ( Config.LoRaDevices[Channel].InCallingMode
&& ( Config.CallingTimeout > 0 )
&& ( Config.LoRaDevices[Channel].ReturnToCallingModeAt > 0 )
&& ( time( NULL ) > Config.LoRaDevices[Channel].ReturnToCallingModeAt ) )
{
Config.LoRaDevices[Channel].InCallingMode = 0;
Config.LoRaDevices[Channel].ReturnToCallingModeAt = 0;
Config.LoRaDevices[Channel].FrequencyOffset = 0; // Fixed bug where offset is used when returning to calling mode frequency
callingModeSettings[Channel].Channel = -1; // invalidates previously saved calling mode settings
LogMessage( "Ch%d: Return to calling mode\n", Channel );
setLoRaMode( Channel );
SetDefaultLoRaParameters( Channel );
setMode( Channel, RF98_MODE_RX_CONTINUOUS );
}
// AFC Timeout ?
if (!Config.LoRaDevices[Channel].InCallingMode &&
(Config.LoRaDevices[Channel].AFCTimeout > 0) &&
(Config.LoRaDevices[Channel].ReturnToOriginalFrequencyAt > 0) &&
(time(NULL) > Config.LoRaDevices[Channel].ReturnToOriginalFrequencyAt))
{
Config.LoRaDevices[Channel].ReturnToOriginalFrequencyAt = 0;
LogMessage("Ch%d: AFC timeout - return to original frequency\n", Channel);
setMode(Channel, RF98_MODE_SLEEP);
setFrequency(Channel, Config.LoRaDevices[Channel].Frequency);
startReceiving(Channel);
Config.LoRaDevices[Channel].FrequencyOffset = 0; // Fixed bug where AFC offset were kept
}
// Uplink cycle time ?
if ((Config.LoRaDevices[Channel].UplinkTime >= 0) && (Config.LoRaDevices[Channel].UplinkCycle > 0))
{
long CycleSeconds;
CycleSeconds = (tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec ) % Config.LoRaDevices[Channel].UplinkCycle;
if (CycleSeconds == Config.LoRaDevices[Channel].UplinkTime)
{
// LogMessage("%02d:%02d:%02d - Time to send uplink message\n", tm->tm_hour, tm->tm_min, tm->tm_sec);
if (Config.LoRaDevices[Channel].ChatMode)
{
sprintf(Config.LoRaDevices[Channel].UplinkMessage, "!%s,%d,%d,%s", Config.LoRaDevices[Channel].ChatPayloadID,
Config.LoRaDevices[Channel].RxMessageID,
Config.LoRaDevices[Channel].TxMessageID,
Config.LoRaDevices[Channel].TxChatMessage);
}
UDPSend(Config.LoRaDevices[Channel].UplinkMessage, Config.UDPPort);
SendUplinkMessage(Channel);
}
}
// LEDs
if (LEDCounts[Channel] && ( Config.LoRaDevices[Channel].ActivityLED >= 0))
{
if ( --LEDCounts[Channel] == 0 )
{
digitalWrite(Config.LoRaDevices[Channel].ActivityLED, 0);
}
}
}
}
if (++Seconds >= 60)
{
char Message[200];
Seconds = 0;
sprintf(Message, "GATEWAY:HOST=%s,IP=%s,VER=%s,CALLSIGN=%s%s\n", Hostname(), GetIPAddress(), VERSION, Config.Tracker, ChannelInfo());
UDPSend(Message, Config.UDPPort);
}
}
delay(MSPerLoop);
LoopPeriod += MSPerLoop;
}
LogMessage("Disabling DIO0 ISRs\n");
for (Channel=0; Channel<2; Channel++)
{
if (Config.LoRaDevices[Channel].InUse)
{
wiringPiISR(Config.LoRaDevices[Channel].DIO0, INT_EDGE_RISING, &DIO_Ignore_Interrupt_0);
}
}
LogMessage( "Closing SSDV pipe\n" );
close( ssdv_pipe_fd[1] );
LogMessage( "Stopping SSDV thread\n" );
stsv.parent_status = STOPPED;
if (Config.EnableSSDV)
{
LogMessage( "Waiting for SSDV thread to close ...\n" );
pthread_join( SSDVThread, NULL );
LogMessage( "SSDV thread closed\n" );
}
pthread_mutex_destroy( &var );
curl_global_cleanup( ); // RJH thread safe
if ( Config.NetworkLED >= 0 )
digitalWrite( Config.NetworkLED, 0 );
if ( Config.InternetLED >= 0 )
digitalWrite( Config.InternetLED, 0 );
if ( Config.LoRaDevices[0].ActivityLED >= 0 )
digitalWrite( Config.LoRaDevices[0].ActivityLED, 0 );
if ( Config.LoRaDevices[1].ActivityLED >= 0 )
digitalWrite( Config.LoRaDevices[1].ActivityLED, 0 );
return 0;
}