#include #include #define __USE_XOPEN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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; }