kopia lustrzana https://github.com/PiInTheSky/lora-gateway
2057 wiersze
53 KiB
C
2057 wiersze
53 KiB
C
#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 <time.h>
|
|
#include <stdarg.h>
|
|
#include <pthread.h>
|
|
#include <curses.h>
|
|
#include <math.h>
|
|
#include <dirent.h>
|
|
|
|
#include <wiringPi.h>
|
|
#include <wiringPiSPI.h>
|
|
|
|
#include "urlencode.h"
|
|
#include "base64.h"
|
|
#include "ssdv.h"
|
|
#include "ftp.h"
|
|
#include "habitat.h"
|
|
#include "network.h"
|
|
#include "global.h"
|
|
#include "server.h"
|
|
|
|
#define VERSION "V1.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
|
|
|
|
// 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 TPayload
|
|
{
|
|
int InUse;
|
|
char Payload[32];
|
|
};
|
|
|
|
|
|
struct TConfig Config;
|
|
struct TPayload Payloads[16];
|
|
struct TSSDVPacketArray SSDVPacketArrays[2];
|
|
int SSDVSendArrayIndex=-1;
|
|
pthread_mutex_t ssdv_mutex=PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
int LEDCounts[2];
|
|
pthread_mutex_t var=PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
#pragma pack(1)
|
|
|
|
struct TBinaryPacket
|
|
{
|
|
uint8_t PayloadIDs;
|
|
uint16_t Counter;
|
|
uint16_t BiSeconds;
|
|
float Latitude;
|
|
float Longitude;
|
|
uint16_t Altitude;
|
|
};
|
|
|
|
const char *Modes[6] = {"Slow", "SSDV", "Repeater", "Turbo", "TurboX", "Calling"};
|
|
|
|
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(int Channel, int8_t SNR, int RSSI, double FreqError, int Bytes, unsigned char MessageType)
|
|
{
|
|
if (Config.EnablePacketLogging)
|
|
{
|
|
FILE *fp;
|
|
|
|
if ((fp = fopen("packets.txt", "at")) != NULL)
|
|
{
|
|
time_t now;
|
|
struct tm *tm;
|
|
|
|
now = time(0);
|
|
tm = localtime(&now);
|
|
|
|
fprintf(fp, "%02d:%02d:%02d - Ch %d, SNR %d, RSSI %d, FreqErr %.1lf, Bytes %d, Type %02Xh\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, SNR, RSSI, FreqError, Bytes, MessageType);
|
|
|
|
fclose(fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogTelemetryPacket(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 - %s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Telemetry);
|
|
|
|
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(9, 80, 16, 0);
|
|
scrollok(Window, TRUE);
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
vsprintf(Buffer, format, args);
|
|
|
|
va_end(args);
|
|
|
|
if (strlen(Buffer) > 79)
|
|
{
|
|
Buffer[77] = '.';
|
|
Buffer[78] = '.';
|
|
Buffer[79] = '\n';
|
|
Buffer[80] = 0;
|
|
}
|
|
|
|
waddstr(Window, Buffer);
|
|
|
|
wrefresh(Window);
|
|
|
|
pthread_mutex_unlock(&var); // unlock once you are done
|
|
}
|
|
|
|
void ChannelPrintf(int Channel, int row, int column, const char *format, ...)
|
|
{
|
|
pthread_mutex_lock(&var); // lock the critical section
|
|
|
|
char Buffer[80];
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vsprintf(Buffer, format, args);
|
|
|
|
va_end(args);
|
|
|
|
mvwaddstr(Config.LoRaDevices[Channel].Window, row, column, Buffer);
|
|
|
|
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)
|
|
{
|
|
while(digitalRead(Config.LoRaDevices[Channel].DIO5) == 0)
|
|
{
|
|
}
|
|
// 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 * 7110656 / 434);
|
|
|
|
writeRegister(Channel, 0x06, (FrequencyValue >> 16) & 0xFF); // Set frequency
|
|
writeRegister(Channel, 0x07, (FrequencyValue >> 8) & 0xFF);
|
|
writeRegister(Channel, 0x08, FrequencyValue & 0xFF);
|
|
|
|
Config.LoRaDevices[Channel].activeFreq = Frequency;
|
|
|
|
// LogMessage("Set Frequency to %lf\n", Frequency);
|
|
|
|
ChannelPrintf(Channel, 1, 1, "Channel %d %s MHz ", Channel, FrequencyString);
|
|
}
|
|
|
|
void setLoRaMode(int Channel)
|
|
{
|
|
double Frequency;
|
|
unsigned long FrequencyValue;
|
|
|
|
// LogMessage("Setting LoRa Mode\n");
|
|
setMode(Channel, RF98_MODE_SLEEP);
|
|
writeRegister(Channel, REG_OPMODE,0x80);
|
|
|
|
setMode(Channel, RF98_MODE_SLEEP);
|
|
|
|
if (sscanf(Config.LoRaDevices[Channel].Frequency, "%lf", &Frequency))
|
|
{
|
|
// LogMessage("Set Default Frequency\n");
|
|
setFrequency(Channel, Frequency);
|
|
}
|
|
}
|
|
|
|
char *BandwidthString(int Bandwidth)
|
|
{
|
|
if (Bandwidth == BANDWIDTH_7K8) return "7.8k";
|
|
if (Bandwidth == BANDWIDTH_10K4) return "10.4k";
|
|
if (Bandwidth == BANDWIDTH_15K6) return "15.6k";
|
|
if (Bandwidth == BANDWIDTH_20K8) return "20.8k";
|
|
if (Bandwidth == BANDWIDTH_31K25) return "31.25k";
|
|
if (Bandwidth == BANDWIDTH_41K7) return "41.7k";
|
|
if (Bandwidth == BANDWIDTH_62K5) return "62.5k";
|
|
if (Bandwidth == BANDWIDTH_125K) return "125k";
|
|
if (Bandwidth == BANDWIDTH_250K) return "250k";
|
|
if (Bandwidth == BANDWIDTH_500K) return "500k";
|
|
return "??k";
|
|
}
|
|
|
|
void SetLoRaParameters(int Channel, int ImplicitOrExplicit, int ErrorCoding, int Bandwidth, int SpreadingFactor, int LowDataRateOptimize)
|
|
{
|
|
writeRegister(Channel, REG_MODEM_CONFIG, ImplicitOrExplicit | ErrorCoding | Bandwidth);
|
|
writeRegister(Channel, REG_MODEM_CONFIG2, SpreadingFactor | CRC_ON);
|
|
writeRegister(Channel, REG_MODEM_CONFIG3, 0x04 | LowDataRateOptimize); // 0x04: AGC sets LNA gain
|
|
writeRegister(Channel, REG_DETECT_OPT, (readRegister(Channel, REG_DETECT_OPT) & 0xF8) | ((SpreadingFactor == SPREADING_6) ? 0x05 : 0x03)); // 0x05 For SF6; 0x03 otherwise
|
|
writeRegister(Channel, REG_DETECTION_THRESHOLD, (SpreadingFactor == SPREADING_6) ? 0x0C : 0x0A); // 0x0C for SF6, 0x0A otherwise
|
|
|
|
Config.LoRaDevices[Channel].CurrentBandwidth = Bandwidth;
|
|
|
|
ChannelPrintf(Channel, 2, 1, "%s, %s, SF%d, EC4:%d %s",
|
|
ImplicitOrExplicit == IMPLICIT_MODE ? "Implicit" : "Explicit",
|
|
BandwidthString(Bandwidth),
|
|
SpreadingFactor >> 4,
|
|
(ErrorCoding >> 1) + 4,
|
|
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("Retune by %lf kHz\n", FreqShift * 1000);
|
|
setFrequency(Channel, Config.LoRaDevices[Channel].activeFreq + FreqShift);
|
|
startReceiving(Channel);
|
|
}
|
|
|
|
void SendLoRaData(int Channel, unsigned char *buffer, int Length)
|
|
{
|
|
unsigned char data[257];
|
|
int i;
|
|
|
|
LogMessage("LoRa Channel %d Sending %d bytes\n", Channel, Length);
|
|
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];
|
|
}
|
|
wiringPiSPIDataRW(Channel, data, Length+1);
|
|
|
|
// Set the length. For implicit mode, since the length needs to match what the receiver expects, we have to set a value which is 255 for an SSDV packet
|
|
writeRegister(Channel, REG_PAYLOAD_LENGTH, Config.LoRaDevices[Channel].PayloadLength ? Config.LoRaDevices[Channel].PayloadLength : Length);
|
|
|
|
// go into transmit mode
|
|
setMode(Channel, RF98_MODE_TX);
|
|
}
|
|
|
|
void ShowPacketCounts(int Channel)
|
|
{
|
|
if (Config.LoRaDevices[Channel].InUse)
|
|
{
|
|
int i;
|
|
|
|
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);
|
|
|
|
ChannelPrintf(Channel, 6, 16, "SSDV %d%c %d%c ", SSDVPacketArrays[0].Count, SSDVSendArrayIndex==0 ? '*' : ' ',
|
|
SSDVPacketArrays[1].Count, SSDVSendArrayIndex==1 ? '*' : ' ');
|
|
}
|
|
}
|
|
|
|
void ProcessUploadMessage(int Channel, char *Message)
|
|
{
|
|
// LogMessage("Ch %d: Uploaded message %s\n", Channel, Message);
|
|
}
|
|
|
|
void ProcessCallingMessage(int Channel, char *Message)
|
|
{
|
|
char Payload[16];
|
|
double Frequency;
|
|
int ImplicitOrExplicit, ErrorCoding, Bandwidth, SpreadingFactor, LowDataRateOptimize;
|
|
|
|
ChannelPrintf(Channel, 3, 1, "Calling message %d bytes ", strlen(Message));
|
|
|
|
if (sscanf(Message+2, "%15[^,],%lf,%d,%d,%d,%d,%d,%d",
|
|
Payload,
|
|
&Frequency,
|
|
&ImplicitOrExplicit,
|
|
&ErrorCoding,
|
|
&Bandwidth,
|
|
&SpreadingFactor,
|
|
&LowDataRateOptimize) == 7)
|
|
{
|
|
if (Config.LoRaDevices[Channel].AFC)
|
|
{
|
|
double MasterFrequency;
|
|
|
|
sscanf(Config.LoRaDevices[Channel].Frequency, "%lf", &MasterFrequency);
|
|
|
|
Frequency += Config.LoRaDevices[Channel].activeFreq - MasterFrequency;
|
|
}
|
|
|
|
LogMessage("Ch %d: Calling message, new frequency %7.3lf\n", Channel, Frequency);
|
|
|
|
// Decoded OK
|
|
setMode(Channel, RF98_MODE_SLEEP);
|
|
|
|
// setFrequency(Channel, Config.LoRaDevices[Channel].activeFreq + );
|
|
setFrequency(Channel, Frequency);
|
|
|
|
SetLoRaParameters(Channel, ImplicitOrExplicit, ErrorCoding, Bandwidth, SpreadingFactor, LowDataRateOptimize);
|
|
|
|
setMode(Channel, RF98_MODE_RX_CONTINUOUS);
|
|
|
|
Config.LoRaDevices[Channel].InCallingMode = 1;
|
|
|
|
// ChannelPrintf(Channel, 1, 1, "Channel %d %7.3lfMHz ", Channel, Frequency);
|
|
}
|
|
}
|
|
|
|
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
return size * nmemb;
|
|
}
|
|
|
|
void UploadListenerTelemetry(char *callsign, float gps_lat, float gps_lon, char *antenna)
|
|
{
|
|
int time_epoch = (int)time(NULL);
|
|
if (Config.EnableHabitat)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
char PostFields[300];
|
|
char JsonData[200];
|
|
|
|
/* In windows, this will init the winsock stuff */
|
|
// curl_global_init(CURL_GLOBAL_ALL); // RJH moved to main in gateway.c not thread safe
|
|
|
|
/* get a curl handle */
|
|
curl = curl_easy_init();
|
|
if (curl)
|
|
{
|
|
// So that the response to the curl POST doesn;'t mess up my finely crafted display!
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
|
|
|
|
// Set the URL that is about to receive our POST
|
|
curl_easy_setopt(curl, CURLOPT_URL, "http://habitat.habhub.org/transition/listener_telemetry");
|
|
|
|
// Now specify the POST data
|
|
sprintf(JsonData,"{\"latitude\": %f, \"longitude\": %f}", gps_lat, gps_lon);
|
|
sprintf(PostFields, "callsign=%s&time=%d&data=%s", callsign, time_epoch, JsonData);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, PostFields);
|
|
|
|
// Perform the request, res will get the return code
|
|
res = curl_easy_perform(curl);
|
|
|
|
// Check for errors
|
|
if(res == CURLE_OK)
|
|
{
|
|
LogMessage("Uploaded listener %s position %f,%f\n", Config.Tracker, Config.latitude, Config.longitude);
|
|
}
|
|
else
|
|
{
|
|
LogMessage("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
}
|
|
|
|
// always cleanup
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
// curl_global_cleanup(); // RJH moved to main in gateway.c not thread safe
|
|
|
|
/* In windows, this will init the winsock stuff */
|
|
// curl_global_init(CURL_GLOBAL_ALL); // RJH moved to main in gateway.c not thread safe
|
|
|
|
/* get a curl handle */
|
|
curl = curl_easy_init();
|
|
if (curl)
|
|
{
|
|
// So that the response to the curl POST doesn;'t mess up my finely crafted display!
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
|
|
|
|
// Set the URL that is about to receive our POST
|
|
curl_easy_setopt(curl, CURLOPT_URL, "http://habitat.habhub.org/transition/listener_information");
|
|
|
|
// Now specify the POST data
|
|
sprintf(JsonData, "{\"radio\": \"%s\", \"antenna\": \"%s\"}", "LoRa RFM98W", antenna);
|
|
sprintf(PostFields, "callsign=%s&time=%d&data=%s", Config.Tracker, time_epoch, JsonData);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, PostFields);
|
|
|
|
// Perform the request, res will get the return code
|
|
res = curl_easy_perform(curl);
|
|
|
|
// Check for errors
|
|
if(res != CURLE_OK)
|
|
{
|
|
LogMessage("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
}
|
|
|
|
// always cleanup
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
// curl_global_cleanup(); // RJH moved to main in gateway.c not thread safe
|
|
}
|
|
}
|
|
|
|
|
|
void DoPositionCalcs(Channel)
|
|
{
|
|
unsigned long Now;
|
|
struct tm tm;
|
|
float Climb, Period;
|
|
|
|
strptime(Config.LoRaDevices[Channel].Time, "%H:%M:%S", &tm);
|
|
Now = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec;
|
|
|
|
if ((Config.LoRaDevices[Channel].LastPositionAt > 0) && (Now > Config.LoRaDevices[Channel].LastPositionAt))
|
|
{
|
|
Climb = (float)Config.LoRaDevices[Channel].Altitude - (float)Config.LoRaDevices[Channel].PreviousAltitude;
|
|
Period = (float)Now - (float)Config.LoRaDevices[Channel].LastPositionAt;
|
|
Config.LoRaDevices[Channel].AscentRate = Climb / Period;
|
|
}
|
|
else
|
|
{
|
|
Config.LoRaDevices[Channel].AscentRate = 0;
|
|
}
|
|
|
|
Config.LoRaDevices[Channel].PreviousAltitude = Config.LoRaDevices[Channel].Altitude;
|
|
Config.LoRaDevices[Channel].LastPositionAt = Now;
|
|
|
|
ChannelPrintf(Channel, 4, 1, "%8.5lf, %8.5lf, %05u ",
|
|
Config.LoRaDevices[Channel].Latitude,
|
|
Config.LoRaDevices[Channel].Longitude,
|
|
Config.LoRaDevices[Channel].Altitude);
|
|
}
|
|
|
|
|
|
void ProcessLine(int Channel, char *Line)
|
|
{
|
|
int FieldCount;
|
|
int Speed, Heading, Satellites;
|
|
float TempInt, TempExt;
|
|
|
|
Config.LoRaDevices[Channel].FlightMode = -1;
|
|
|
|
if (Config.EnableDev)
|
|
{
|
|
FieldCount = sscanf(Line+2, "%15[^,],%u,%8[^,],%lf,%lf,%u,%d,%d,%d,%f,%f,%lf,%lf,%lf,%lf,%d,%d,%d,%lf,%d,%d,%d,%d,%lf,%d",
|
|
&(Config.LoRaDevices[Channel].Payload),
|
|
&(Config.LoRaDevices[Channel].Counter),
|
|
&(Config.LoRaDevices[Channel].Time),
|
|
&(Config.LoRaDevices[Channel].Latitude),
|
|
&(Config.LoRaDevices[Channel].Longitude),
|
|
&(Config.LoRaDevices[Channel].Altitude),
|
|
&(Config.LoRaDevices[Channel].Speed),
|
|
&(Config.LoRaDevices[Channel].Heading),
|
|
&Satellites,
|
|
&TempInt, &TempExt,
|
|
&(Config.LoRaDevices[Channel].cda),
|
|
&(Config.LoRaDevices[Channel].PredictedLatitude),
|
|
&(Config.LoRaDevices[Channel].PredictedLongitude),
|
|
&(Config.LoRaDevices[Channel].PredictedLandingSpeed),
|
|
&(Config.LoRaDevices[Channel].PredictedTime),
|
|
&(Config.LoRaDevices[Channel].CompassActual),
|
|
&(Config.LoRaDevices[Channel].CompassTarget),
|
|
&(Config.LoRaDevices[Channel].AirSpeed),
|
|
&(Config.LoRaDevices[Channel].AirDirection),
|
|
&(Config.LoRaDevices[Channel].ServoLeft),
|
|
&(Config.LoRaDevices[Channel].ServoRight),
|
|
&(Config.LoRaDevices[Channel].ServoTime),
|
|
&(Config.LoRaDevices[Channel].GlideRatio),
|
|
&(Config.LoRaDevices[Channel].FlightMode));
|
|
}
|
|
else
|
|
{
|
|
FieldCount = sscanf(Line+2, "%15[^,],%u,%8[^,],%lf,%lf,%u",
|
|
&(Config.LoRaDevices[Channel].Payload),
|
|
&(Config.LoRaDevices[Channel].Counter),
|
|
&(Config.LoRaDevices[Channel].Time),
|
|
&(Config.LoRaDevices[Channel].Latitude),
|
|
&(Config.LoRaDevices[Channel].Longitude),
|
|
&(Config.LoRaDevices[Channel].Altitude));
|
|
}
|
|
}
|
|
|
|
|
|
void ProcessTelemetryMessage(int Channel, char *Message)
|
|
{
|
|
if (strlen(Message+1) < 250)
|
|
{
|
|
int i;
|
|
unsigned char *startmessage, *endmessage;
|
|
|
|
ChannelPrintf(Channel, 3, 1, "Telemetry %d bytes ", strlen(Message+1));
|
|
|
|
endmessage = Message;
|
|
|
|
startmessage = endmessage;
|
|
endmessage = strchr(startmessage, '\n');
|
|
|
|
if (endmessage != NULL)
|
|
{
|
|
time_t now;
|
|
struct tm *tm;
|
|
|
|
*endmessage = '\0';
|
|
|
|
LogTelemetryPacket(startmessage);
|
|
|
|
strcpy(Config.LoRaDevices[Channel].Telemetry, startmessage);
|
|
// UploadTelemetryPacket(startmessage);
|
|
|
|
ProcessLine(Channel, startmessage);
|
|
|
|
|
|
now = time(0);
|
|
tm = localtime(&now);
|
|
|
|
LogMessage("%02d:%02d:%02d Ch%d: %s\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Channel, startmessage);
|
|
}
|
|
|
|
DoPositionCalcs(Channel);
|
|
|
|
Config.LoRaDevices[Channel].TelemetryCount++;
|
|
Config.LoRaDevices[Channel].LastTelemetryPacketAt = time(NULL);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
// SSDV packet
|
|
static uint32_t PreviousCallsignCode=0;
|
|
static int PreviousImageNumber=-1, PreviousPacketNumber=0;
|
|
uint32_t CallsignCode;
|
|
char Callsign[7], *FileMode, *EncodedEncoding, *Base64Data, *EncodedData, HexString[513], Command[1000];
|
|
int output_length, ImageNumber, PacketNumber;
|
|
char filename[100];
|
|
FILE *fp;
|
|
|
|
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("Ch%d: SSDV Packet, Callsign %s, Image %d, Packet %d\n", Channel, Callsign, Message[6], PacketNumber);
|
|
ChannelPrintf(Channel, 3, 1, "SSDV Packet ");
|
|
ChannelPrintf(Channel, 5, 1, "SSDV %s: Image %d, Packet %d", Callsign, Message[6], PacketNumber);
|
|
|
|
PreviousImageNumber = ImageNumber;
|
|
PreviousPacketNumber = PacketNumber;
|
|
PreviousCallsignCode = CallsignCode;
|
|
|
|
// 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)
|
|
{
|
|
int ArrayIndex, PacketIndex;
|
|
|
|
|
|
pthread_mutex_lock(&ssdv_mutex);
|
|
|
|
ArrayIndex = (SSDVSendArrayIndex == 0) ? 1 : 0;
|
|
|
|
// Place in array for upload to server
|
|
PacketIndex = SSDVPacketArrays[ArrayIndex].Count;
|
|
|
|
if (PacketIndex < SSDV_PACKETS)
|
|
{
|
|
// LogMessage("Adding to array %d packet %d\n", ArrayIndex, PacketIndex);
|
|
|
|
// Copy packet etc
|
|
memcpy(SSDVPacketArrays[ArrayIndex].Packets[PacketIndex].Packet, Message, 256);
|
|
strcpy(SSDVPacketArrays[ArrayIndex].Packets[PacketIndex].Callsign, Callsign);
|
|
|
|
// Update packet count
|
|
SSDVPacketArrays[ArrayIndex].Count++;
|
|
}
|
|
else
|
|
{
|
|
LogMessage("No free SSDV packets\n");
|
|
}
|
|
|
|
if (SSDVSendArrayIndex < 0)
|
|
{
|
|
SSDVSendArrayIndex = ArrayIndex;
|
|
}
|
|
pthread_mutex_unlock(&ssdv_mutex);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DIO0_Interrupt(int Channel)
|
|
{
|
|
if (Config.LoRaDevices[Channel].Sending)
|
|
{
|
|
Config.LoRaDevices[Channel].Sending = 0;
|
|
LogMessage("Ch%d: End of Tx\n", Channel);
|
|
|
|
setLoRaMode(Channel);
|
|
SetDefaultLoRaParameters(Channel);
|
|
startReceiving(Channel);
|
|
}
|
|
else
|
|
{
|
|
int Bytes;
|
|
unsigned char Message[257];
|
|
|
|
Bytes = receiveMessage(Channel, Message+1);
|
|
|
|
if (Bytes > 0)
|
|
{
|
|
if (Config.LoRaDevices[Channel].ActivityLED >= 0)
|
|
{
|
|
digitalWrite(Config.LoRaDevices[Channel].ActivityLED, 1);
|
|
LEDCounts[Channel] = 5;
|
|
}
|
|
|
|
if (Message[1] == '!')
|
|
{
|
|
ProcessUploadMessage(Channel, Message+1);
|
|
}
|
|
else if (Message[1] == '^')
|
|
{
|
|
ProcessCallingMessage(Channel, Message+1);
|
|
}
|
|
else if (Message[1] == '$')
|
|
{
|
|
ProcessTelemetryMessage(Channel, Message+1);
|
|
TestMessageForSMSAcknowledgement(Channel, Message+1);
|
|
}
|
|
else if (Message[1] == '>')
|
|
{
|
|
LogMessage("Flight Controller message %d bytes = %s", Bytes, Message+1);
|
|
}
|
|
else if (Message[1] == '*')
|
|
{
|
|
LogMessage("Uplink Command message %d bytes = %s", Bytes, Message+1);
|
|
}
|
|
else if (Message[1] == 0x66)
|
|
{
|
|
ProcessSSDVMessage(Channel, Message);
|
|
}
|
|
else
|
|
{
|
|
LogMessage("Unknown packet type is %02Xh, RSSI %d\n", Message[1], readRegister(Channel, REG_PACKET_RSSI) - 157);
|
|
ChannelPrintf(Channel, 3, 1, "Unknown Packet %d, %d bytes", Message[0], 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;
|
|
}
|
|
|
|
ShowPacketCounts(Channel);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
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)
|
|
{
|
|
fprintf(stderr, "Failed to open SPI port. Try loading spi library with 'gpio load spi'");
|
|
exit(1);
|
|
}
|
|
|
|
// LoRa mode
|
|
setLoRaMode(Channel);
|
|
|
|
SetDefaultLoRaParameters(Channel);
|
|
|
|
startReceiving(Channel);
|
|
}
|
|
}
|
|
|
|
double FrequencyReference(int Channel)
|
|
{
|
|
switch (Config.LoRaDevices[Channel].CurrentBandwidth)
|
|
{
|
|
case BANDWIDTH_7K8: return 7800;
|
|
case BANDWIDTH_10K4: return 10400;
|
|
case BANDWIDTH_15K6: return 15600;
|
|
case BANDWIDTH_20K8: return 20800;
|
|
case BANDWIDTH_31K25: return 31250;
|
|
case BANDWIDTH_41K7: return 41700;
|
|
case BANDWIDTH_62K5: return 62500;
|
|
case BANDWIDTH_125K: return 125000;
|
|
case BANDWIDTH_250K: return 250000;
|
|
case BANDWIDTH_500K: return 500000;
|
|
}
|
|
}
|
|
|
|
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) * (FrequencyReference(Channel) / 500000.0);
|
|
}
|
|
|
|
int receiveMessage(int Channel, unsigned char *message)
|
|
{
|
|
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, readRegister(Channel, REG_PACKET_RSSI) - 157);
|
|
// 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
|
|
{
|
|
int8_t SNR;
|
|
int RSSI;
|
|
|
|
currentAddr = readRegister(Channel, REG_FIFO_RX_CURRENT_ADDR);
|
|
Bytes = readRegister(Channel, REG_RX_NB_BYTES);
|
|
|
|
SNR = readRegister(Channel, REG_PACKET_SNR);
|
|
SNR /= 4;
|
|
RSSI = readRegister(Channel, REG_PACKET_RSSI) - 157;
|
|
if (SNR < 0)
|
|
{
|
|
RSSI += SNR;
|
|
}
|
|
|
|
ChannelPrintf(Channel, 10, 1, "Packet SNR = %d, RSSI = %d ", (int)SNR, RSSI);
|
|
|
|
FreqError = FrequencyError(Channel) / 1000;
|
|
ChannelPrintf(Channel, 11, 1, "Freq. Error = %5.1lfkHz ", 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';
|
|
|
|
LogPacket(Channel, SNR, RSSI, FreqError, Bytes, message[1]);
|
|
|
|
if(Config.LoRaDevices[Channel].AFC && (fabs(FreqError)>0.5))
|
|
{
|
|
ReTune(Channel, FreqError/1000);
|
|
}
|
|
}
|
|
|
|
// Clear all flags
|
|
writeRegister(Channel, REG_IRQ_FLAGS, 0xFF);
|
|
|
|
return Bytes;
|
|
}
|
|
|
|
void ReadString(FILE *fp, char *keyword, char *Result, int Length, int NeedValue)
|
|
{
|
|
char line[100], *token, *value;
|
|
|
|
fseek(fp, 0, SEEK_SET);
|
|
*Result = '\0';
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL)
|
|
{
|
|
token = strtok(line, "=");
|
|
if (strcasecmp(keyword, token) == 0)
|
|
{
|
|
value = strtok(NULL, "\n");
|
|
strcpy(Result, value);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (NeedValue)
|
|
{
|
|
LogMessage("Missing value for '%s' in configuration file\n", keyword);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int ReadInteger(FILE *fp, char *keyword, int NeedValue, int DefaultValue)
|
|
{
|
|
char Temp[64];
|
|
|
|
ReadString(fp, keyword, Temp, sizeof(Temp), NeedValue);
|
|
|
|
if (Temp[0])
|
|
{
|
|
return atoi(Temp);
|
|
}
|
|
|
|
return DefaultValue;
|
|
}
|
|
|
|
float ReadFloat(FILE *fp, char *keyword)
|
|
{
|
|
char Temp[64];
|
|
|
|
ReadString(fp, keyword, Temp, sizeof(Temp), 0);
|
|
|
|
if (Temp[0])
|
|
{
|
|
return atof(Temp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ReadBoolean(FILE *fp, char *keyword, int NeedValue, int *Result)
|
|
{
|
|
char Temp[32];
|
|
|
|
ReadString(fp, keyword, Temp, sizeof(Temp), NeedValue);
|
|
|
|
if (*Temp)
|
|
{
|
|
*Result = (*Temp == '1') || (*Temp == 'Y') || (*Temp == 'y') || (*Temp == 't') || (*Temp == 'T');
|
|
}
|
|
|
|
return *Temp;
|
|
}
|
|
|
|
void LoadConfigFile()
|
|
{
|
|
FILE *fp;
|
|
char *filename = "gateway.txt";
|
|
char Keyword[32];
|
|
int Channel, Temp;
|
|
char TempString[16];
|
|
|
|
Config.EnableHabitat = 1;
|
|
Config.EnableSSDV = 1;
|
|
Config.EnableTelemetryLogging = 0;
|
|
Config.EnablePacketLogging = 0;
|
|
Config.SSDVJpegFolder[0] = '\0';
|
|
Config.ftpServer[0] = '\0';
|
|
Config.ftpUser[0] = '\0';
|
|
Config.ftpPassword[0] = '\0';
|
|
Config.ftpFolder[0] = '\0';
|
|
Config.latitude = -999;
|
|
Config.longitude = -999;
|
|
Config.antenna[0] = '\0';
|
|
Config.EnableDev = 0;
|
|
|
|
if ((fp = fopen(filename, "r")) == NULL)
|
|
{
|
|
printf("\nFailed to open config file %s (error %d - %s).\nPlease check that it exists and has read permission.\n", filename, errno, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
// Receiver config
|
|
ReadString(fp, "tracker", Config.Tracker, sizeof(Config.Tracker), 1);
|
|
LogMessage("Tracker = '%s'\n", Config.Tracker);
|
|
|
|
// Enable uploads
|
|
ReadBoolean(fp, "EnableHabitat", 0, &Config.EnableHabitat);
|
|
ReadBoolean(fp, "EnableSSDV", 0, &Config.EnableSSDV);
|
|
|
|
// Enable telemetry logging
|
|
ReadBoolean(fp, "LogTelemetry", 0, &Config.EnableTelemetryLogging);
|
|
|
|
// Enable packet logging
|
|
ReadBoolean(fp, "LogPackets", 0, &Config.EnablePacketLogging);
|
|
|
|
// Calling mode
|
|
Config.CallingTimeout = ReadInteger(fp, "CallingTimeout", 0, 300);
|
|
|
|
// LED allocations
|
|
Config.NetworkLED = ReadInteger(fp, "NetworkLED", 0, -1);
|
|
Config.InternetLED = ReadInteger(fp, "InternetLED", 0, -1);
|
|
Config.LoRaDevices[0].ActivityLED = ReadInteger(fp, "ActivityLED_0", 0, -1);
|
|
Config.LoRaDevices[1].ActivityLED = ReadInteger(fp, "ActivityLED_1", 0, -1);
|
|
|
|
// Server Port
|
|
Config.ServerPort = ReadInteger(fp, "ServerPort", 0, -1);
|
|
|
|
// SSDV Settings
|
|
ReadString(fp, "jpgFolder", Config.SSDVJpegFolder, sizeof(Config.SSDVJpegFolder), 0);
|
|
if (Config.SSDVJpegFolder[0])
|
|
{
|
|
// Create SSDV Folders
|
|
struct stat st = {0};
|
|
|
|
if (stat(Config.SSDVJpegFolder, &st) == -1)
|
|
{
|
|
mkdir(Config.SSDVJpegFolder, 0777);
|
|
}
|
|
}
|
|
|
|
// ftp images
|
|
ReadString(fp, "ftpserver", Config.ftpServer, sizeof(Config.ftpServer), 0);
|
|
ReadString(fp, "ftpUser", Config.ftpUser, sizeof(Config.ftpUser), 0);
|
|
ReadString(fp, "ftpPassword", Config.ftpPassword, sizeof(Config.ftpPassword), 0);
|
|
ReadString(fp, "ftpFolder", Config.ftpFolder, sizeof(Config.ftpFolder), 0);
|
|
|
|
// Listener
|
|
Config.latitude = ReadFloat(fp, "Latitude");
|
|
Config.longitude = ReadFloat(fp, "Longitude");
|
|
ReadString(fp, "antenna", Config.antenna, sizeof(Config.antenna), 0);
|
|
|
|
// Dev mode
|
|
ReadBoolean(fp, "EnableDev", 0, &Config.EnableDev);
|
|
|
|
// SMS upload to tracker
|
|
Config.SMSFolder[0] = '\0';
|
|
ReadString(fp, "SMSFolder", Config.SMSFolder, sizeof(Config.SMSFolder), 0);
|
|
if (Config.SMSFolder[0])
|
|
{
|
|
LogMessage("Scanning folder %s for messages to upload\n", Config.SMSFolder);
|
|
}
|
|
|
|
for (Channel=0; Channel<=1; Channel++)
|
|
{
|
|
// Defaults
|
|
Config.LoRaDevices[Channel].Frequency[0] = '\0';
|
|
|
|
sprintf(Keyword, "frequency_%d", Channel);
|
|
ReadString(fp, Keyword, Config.LoRaDevices[Channel].Frequency, sizeof(Config.LoRaDevices[Channel].Frequency), 0);
|
|
if (Config.LoRaDevices[Channel].Frequency[0])
|
|
{
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = EXPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_8;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_20K8;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_11;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0x00;
|
|
Config.LoRaDevices[Channel].AFC = FALSE;
|
|
|
|
LogMessage("Channel %d frequency set to %s\n", Channel, Config.LoRaDevices[Channel].Frequency);
|
|
Config.LoRaDevices[Channel].InUse = 1;
|
|
|
|
// DIO0 / DIO5 overrides
|
|
sprintf(Keyword, "DIO0_%d", Channel);
|
|
Config.LoRaDevices[Channel].DIO0 = ReadInteger(fp, Keyword, 0, Config.LoRaDevices[Channel].DIO0);
|
|
|
|
sprintf(Keyword, "DIO5_%d", Channel);
|
|
Config.LoRaDevices[Channel].DIO5 = ReadInteger(fp, Keyword, 0, Config.LoRaDevices[Channel].DIO5);
|
|
|
|
LogMessage("LoRa Channel %d DIO0=%d DIO5=%d\n", Channel, Config.LoRaDevices[Channel].DIO0, Config.LoRaDevices[Channel].DIO5);
|
|
|
|
// Uplink
|
|
sprintf(Keyword, "UplinkTime_%d", Channel);
|
|
Config.LoRaDevices[Channel].UplinkTime = ReadInteger(fp, Keyword, 0, 0);
|
|
sprintf(Keyword, "UplinkCycle_%d", Channel);
|
|
Config.LoRaDevices[Channel].UplinkCycle = ReadInteger(fp, Keyword, 0, 0);
|
|
LogMessage("Channel %d UplinkTime %d Uplink Cycle %d\n", Channel, Config.LoRaDevices[Channel].UplinkTime, Config.LoRaDevices[Channel].UplinkCycle);
|
|
|
|
sprintf(Keyword, "Power_%d", Channel);
|
|
Config.LoRaDevices[Channel].Power = ReadInteger(fp, Keyword, 0, PA_MAX_UK);
|
|
LogMessage("Channel %d power set to %02Xh\n", Channel, Config.LoRaDevices[Channel].Power);
|
|
|
|
|
|
Config.LoRaDevices[Channel].SpeedMode = 0;
|
|
|
|
sprintf(Keyword, "mode_%d", Channel);
|
|
Config.LoRaDevices[Channel].SpeedMode = ReadInteger(fp, Keyword, 0, 0);
|
|
|
|
if (Config.LoRaDevices[Channel].SpeedMode == 5)
|
|
{
|
|
// Calling channel
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = EXPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_8;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_41K7;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_11;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0;
|
|
}
|
|
else if (Config.LoRaDevices[Channel].SpeedMode == 4)
|
|
{
|
|
// Testing
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = IMPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_5;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_250K;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_6;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0;
|
|
}
|
|
else if (Config.LoRaDevices[Channel].SpeedMode == 3)
|
|
{
|
|
// Normal mode for high speed images in 868MHz band
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = EXPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_6;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_250K;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_7;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0;
|
|
}
|
|
else if (Config.LoRaDevices[Channel].SpeedMode == 2)
|
|
{
|
|
// Normal mode for repeater network
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = EXPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_8;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_62K5;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_8;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0x00;
|
|
}
|
|
else if (Config.LoRaDevices[Channel].SpeedMode == 1)
|
|
{
|
|
// Normal mode for SSDV
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = IMPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_5;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_20K8;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_6;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0;
|
|
}
|
|
else
|
|
{
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = EXPLICIT_MODE;
|
|
Config.LoRaDevices[Channel].ErrorCoding = ERROR_CODING_4_8;
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_20K8;
|
|
Config.LoRaDevices[Channel].SpreadingFactor = SPREADING_11;
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0x08;
|
|
}
|
|
|
|
sprintf(Keyword, "sf_%d", Channel);
|
|
Temp = ReadInteger(fp, Keyword, 0, 0);
|
|
if ((Temp >= 6) && (Temp <= 12))
|
|
{
|
|
Config.LoRaDevices[Channel].SpreadingFactor = Temp << 4;
|
|
LogMessage("Setting SF=%d\n", Temp);
|
|
}
|
|
|
|
sprintf(Keyword, "bandwidth_%d", Channel);
|
|
ReadString(fp, Keyword, TempString, sizeof(TempString), 0);
|
|
if (*TempString)
|
|
{
|
|
LogMessage("Setting BW=%s\n", TempString);
|
|
}
|
|
if (strcmp(TempString, "7K8") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_7K8;
|
|
}
|
|
else if (strcmp(TempString, "10K4") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_10K4;
|
|
}
|
|
else if (strcmp(TempString, "15K6") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_15K6;
|
|
}
|
|
else if (strcmp(TempString, "20K8") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_20K8;
|
|
}
|
|
else if (strcmp(TempString, "31K25") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_31K25;
|
|
}
|
|
else if (strcmp(TempString, "41K7") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_41K7;
|
|
}
|
|
else if (strcmp(TempString, "62K5") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_62K5;
|
|
}
|
|
else if (strcmp(TempString, "125K") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_125K;
|
|
}
|
|
else if (strcmp(TempString, "250K") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_250K;
|
|
}
|
|
else if (strcmp(TempString, "500K") == 0)
|
|
{
|
|
Config.LoRaDevices[Channel].Bandwidth = BANDWIDTH_500K;
|
|
}
|
|
|
|
sprintf(Keyword, "implicit_%d", Channel);
|
|
if (ReadBoolean(fp, Keyword, 0, &Temp))
|
|
{
|
|
Config.LoRaDevices[Channel].ImplicitOrExplicit = Temp ? IMPLICIT_MODE : EXPLICIT_MODE;
|
|
}
|
|
|
|
sprintf(Keyword, "coding_%d", Channel);
|
|
Temp = ReadInteger(fp, Keyword, 0, 0);
|
|
if ((Temp >= 5) && (Temp <= 8))
|
|
{
|
|
Config.LoRaDevices[Channel].ErrorCoding = (Temp-4) << 1;
|
|
LogMessage("Setting Error Coding=%d\n", Temp);
|
|
}
|
|
|
|
sprintf(Keyword, "lowopt_%d", Channel);
|
|
if (ReadBoolean(fp, Keyword, 0, &Temp))
|
|
{
|
|
if (Temp)
|
|
{
|
|
Config.LoRaDevices[Channel].LowDataRateOptimize = 0x08;
|
|
}
|
|
}
|
|
|
|
sprintf(Keyword, "AFC_%d", Channel);
|
|
if (ReadBoolean(fp, Keyword, 0, &Temp))
|
|
{
|
|
if (Temp)
|
|
{
|
|
Config.LoRaDevices[Channel].AFC = TRUE;
|
|
ChannelPrintf(Channel, 11, 24, "AFC");
|
|
}
|
|
}
|
|
|
|
// Clear any flags left over from a previous run
|
|
writeRegister(Channel, REG_IRQ_FLAGS, 0xFF);
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
void LoadPayloadFile(int ID)
|
|
{
|
|
FILE *fp;
|
|
char filename[16];
|
|
char Keyword[32];
|
|
int Channel, Temp;
|
|
char TempString[16];
|
|
|
|
sprintf(filename, "payload_%d.txt", ID);
|
|
|
|
if ((fp = fopen(filename, "r")) != NULL)
|
|
{
|
|
LogMessage("Reading payload file %s\n", filename);
|
|
ReadString(fp, "payload", Payloads[ID].Payload, sizeof(Payloads[ID].Payload), 1);
|
|
LogMessage("Payload %d = '%s'\n", ID, Payloads[ID].Payload);
|
|
|
|
Payloads[ID].InUse = 1;
|
|
|
|
fclose(fp);
|
|
}
|
|
else
|
|
{
|
|
strcpy(Payloads[ID].Payload, "Unknown");
|
|
Payloads[ID].InUse = 0;
|
|
}
|
|
}
|
|
|
|
void LoadPayloadFiles(void)
|
|
{
|
|
int ID;
|
|
|
|
for (ID=0; ID<16; ID++)
|
|
{
|
|
LoadPayloadFile(ID);
|
|
}
|
|
}
|
|
|
|
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_YELLOW, COLOR_BLUE);
|
|
|
|
color_set(1, NULL);
|
|
// bkgd(COLOR_PAIR(1));
|
|
// attrset(COLOR_PAIR(1) | A_BOLD);
|
|
|
|
// Title bar
|
|
mvaddstr(0, 17, " LoRa Habitat and SSDV Gateway " VERSION " by daveake ");
|
|
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(2));
|
|
|
|
// wcolor_set(Config.LoRaDevices[Channel].Window, 2, NULL);
|
|
// waddstr(Config.LoRaDevices[Channel].Window, "WINDOW");
|
|
// mvwaddstr(Config.LoRaDevices[Channel].Window, 0, 0, "Window");
|
|
wrefresh(Config.LoRaDevices[Channel].Window);
|
|
}
|
|
|
|
curs_set(0);
|
|
|
|
return mainwin;
|
|
}
|
|
|
|
void CloseDisplay(WINDOW * mainwin)
|
|
{
|
|
/* Clean up after ourselves */
|
|
delwin(mainwin);
|
|
endwin();
|
|
refresh();
|
|
}
|
|
|
|
|
|
uint16_t CRC16(unsigned char *ptr)
|
|
{
|
|
uint16_t CRC, xPolynomial;
|
|
int j;
|
|
|
|
CRC = 0xffff; // Seed
|
|
xPolynomial = 0x1021;
|
|
|
|
for (; *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;
|
|
}
|
|
}
|
|
|
|
return CRC;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
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;
|
|
}
|
|
|
|
int GetTextMessageToUpload(int Channel, char *Message)
|
|
{
|
|
DIR *dp;
|
|
struct dirent *ep;
|
|
int Result;
|
|
|
|
Result = 0;
|
|
|
|
// LogMessage("Checking for SMS file ...\n");
|
|
|
|
if (Config.SMSFolder[0])
|
|
{
|
|
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
|
|
// @jonathenharty: RT @ProjectHeT: #astroPiTest The Essex Space Agency is looking forward to tweeting the real @esa! @GallarottiA: RT @ProjectHeT: #astroPiTest The Essex Space Agency is looking forward to tweeting the real @esa!
|
|
sprintf(Message, "#%d,%s\n", FileNumber, Line);
|
|
|
|
LogMessage("UPLINK: %s", Message);
|
|
Result = 1;
|
|
}
|
|
else
|
|
{
|
|
LogMessage("FAIL\n");
|
|
}
|
|
fclose(fp);
|
|
}
|
|
}
|
|
}
|
|
closedir(dp);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
int GetExternalListOfMissingSSDVPackets(int Channel, char *Message)
|
|
{
|
|
// First, create request file
|
|
FILE *fp;
|
|
|
|
// LogMessage("GetExternalListOfMissingSSDVPackets()\n");
|
|
|
|
// if ((fp = fopen("get_list.txt", "wt")) != NULL)
|
|
{
|
|
int i;
|
|
|
|
// fprintf(fp, "No Message\n");
|
|
// fclose(fp);
|
|
|
|
// LogMessage("File created\n");
|
|
|
|
// Now wait for uplink.txt file to appear.
|
|
// Timeout before the end of our Tx slot if no file appears
|
|
|
|
for (i=0; i<20; 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");
|
|
remove("uplink.txt");
|
|
|
|
return strlen(Message);
|
|
}
|
|
|
|
usleep(100000);
|
|
}
|
|
|
|
// LogMessage("Timed out waiting for file\n");
|
|
// remove("get_list.txt");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
int BuildListOfMissingSSDVPackets(int Channel, char *Message)
|
|
{
|
|
char Temp[10], *Comma;
|
|
int i, FirstMissing;
|
|
|
|
Comma = "";
|
|
FirstMissing = -1;
|
|
sprintf(Message, "!%d:%d=", Config.LoRaDevices[Channel].SSDVPackets[0].ImageNumber, Config.LoRaDevices[Channel].SSDVPackets[0].HighestPacket);
|
|
|
|
for (i=0; i<=Config.LoRaDevices[Channel].SSDVPackets[0].HighestPacket; i++)
|
|
{
|
|
if ((Config.LoRaDevices[Channel].SSDVPackets[0].Packets[i]) || (i == Config.LoRaDevices[Channel].SSDVPackets[0].HighestPacket))
|
|
{
|
|
// This packet present
|
|
if (FirstMissing >= 0)
|
|
{
|
|
if (i > (FirstMissing+1))
|
|
{
|
|
// Group of adjacent missing packets
|
|
sprintf(Temp, "%s%d-%d", Comma, FirstMissing, i-1);
|
|
if ((strlen(Temp) + strlen(Message)) < 255)
|
|
{
|
|
strcat(Message, Temp);
|
|
Comma = ",";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Missing packet is isolated
|
|
sprintf(Temp, "%s%d", Comma, FirstMissing);
|
|
if ((strlen(Temp) + strlen(Message)) < 255)
|
|
{
|
|
strcat(Message, Temp);
|
|
Comma = ",";
|
|
}
|
|
}
|
|
}
|
|
|
|
FirstMissing = -1;
|
|
}
|
|
else
|
|
{
|
|
// This packet missing
|
|
// If previous packet also missing then extend the range
|
|
// Otherwise we need to start the range
|
|
if (FirstMissing < 0)
|
|
{
|
|
FirstMissing = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*Comma)
|
|
{
|
|
strcat(Message, "\n");
|
|
LogMessage("Uplink: %s", Message);
|
|
}
|
|
|
|
return *Comma;
|
|
}
|
|
*/
|
|
|
|
void SendUplinkMessage(int Channel)
|
|
{
|
|
char Message[512];
|
|
|
|
// Decide what type of message we need to send
|
|
if (GetTextMessageToUpload(Channel, Message))
|
|
{
|
|
SendLoRaData(Channel, Message, 255);
|
|
}
|
|
else if (GetExternalListOfMissingSSDVPackets(Channel, Message))
|
|
{
|
|
setFrequency(Channel, 869.5);
|
|
SetLoRaParameters(Channel, EXPLICIT_MODE, ERROR_CODING_4_8, BANDWIDTH_125K, SPREADING_8, 0);
|
|
SendLoRaData(Channel, Message, 255);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
unsigned char Command[200], Telemetry[100], *dest, *src;
|
|
int ch, i;
|
|
int LoopPeriod;
|
|
pthread_t SSDVThread, FTPThread, NetworkThread, HabitatThread, ServerThread;
|
|
WINDOW * mainwin;
|
|
|
|
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);
|
|
|
|
Config.LoRaDevices[0].InUse = 0;
|
|
Config.LoRaDevices[1].InUse = 0;
|
|
|
|
LEDCounts[0] = 0;
|
|
LEDCounts[1] = 0;
|
|
|
|
// Remove any old SSDV files
|
|
// system("rm -f /tmp/*.bin");
|
|
|
|
// Default pin allocations
|
|
|
|
Config.LoRaDevices[0].DIO0 = 6;
|
|
Config.LoRaDevices[0].DIO5 = 5;
|
|
|
|
Config.LoRaDevices[1].DIO0 = 27;
|
|
Config.LoRaDevices[1].DIO5 = 26;
|
|
|
|
LoadConfigFile();
|
|
LoadPayloadFiles();
|
|
|
|
if (wiringPiSetup() < 0)
|
|
{
|
|
fprintf(stderr, "Failed to open wiringPi\n");
|
|
exit(1);
|
|
}
|
|
|
|
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);
|
|
|
|
ShowPacketCounts(0);
|
|
ShowPacketCounts(1);
|
|
|
|
LoopPeriod = 0;
|
|
|
|
if (pthread_create(&SSDVThread, NULL, SSDVLoop, NULL))
|
|
{
|
|
fprintf(stderr, "Error creating SSDV thread %d\n", i);
|
|
return 1;
|
|
}
|
|
|
|
if (pthread_create(&FTPThread, NULL, FTPLoop, NULL))
|
|
{
|
|
fprintf(stderr, "Error creating FTP thread\n");
|
|
return 1;
|
|
}
|
|
|
|
if (pthread_create(&HabitatThread, NULL, HabitatLoop, NULL))
|
|
{
|
|
fprintf(stderr, "Error creating Habitat thread\n");
|
|
return 1;
|
|
}
|
|
|
|
if (Config.ServerPort > 0)
|
|
{
|
|
if (pthread_create(&ServerThread, NULL, ServerLoop, NULL))
|
|
{
|
|
fprintf(stderr, "Error creating 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;
|
|
}
|
|
}
|
|
|
|
if ((Config.latitude > -90) && (Config.longitude > -90))
|
|
{
|
|
UploadListenerTelemetry(Config.Tracker, Config.latitude, Config.longitude, Config.antenna);
|
|
}
|
|
|
|
while (run)
|
|
{
|
|
if ((ch = getch()) != ERR)
|
|
{
|
|
ProcessKeyPress(ch);
|
|
}
|
|
|
|
if (LoopPeriod > 1000)
|
|
{
|
|
// Every 1 second
|
|
int Channel;
|
|
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)
|
|
{
|
|
int8_t SNR;
|
|
|
|
ShowPacketCounts(Channel);
|
|
|
|
ChannelPrintf(Channel, 12, 1, "Current RSSI = %4d ", readRegister(Channel, REG_CURRENT_RSSI) - 157);
|
|
|
|
// if (Config.LoRaDevices[Channel].LastPacketAt > 0)
|
|
// {
|
|
// ChannelPrintf(Channel, 6, 1, "%us since last packet ", (unsigned int)(time(NULL) - Config.LoRaDevices[Channel].LastPacketAt));
|
|
// }
|
|
|
|
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;
|
|
|
|
LogMessage("Return to calling mode\n");
|
|
|
|
setLoRaMode(Channel);
|
|
|
|
SetDefaultLoRaParameters(Channel);
|
|
|
|
setMode(Channel, RF98_MODE_RX_CONTINUOUS);
|
|
|
|
ChannelPrintf(Channel, 1, 1, "Channel %d %sMHz %s mode", Channel, Config.LoRaDevices[Channel].Frequency, Modes[Config.LoRaDevices[Channel].SpeedMode]);
|
|
}
|
|
|
|
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);
|
|
|
|
SendUplinkMessage(Channel);
|
|
}
|
|
}
|
|
|
|
if (LEDCounts[Channel] && (Config.LoRaDevices[Channel].ActivityLED >= 0))
|
|
{
|
|
if (--LEDCounts[Channel] == 0)
|
|
{
|
|
digitalWrite(Config.LoRaDevices[Channel].ActivityLED, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
delay(100);
|
|
LoopPeriod += 100;
|
|
}
|
|
|
|
CloseDisplay(mainwin);
|
|
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;
|
|
}
|
|
|