merging pull request #2 by DL2MF to devel branch

This commit is contained in:
Hans P. Reiser 2019-04-23 13:08:02 +02:00
parent 619631f80e
commit d056ec8b76
13 changed files with 373 additions and 151 deletions

View File

@ -4,14 +4,20 @@ RDZ_TTGO_SONDE
This a simple, experimental, not (well) tested, and incomplete decoder for This a simple, experimental, not (well) tested, and incomplete decoder for
radiosonde RS41 and DFM06/09 on a TTGO LoRa ESP32 with OLED display board. radiosonde RS41 and DFM06/09 on a TTGO LoRa ESP32 with OLED display board.
There have been made some additions for TTGO LoRa ESP32 with only RST button.
Please check also your OLED port settings, both versions use different ports.
You can setup the depending ports in config.txt, OLED Setup is depending on hardware of LoRa board
- TTGO v1: SDA=4 SCL=15, RST=16
- TTGO v2: SDA=21 SCL=22, RST=16
## Button commands ## Button commands
You can use the button on the board (not the reset button, the second one) to You can use the button on the board (not the reset button, the second one) to
issue some commands. The software distinguishes between several inputs: issue some commands. The software distinguishes between several inputs:
SHORT Short button press (<1.5 seconds) - SHORT Short button press (<1.5 seconds)
DOUBLE Short button press, followed by another button press within 0.5 seconds - DOUBLE Short button press, followed by another button press within 0.5 seconds
MID Medium-length button press (2-4 seconds) - MID Medium-length button press (2-4 seconds)
LONG Long button press (>5 seconds) - LONG Long button press (>5 seconds)
## Wireless configuration ## Wireless configuration
@ -39,10 +45,14 @@ for the last 18 frames, if reception was successfull (|) or failed (.)
A DOUBLE press will switch to scanning mode. A DOUBLE press will switch to scanning mode.
A SHORT press will switch to the next channel in channels.txt A SHORT press will switch to the next channel in channels.txt
# Spectrum mode ## Spectrum mode
A medium press will active scan the whole band (400..406 MHz) and display a A medium press will active scan the whole band (400..406 MHz) and display a
spectrum diagram (each line == 50 kHz) spectrum diagram (each line == 50 kHz)
For TTGO boards without configurable button there are some new parameter in config.txt:
- spectrum=10 // 0=off / 1-99 number of seconds to show spectrum after restart
- timer=1 // 0=off / 1= show spectrum countdown timer in spectrum display
- marker=1 // 0=off / 1= show channel edge freq in spectrum display
## Setup ## Setup

View File

@ -3,6 +3,7 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SPIFFS.h> #include <SPIFFS.h>
#include <U8x8lib.h> #include <U8x8lib.h>
#include <U8g2lib.h>
#include <SPI.h> #include <SPI.h>
#include <SX1278FSK.h> #include <SX1278FSK.h>
@ -12,11 +13,6 @@
#define LORA_LED 9 #define LORA_LED 9
// I2C OLED Display works with SSD1306 driver
//#define OLED_SDA 4
//#define OLED_SCL 15
//#define OLED_RST 16
// UNCOMMENT one of the constructor lines below // UNCOMMENT one of the constructor lines below
U8X8_SSD1306_128X64_NONAME_SW_I2C *u8x8=NULL; // initialize later after reading config file U8X8_SSD1306_128X64_NONAME_SW_I2C *u8x8=NULL; // initialize later after reading config file
//U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ OLED_SCL, /* data=*/ OLED_SDA, /* reset=*/ OLED_RST); // Unbuffered, basic graphics, software I2C //U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ OLED_SCL, /* data=*/ OLED_SDA, /* reset=*/ OLED_RST); // Unbuffered, basic graphics, software I2C
@ -29,20 +25,14 @@ AsyncWebServer server(80);
#define LOCALUDPPORT 9002 #define LOCALUDPPORT 9002
// moved to sonde.config
//const char * udpAddress = "192.168.42.20";
//const int udpPort = 9002;
boolean connected = false; boolean connected = false;
WiFiUDP udp; WiFiUDP udp;
// Set LED GPIO // Set LED GPIO
const int ledPin = 2; int ledPin = 1;
// Stores LED state // Stores LED state
String ledState; String ledState;
// Replaces placeholder with LED state value // Replaces placeholder with LED state value
String processor(const String& var){ String processor(const String& var){
Serial.println(var); Serial.println(var);
@ -91,6 +81,7 @@ void setupChannelList() {
} }
int i=0; int i=0;
sonde.clearSonde(); sonde.clearSonde();
Serial.println("Reading channel config:");
while(file.available()) { while(file.available()) {
String line = file.readStringUntil('\n'); String line = file.readStringUntil('\n');
if(!file.available()) break; if(!file.available()) break;
@ -105,8 +96,9 @@ void setupChannelList() {
else if (space[1]=='6') { type=STYPE_DFM06; } else if (space[1]=='6') { type=STYPE_DFM06; }
else continue; else continue;
int active = space[3]=='+'?1:0; int active = space[3]=='+'?1:0;
Serial.printf("Adding %f with type %d (active: %d)\n",freq,type,active); char *launchsite = strchr(line.c_str(), ' ');
sonde.addSonde(freq, type, active); Serial.printf("Add %f - type %d (on/off: %d)- Site: \n",freq,type,active,launchsite);
sonde.addSonde(freq, type, active, launchsite);
i++; i++;
} }
} }
@ -114,7 +106,7 @@ void setupChannelList() {
const char *createQRGForm() { const char *createQRGForm() {
char *ptr = message; char *ptr = message;
strcpy(ptr,"<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"></head><body><form action=\"qrg.html\" method=\"post\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Mode</th></tr>"); strcpy(ptr,"<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"></head><body><form action=\"qrg.html\" method=\"post\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Mode</th></tr>");
for(int i=0; i<10; i++) { for(int i=0; i<sonde.config.maxsonde; i++) {
String s = sondeTypeSelect(i>=sonde.nSonde?2:sonde.sondeList[i].type); String s = sondeTypeSelect(i>=sonde.nSonde?2:sonde.sondeList[i].type);
sprintf(ptr+strlen(ptr), "<tr><td>%d</td><td><input name=\"A%d\" type=\"checkbox\" %s/></td>" sprintf(ptr+strlen(ptr), "<tr><td>%d</td><td><input name=\"A%d\" type=\"checkbox\" %s/></td>"
"<td><input name=\"F%d\" type=\"text\" value=\"%3.3f\"></td>" "<td><input name=\"F%d\" type=\"text\" value=\"%3.3f\"></td>"
@ -161,6 +153,9 @@ const char *handleQRGPost(AsyncWebServerRequest *request) {
f.printf("%3.3f %c %c\n", atof(fstr), typech, active?'+':'-'); f.printf("%3.3f %c %c\n", atof(fstr), typech, active?'+':'-');
} }
f.close(); f.close();
Serial.println("Channel setup finished");
Serial.println();
setupChannelList(); setupChannelList();
} }
@ -252,9 +247,9 @@ void addSondeStatus(char *ptr, int i)
sprintf(ptr+strlen(ptr),"<tr><td id=\"sfreq\">%3.3f MHz, Type: %s</td><tr><td>ID: %s</td></tr><tr><td>QTH: %.6f,%.6f h=%.0fm</td></tr>\n", sprintf(ptr+strlen(ptr),"<tr><td id=\"sfreq\">%3.3f MHz, Type: %s</td><tr><td>ID: %s</td></tr><tr><td>QTH: %.6f,%.6f h=%.0fm</td></tr>\n",
s->freq, sondeTypeStr[s->type], s->freq, sondeTypeStr[s->type],
s->validID?s->id:"<??>", s->validID?s->id:"<??>",
s->lat, s->lon, s->hei); s->lat, s->lon, s->alt);
sprintf(ptr+strlen(ptr), "<tr><td><a target=\"_empty\" href=\"geo:%.6f,%.6f\">Geo-Ref</a> -", s->lat, s->lon); sprintf(ptr+strlen(ptr), "<tr><td><a target=\"_empty\" href=\"geo:%.6f,%.6f\">GEO-App</a> - ", s->lat, s->lon);
sprintf(ptr+strlen(ptr), "<a target=\"_empty\" href=\"https://www.google.com/maps/search/?api=1&query=%.6f,%.6f\">Google map</a> - ", s->lat, s->lon); sprintf(ptr+strlen(ptr), "<a target=\"_empty\" href=\"https://wx.dl2mf.de/?%s\">WX.DL2MF.de</a> - ", s->id);
sprintf(ptr+strlen(ptr), "<a target=\"_empty\" href=\"https://www.openstreetmap.org/?mlat=%.6f&mlon=%.6f&zoom=14\">OSM</a></td></tr>", s->lat, s->lon); sprintf(ptr+strlen(ptr), "<a target=\"_empty\" href=\"https://www.openstreetmap.org/?mlat=%.6f&mlon=%.6f&zoom=14\">OSM</a></td></tr>", s->lat, s->lon);
strcat(ptr, "</table><p/>\n"); strcat(ptr, "</table><p/>\n");
} }
@ -291,8 +286,12 @@ struct st_configitems {
int type; // 0: numeric; i>0 string of length i; -1: separator; -2: type selector int type; // 0: numeric; i>0 string of length i; -1: separator; -2: type selector
void *data; void *data;
}; };
#define N_CONFIG 16 #define N_CONFIG 20
struct st_configitems config_list[N_CONFIG] = { struct st_configitems config_list[N_CONFIG] = {
{"ShowSpectrum (s)", 0, &sonde.config.spectrum},
{"Startfreq (MHz)", 0, &sonde.config.startfreq},
{"Bandwidth (kHz)", 0, &sonde.config.channelbw},
{"---", -1, NULL},
{"Call", 8, sonde.config.call}, {"Call", 8, sonde.config.call},
{"Passcode", 8, sonde.config.passcode}, {"Passcode", 8, sonde.config.passcode},
{"---", -1, NULL}, {"---", -1, NULL},
@ -415,11 +414,11 @@ void SetupAsyncServer() {
const char *fetchWifiPw(const char *id) { const char *fetchWifiPw(const char *id) {
for(int i=0; i<nNetworks; i++) { for(int i=0; i<nNetworks; i++) {
Serial.print("Comparing '"); //Serial.print("Comparing '");
Serial.print(id); //Serial.print(id);
Serial.print("' and '"); //Serial.print("' and '");
Serial.print(networks[i].id.c_str()); //Serial.print(networks[i].id.c_str());
Serial.println("'"); //Serial.println("'");
if(strcmp(id,networks[i].id.c_str())==0) return networks[i].pw.c_str(); if(strcmp(id,networks[i].id.c_str())==0) return networks[i].pw.c_str();
} }
return NULL; return NULL;
@ -471,28 +470,81 @@ int hasKeyPress() {
void setup() void setup()
{ {
char buf[12];
// Open serial communications and wait for port to open: // Open serial communications and wait for port to open:
Serial.begin(115200); Serial.begin(115200);
pinMode(LORA_LED, OUTPUT);
aprs_gencrctab(); aprs_gencrctab();
pinMode(LORA_LED, OUTPUT); // Initialize SPIFFS
// Initialize SPIFFS
if(!SPIFFS.begin(true)){ if(!SPIFFS.begin(true)){
Serial.println("An Error has occurred while mounting SPIFFS"); Serial.println("An Error has occurred while mounting SPIFFS");
return; return;
} }
setupWifiList(); setupConfigData(); // configuration must be read first due to OLED ports!!!
setupConfigData();
button1.pin = sonde.config.button_pin;
u8x8 = new U8X8_SSD1306_128X64_NONAME_SW_I2C(/* clock=*/ sonde.config.oled_scl, /* data=*/ sonde.config.oled_sda, /* reset=*/ sonde.config.oled_rst); // Unbuffered, basic graphics, software I2C u8x8 = new U8X8_SSD1306_128X64_NONAME_SW_I2C(/* clock=*/ sonde.config.oled_scl, /* data=*/ sonde.config.oled_sda, /* reset=*/ sonde.config.oled_rst); // Unbuffered, basic graphics, software I2C
u8x8->begin(); u8x8->begin();
delay(100);
u8x8->clear();
u8x8->setFont(u8x8_font_7x14_1x2_r);
u8x8->drawString(1, 1, "RDZ_TTGO_SONDE");
u8x8->drawString(2, 3, " V0.1e");
u8x8->drawString(1, 5, "Mods by DL2MF");
delay(3000);
sonde.clearDisplay();
setupWifiList();
button1.pin = sonde.config.button_pin;
// == show initial values from config.txt ========================= //
if (sonde.config.debug == 1) {
u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->drawString(0, 0, "Config:");
delay(500);
itoa(sonde.config.oled_sda, buf, 10);
u8x8->drawString(0, 1, " SDA:");
u8x8->drawString(6, 1, buf);
delay(500);
itoa(sonde.config.oled_scl, buf, 10);
u8x8->drawString(0, 2, " SCL:");
u8x8->drawString(6, 2, buf);
delay(500);
itoa(sonde.config.oled_rst, buf, 10);
u8x8->drawString(0, 3, " RST:");
u8x8->drawString(6, 3, buf);
delay(1000);
itoa(sonde.config.led_pin, buf, 10);
u8x8->drawString(0, 4, " LED:");
u8x8->drawString(6, 4, buf);
delay(500);
itoa(sonde.config.spectrum, buf, 10);
u8x8->drawString(0, 5, " SPEC:");
u8x8->drawString(6, 5, buf);
delay(500);
itoa(sonde.config.maxsonde, buf, 10);
u8x8->drawString(0, 6, " MAX:");
u8x8->drawString(6, 6, buf);
delay(5000);
sonde.clearDisplay();
}
// == show initial values from config.txt ========================= //
#if 0 #if 0
// == check the radio chip by setting default frequency =========== //
if(rs41.setFrequency(402700000)==0) { if(rs41.setFrequency(402700000)==0) {
Serial.println(F("Setting freq: SUCCESS ")); Serial.println(F("Setting freq: SUCCESS "));
} else { } else {
@ -501,6 +553,7 @@ void setup()
float f = sx1278.getFrequency(); float f = sx1278.getFrequency();
Serial.print("Frequency set to "); Serial.print("Frequency set to ");
Serial.println(f); Serial.println(f);
// == check the radio chip by setting default frequency =========== //
#endif #endif
//sx1278.setLNAGain(-48); //sx1278.setLNAGain(-48);
@ -511,11 +564,10 @@ void setup()
Serial.println(gain); Serial.println(gain);
// Print a success message // Print a success message
Serial.println(F("sx1278 configured finished")); Serial.println(F("SX1278 configuration finished"));
Serial.println();
Serial.println("Setup finished"); Serial.println("Setup finished");
Serial.println();
// int returnValue = pthread_create(&wifithread, NULL, wifiloop, (void *)0); // int returnValue = pthread_create(&wifithread, NULL, wifiloop, (void *)0);
// if (returnValue) { // if (returnValue) {
@ -526,20 +578,26 @@ void setup()
// Handle button press // Handle button press
attachInterrupt(button1.pin, buttonISR, CHANGE); attachInterrupt(button1.pin, buttonISR, CHANGE);
// == setup default channel list if qrg.txt read fails =========== //
setupChannelList(); setupChannelList();
#if 0 #if 0
sonde.clearSonde(); sonde.clearSonde();
sonde.addSonde(402.300, STYPE_RS41);
sonde.addSonde(402.700, STYPE_RS41); sonde.addSonde(402.700, STYPE_RS41);
sonde.addSonde(405.700, STYPE_RS41);
sonde.addSonde(405.900, STYPE_RS41);
sonde.addSonde(403.450, STYPE_DFM09); sonde.addSonde(403.450, STYPE_DFM09);
Serial.println("No channel config file, using defaults!");
Serial.println();
#endif #endif
/// not here, done by sonde.setup(): rs41.setup(); /// not here, done by sonde.setup(): rs41.setup();
// == setup default channel list if qrg.txt read fails =========== //
sonde.setup(); sonde.setup();
} }
enum MainState { ST_DECODER, ST_SCANNER, ST_SPECTRUM, ST_WIFISCAN }; enum MainState { ST_DECODER, ST_SCANNER, ST_SPECTRUM, ST_WIFISCAN };
static MainState mainState = ST_DECODER; static MainState mainState = ST_WIFISCAN;
void enterMode(int mode) { void enterMode(int mode) {
mainState = (MainState)mode; mainState = (MainState)mode;
@ -571,7 +629,7 @@ void loopDecoder() {
Serial.println("Sending position via UDP"); Serial.println("Sending position via UDP");
SondeInfo *s = sonde.si(); SondeInfo *s = sonde.si();
char raw[201]; char raw[201];
const char *str = aprs_senddata(s->lat, s->lon, s->hei, s->hs, s->dir, s->vs, sondeTypeStr[s->type], s->id, "TE0ST", const char *str = aprs_senddata(s->lat, s->lon, s->alt, s->hs, s->dir, s->vs, sondeTypeStr[s->type], s->id, "TE0ST",
sonde.config.udpfeed.symbol); sonde.config.udpfeed.symbol);
int rawlen = aprsstr_mon2raw(str, raw, APRS_MAXLEN); int rawlen = aprsstr_mon2raw(str, raw, APRS_MAXLEN);
Serial.print("Sending: "); Serial.println(raw); Serial.print("Sending: "); Serial.println(raw);
@ -601,7 +659,7 @@ void loopScanner() {
} }
// receiveFrame returns 0 on success, 1 on timeout // receiveFrame returns 0 on success, 1 on timeout
int res = sonde.receiveFrame(); // Maybe instead of receiveFrame, just detect if right type is present? TODO int res = sonde.receiveFrame(); // Maybe instead of receiveFrame, just detect if right type is present? TODO
Serial.print("Scanner: receiveFrame returned"); Serial.print("Scanner: receiveFrame returned: ");
Serial.println(res); Serial.println(res);
if(res==0) { if(res==0) {
enterMode(ST_DECODER); enterMode(ST_DECODER);
@ -666,44 +724,55 @@ void WiFiEvent(WiFiEvent_t event){
} }
} }
static char* _scan[2]={"/","\\"}; static char* _scan[2]={"/","\\"};
void loopWifiScan() { void loopWifiScan() {
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->drawString(0,0,"WiFi Scan..."); if (sonde.config.wifi != 0) {
u8x8->drawString(0,0,"WiFi Scan...");
}
else if (sonde.config.wifiap != 0) {
u8x8->drawString(0,0,"WiFi AP-Mode:");
}
int line=0; int line=0;
int cnt=0; int cnt=0;
int marker=0;
char buf[5];
WiFi.disconnect(true); WiFi.disconnect(true);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
const char *id, *pw; const char *id, *pw;
char idstr[64]="test"; char idstr[64]="test";
int n = WiFi.scanNetworks();
for (int i = 0; i < n; i++) { if (sonde.config.wifi != 0) {
Serial.print("Network name: "); int n = WiFi.scanNetworks();
Serial.println(WiFi.SSID(i)); for (int i = 0; i < n; i++) {
u8x8->drawString(0,1+line,WiFi.SSID(i).c_str()); Serial.print("Network name: ");
line = (line+1)%5; Serial.println(WiFi.SSID(i));
Serial.print("Signal strength: "); u8x8->drawString(0,1+line,WiFi.SSID(i).c_str());
Serial.println(WiFi.RSSI(i)); line = (line+1)%5;
Serial.print("MAC address: "); Serial.print("Signal strength: ");
Serial.println(WiFi.BSSIDstr(i)); Serial.println(WiFi.RSSI(i));
Serial.print("Encryption type: "); Serial.print("MAC address: ");
String encryptionTypeDescription = translateEncryptionType(WiFi.encryptionType(i)); Serial.println(WiFi.BSSIDstr(i));
Serial.println(encryptionTypeDescription); Serial.print("Encryption type: ");
Serial.println("-----------------------"); String encryptionTypeDescription = translateEncryptionType(WiFi.encryptionType(i));
id=WiFi.SSID(i).c_str(); Serial.println(encryptionTypeDescription);
pw=fetchWifiPw(id); Serial.println("-----------------------");
if(pw) { strncpy(idstr, id, 63); } id=WiFi.SSID(i).c_str();
} pw=fetchWifiPw(id);
if(!pw) { pw="test"; } if(pw) { strncpy(idstr, id, 63); }
Serial.print("Connecting to: "); Serial.println(idstr); }
u8x8->drawString(0,6, "Conn:"); if(!pw) { pw="test"; }
u8x8->drawString(6,6, idstr); Serial.print("Connecting to: "); Serial.println(idstr);
//register event handler u8x8->drawString(0,6, "Conn:");
WiFi.onEvent(WiFiEvent); u8x8->drawString(6,6, idstr);
//register event handler
WiFi.onEvent(WiFiEvent);
WiFi.begin(idstr, pw);
}
WiFi.begin(idstr, pw);
while(WiFi.status() != WL_CONNECTED) { while(WiFi.status() != WL_CONNECTED) {
delay(500); delay(500);
Serial.print("."); Serial.print(".");
@ -718,20 +787,58 @@ void loopWifiScan() {
#endif #endif
if(cnt==15) { if(cnt==15) {
WiFi.disconnect(true); WiFi.disconnect(true);
delay(1000);
WiFi.softAP(networks[0].id.c_str(),networks[0].pw.c_str()); if (sonde.config.wifiap != 0) { // enable WiFi AP mode in config.txt: wifi=1
IPAddress myIP = WiFi.softAPIP(); delay(1000);
Serial.print("AP IP address: "); WiFi.softAP(networks[0].id.c_str(),networks[0].pw.c_str());
Serial.println(myIP); IPAddress myIP = WiFi.softAPIP();
u8x8->drawString(0,6, "AP: "); Serial.print("AP IP address: ");
u8x8->drawString(6,6, networks[0].id.c_str()); Serial.println(myIP);
sonde.setIP(myIP.toString().c_str(), true); u8x8->drawString(0,6, "AP: ");
sonde.updateDisplayIP(); u8x8->drawString(6,6, networks[0].id.c_str());
SetupAsyncServer(); sonde.setIP(myIP.toString().c_str(), true);
delay(3000); sonde.updateDisplayIP();
enterMode(ST_DECODER); SetupAsyncServer();
delay(3000);
}
if (sonde.config.spectrum != 0) { // enable Spectrum in config.txt: spectrum=number_of_seconds
sonde.clearDisplay();
u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->drawString(0, 0, "Spectrum Scan...");
delay(500);
enterMode(ST_SPECTRUM);
for (int i = 0; i < sonde.config.spectrum; i++) {
scanner.scan();
scanner.plotResult();
if (sonde.config.marker != 0) {
itoa((sonde.config.startfreq), buf, 10);
u8x8->drawString(0, 1, buf);
u8x8->drawString(7, 1, "MHz");
itoa((sonde.config.startfreq + 6), buf, 10);
u8x8->drawString(13, 1, buf);
}
if (sonde.config.timer != 0) {
itoa((sonde.config.spectrum - i), buf, 10);
if (sonde.config.marker != 0) {
marker = 1;
}
u8x8->drawString(0, 1+marker, buf);
u8x8->drawString(2, 1+marker, "Sec.");
}
}
delay(1000);
}
enterMode(ST_SCANNER);
return; return;
} }
} }
Serial.println(""); Serial.println("");
@ -742,9 +849,10 @@ void loopWifiScan() {
sonde.updateDisplayIP(); sonde.updateDisplayIP();
SetupAsyncServer(); SetupAsyncServer();
delay(2000); delay(2000);
enterMode(ST_DECODER);
}
// enterMode(ST_DECODER); ### 2019-04-20 - changed DL2MF
enterMode(ST_SCANNER);
}
void loop() { void loop() {
Serial.println("Running main loop"); Serial.println("Running main loop");

View File

@ -1,14 +1,39 @@
# Input button #-------------------------------#
# Hardware depending settings
#-------------------------------#
button_pin=0 button_pin=0
# oled: SDA, SCL, RST (4,15,16 für TTGO v1) # LED port
oled_sda=4 led_pin=25
oled_scl=15 # OLED Setup is depending on hardware of LoRa board
# TTGO v1: SDA=4 SCL=15, RST=16
# TTGO v2: SDA=21 SCL=22, RST=16
oled_sda=21
oled_scl=22
oled_rst=16 oled_rst=16
#-------------------------------#
noisefloor=-130 # General config settings
call=NOCALL #-------------------------------#
maxsonde=20
debug=0
wifi=0
wifiap=1
#-------------------------------#
# Spectrum display settings
#-------------------------------#
startfreq=400
channelbw=10
spectrum=10
timer=1
noisefloor=-110
marker=1
#-------------------------------#
# APRS settings
#-------------------------------#
call=N0CALL
passcode=12345 passcode=12345
#-------------------------------#
# axudp for sending to aprsmap # axudp for sending to aprsmap
#-------------------------------#
# local use only, do not feed to public services # local use only, do not feed to public services
# data not sanities / quality checked, outliers not filtered out # data not sanities / quality checked, outliers not filtered out
axudp.active=1 axudp.active=1
@ -17,7 +42,9 @@ axudp.port=9002
axudp.symbol=/O axudp.symbol=/O
axudp.highrate=1 axudp.highrate=1
axudp.idformat=0 axudp.idformat=0
#-------------------------------#
# maybe some time in the future # maybe some time in the future
#-------------------------------#
# currently simply not implemented, no need to put anything here anyway # currently simply not implemented, no need to put anything here anyway
tcp.active=0 tcp.active=0
tcp.host=radiosondy.info tcp.host=radiosondy.info
@ -25,4 +52,6 @@ tcp.port=14590
tcp.symbol=/O tcp.symbol=/O
tcp.highrate=20 tcp.highrate=20
tcp.idformat=0 tcp.idformat=0
#-------------------------------#
# EOF
#-------------------------------#

View File

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>ESP32 Web Server</title> <title>RDZSonde Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
</head> </head>
<body> <body>
<h1>ESP32 Web Server</h1> <h2>RDZSonde Server</h2>
<!-- <!--
<p>GPIO state: <strong> %STATE%</strong></p> <p>GPIO state: <strong> %STATE%</strong></p>
<p><a href="/on"><button class="button">ON</button></a></p> <p><a href="/on"><button class="button">ON</button></a></p>
@ -16,19 +16,20 @@
<div class="tab"> <div class="tab">
<button class="tablinks" onclick="selTab(event,'QRG')" id="defaultTab">QRG</button> <button class="tablinks" onclick="selTab(event,'QRG')" id="defaultTab">QRG</button>
<button class="tablinks" onclick="selTab(event,'WIFI')">WLAN</button> <button class="tablinks" onclick="selTab(event,'WLAN')">WLAN</button>
<button class="tablinks" onclick="selTab(event,'Data')">Data</button> <button class="tablinks" onclick="selTab(event,'Data')">Data</button>
<button class="tablinks" onclick="selTab(event,'WebWX')">Webwx</button>
<button class="tablinks" onclick="selTab(event,'Config')">Config</button> <button class="tablinks" onclick="selTab(event,'Config')">Config</button>
<button class="tablinks" onclick="selTab(event,'About')">About</button> <button class="tablinks" onclick="selTab(event,'About')">About</button>
</div> </div>
<div id="QRG" class="tabcontent"> <div id="QRG" class="tabcontent">
<h3> QRG</h3> <h3> QRG - Setup</h3>
<iframe src="qrg.html" style="border:none;" width="100%%" height="100%%"></iframe> <iframe src="qrg.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div> </div>
<div id="WIFI" class="tabcontent"> <div id="WLAN" class="tabcontent">
<h3> WIFI</h3> <h3> WLAN - Settings</h3>
<iframe src="wifi.html" style="border:none;" width="100%%" height="100%%"></iframe> <iframe src="wifi.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div> </div>
@ -37,14 +38,24 @@
<iframe src="status.html" style="border:none;" width="100%%" height="100%%"></iframe> <iframe src="status.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div> </div>
<div id="Data" class="tabcontent">
<h3>wetterson.de</h3>
<iframe src="https://wetterson.de/karte/" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="Config" class="tabcontent"> <div id="Config" class="tabcontent">
<h3>Config</h3> <h3>Configuration</h3>
<iframe src="config.html" style="border:none;" width="100%%" height="100%%"></iframe> <iframe src="config.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div> </div>
<div id="About" class="tabcontent"> <div id="About" class="tabcontent">
<h3>About</h3> <h3>About</h3>
RDZSonde RDZSonde - OpenSource<br>
Copyright &copy; 2019<br>
by Hans P. Reiser, DL9RDZ<br>
(devel-version 2019-04-23)<br>
-------------------------------<br>
with mods by Meinhard Guenther, DL2MF<br>
</div> </div>
<script> <script>

View File

@ -1,6 +1,4 @@
RDZsonde AUTORX
RDZsonde 12345678
DinoGast SONDERX
Schokolade radiosonde
AndroidDD
dl9rdzhr

View File

@ -1,8 +1,23 @@
# Frequency in Mhz (format nnn.nnn) # Frequency in Mhz (format nnn.nnn)
# Type (4=RS41, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09) # Type (4=RS41, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09)
# #
402.700 4 + 402.300 4 - Greifswald
402.300 4 + 402.500 4 - Schleswig
403.450 9 + 402.700 4 + HH-Sasel
405.100 4 - 403.000 4 - DeBilt
404.100 4 + Norderney
404.300 4 - Schleswig_2
404.500 4 - Meppen
404.700 4 - Greifswald_2
405.100 4 - Lindenberg
405.700 4 + Bergen
405.900 4 + Bergen_2
405.100 4 + Meppen_2
405.300 4 - Essen
403.330 9 - TrUebPl
403.450 9 - TrUebPl
403.470 9 - TrUebPl
403.850 9 - TrUebPl
403.870 9 - TrUebPl
403.890 9 - TrUebPl
# end # end

View File

@ -228,12 +228,12 @@ int DFM::decodeDAT(uint8_t *dat)
break; break;
case 4: case 4:
{ {
float hei, vv; float lat, vv;
hei = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + dat[3]; lat = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + dat[3];
vv = (int16_t)( (dat[4]<<8) | dat[5] ); vv = (int16_t)( (dat[4]<<8) | dat[5] );
Serial.print("GPS-height: "); Serial.print(hei*0.01); Serial.print("GPS-height: "); Serial.print(lat*0.01);
Serial.print(", vv: "); Serial.print(vv*0.01); Serial.print(", vv: "); Serial.print(vv*0.01);
sonde.si()->hei = hei*0.01; sonde.si()->lat = lat*0.01;
sonde.si()->vs = vv*0.01; sonde.si()->vs = vv*0.01;
sonde.si()->validPos |= 0x0C; sonde.si()->validPos |= 0x0C;
} }

View File

@ -351,7 +351,7 @@ static void posrs41(const byte b[], uint32_t b_len, uint32_t p)
Serial.print("m/s "); Serial.print("m/s ");
Serial.print(getcard16(b, b_len, p+18UL)&255UL); Serial.print(getcard16(b, b_len, p+18UL)&255UL);
Serial.print("Sats"); Serial.print("Sats");
sonde.si()->hei = heig; sonde.si()->alt = heig;
sonde.si()->validPos = true; sonde.si()->validPos = true;
} /* end posrs41() */ } /* end posrs41() */

View File

@ -2,19 +2,26 @@
#include <SX1278FSK.h> #include <SX1278FSK.h>
#include <U8x8lib.h> #include <U8x8lib.h>
#include "Sonde.h"
extern U8X8_SSD1306_128X64_NONAME_SW_I2C *u8x8; extern U8X8_SSD1306_128X64_NONAME_SW_I2C *u8x8;
#define CHANBW 10 #define CHANBW 10
#define PIXSAMPL (50/CHANBW) #define PIXSAMPL (50/CHANBW)
#define SMOOTH 3 #define SMOOTH 3
#define STARTF 400000000 //#define STARTF 401000000
#define NCHAN ((int)(6000/CHANBW)) #define NCHAN ((int)(6000/CHANBW))
double STARTF = (sonde.config.startfreq * 1000000);
//int CHANBW = (sonde.config.channelbw);
//int NCHAN = ((int)(6000/CHANBW));
//int PIXSAMPL = (50/CHANBW);
int scanresult[NCHAN]; int scanresult[NCHAN];
int scandisp[NCHAN/PIXSAMPL]; int scandisp[NCHAN/PIXSAMPL];
#define PLOT_N 120 #define PLOT_N 128
#define TICK1 (120/6) #define TICK1 (128/6)
#define TICK2 (TICK1/4) #define TICK2 (TICK1/4)
#define PLOT_MIN -250 #define PLOT_MIN -250
#define PLOT_SCALE(x) (x<PLOT_MIN?0:(x-PLOT_MIN)/2) #define PLOT_SCALE(x) (x<PLOT_MIN?0:(x-PLOT_MIN)/2)
@ -30,7 +37,7 @@ void Scanner::fillTiles(uint8_t *row, int value) {
} }
/* /*
* There are 16*8 columns to plot, NPLOT must be lower than that * There are 16*8 columns to plot, NPLOT must be lower than that
* currently, we use 120 * 50kHz channels * currently, we use 128 * 50kHz channels
* There are 8*8 values to plot; MIN is bottom end, * There are 8*8 values to plot; MIN is bottom end,
*/ */
uint8_t tiles[16] = { 0x0f,0x0f,0x0f,0x0f,0xf0,0xf0,0xf0,0xf0, 1, 3, 7, 15, 31, 63, 127, 255}; uint8_t tiles[16] = { 0x0f,0x0f,0x0f,0x0f,0xf0,0xf0,0xf0,0xf0, 1, 3, 7, 15, 31, 63, 127, 255};

View File

@ -52,10 +52,18 @@ Sonde::Sonde() {
config.oled_sda = 4; config.oled_sda = 4;
config.oled_scl = 15; config.oled_scl = 15;
config.oled_rst = 16; config.oled_rst = 16;
config.noisefloor = -130; config.noisefloor = -130;
strcpy(config.call,"NOCALL"); strcpy(config.call,"NOCALL");
strcpy(config.passcode, "---"); strcpy(config.passcode, "---");
config.maxsonde=15;
config.debug=0;
config.wifi=1;
config.wifiap=1;
config.startfreq=400;
config.channelbw=10;
config.spectrum=10;
config.timer=0;
config.marker=0;
config.udpfeed.active = 1; config.udpfeed.active = 1;
config.udpfeed.type = 0; config.udpfeed.type = 0;
strcpy(config.udpfeed.host, "192.168.42.20"); strcpy(config.udpfeed.host, "192.168.42.20");
@ -80,7 +88,7 @@ void Sonde::setConfig(const char *cfg) {
char *val = s+1; char *val = s+1;
*s=0; s--; *s=0; s--;
while(s>cfg && (*s==' '||*s=='\t')) { *s=0; s--; } while(s>cfg && (*s==' '||*s=='\t')) { *s=0; s--; }
Serial.printf("handling option '%s'='%s'\n", cfg, val); Serial.printf("configuration option '%s'=%s \n", cfg, val);
if(strcmp(cfg,"noisefloor")==0) { if(strcmp(cfg,"noisefloor")==0) {
config.noisefloor = atoi(val); config.noisefloor = atoi(val);
if(config.noisefloor==0) config.noisefloor=-130; if(config.noisefloor==0) config.noisefloor=-130;
@ -90,12 +98,32 @@ void Sonde::setConfig(const char *cfg) {
strncpy(config.passcode, val, 9); strncpy(config.passcode, val, 9);
} else if(strcmp(cfg,"button_pin")==0) { } else if(strcmp(cfg,"button_pin")==0) {
config.button_pin = atoi(val); config.button_pin = atoi(val);
} else if(strcmp(cfg,"led_pin")==0) {
config.led_pin = atoi(val);
} else if(strcmp(cfg,"oled_sda")==0) { } else if(strcmp(cfg,"oled_sda")==0) {
config.oled_sda = atoi(val); config.oled_sda = atoi(val);
} else if(strcmp(cfg,"oled_scl")==0) { } else if(strcmp(cfg,"oled_scl")==0) {
config.oled_scl = atoi(val); config.oled_scl = atoi(val);
} else if(strcmp(cfg,"oled_rst")==0) { } else if(strcmp(cfg,"oled_rst")==0) {
config.oled_rst = atoi(val); config.oled_rst = atoi(val);
} else if(strcmp(cfg,"maxsonde")==0) {
config.maxsonde = atoi(val);
} else if(strcmp(cfg,"debug")==0) {
config.debug = atoi(val);
} else if(strcmp(cfg,"wifi")==0) {
config.wifi = atoi(val);
} else if(strcmp(cfg,"wifiap")==0) {
config.wifiap = atoi(val);
} else if(strcmp(cfg,"startfreq")==0) {
config.startfreq = atoi(val);
} else if(strcmp(cfg,"channelbw")==0) {
config.channelbw = atoi(val);
} else if(strcmp(cfg,"spectrum")==0) {
config.spectrum = atoi(val);
} else if(strcmp(cfg,"timer")==0) {
config.timer = atoi(val);
} else if(strcmp(cfg,"marker")==0) {
config.marker = atoi(val);
} else if(strcmp(cfg,"axudp.active")==0) { } else if(strcmp(cfg,"axudp.active")==0) {
config.udpfeed.active = atoi(val)>0; config.udpfeed.active = atoi(val)>0;
} else if(strcmp(cfg,"axudp.host")==0) { } else if(strcmp(cfg,"axudp.host")==0) {
@ -121,7 +149,7 @@ void Sonde::setConfig(const char *cfg) {
} else if(strcmp(cfg,"tcp.idformat")==0) { } else if(strcmp(cfg,"tcp.idformat")==0) {
config.tcpfeed.idformat = atoi(val); config.tcpfeed.idformat = atoi(val);
} else { } else {
Serial.printf("Invalid config option '%s'='%s'\n", cfg, val); Serial.printf("Invalid config option '%s'=%s \n", cfg, val);
} }
} }
@ -146,21 +174,22 @@ void Sonde::setIP(const char *ip, bool AP) {
void Sonde::clearSonde() { void Sonde::clearSonde() {
nSonde = 0; nSonde = 0;
} }
void Sonde::addSonde(float frequency, SondeType type, int active) { void Sonde::addSonde(float frequency, SondeType type, int active, char *launchsite) {
if(nSonde>=MAXSONDE) { if(nSonde>=config.maxsonde) {
Serial.println("Cannot add another sonde, MAXSONDE reached"); Serial.println("Cannot add another sonde, MAXSONDE reached");
return; return;
} }
sondeList[nSonde].type = type; sondeList[nSonde].type = type;
sondeList[nSonde].freq = frequency; sondeList[nSonde].freq = frequency;
sondeList[nSonde].active = active; sondeList[nSonde].active = active;
sondeList[nSonde].launchsite = launchsite;
memcpy(sondeList[nSonde].rxStat, "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", 18); // unknown/undefined memcpy(sondeList[nSonde].rxStat, "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", 18); // unknown/undefined
nSonde++; nSonde++;
} }
void Sonde::nextConfig() { void Sonde::nextConfig() {
currentSonde++; currentSonde++;
// Skip non-active entries (but don't loop forever if there are no active ones // Skip non-active entries (but don't loop forever if there are no active ones
for(int i=0; i<MAXSONDE; i++) { for(int i=0; i<config.maxsonde; i++) {
if(!sondeList[currentSonde].active) { if(!sondeList[currentSonde].active) {
currentSonde++; currentSonde++;
if(currentSonde>=nSonde) currentSonde=0; if(currentSonde>=nSonde) currentSonde=0;
@ -230,7 +259,7 @@ void Sonde::updateDisplayPos2() {
u8x8->drawString(10,4," "); u8x8->drawString(10,4," ");
return; return;
} }
snprintf(buf, 16, si()->hei>999?" %5.0fm":" %3.1fm", si()->hei); snprintf(buf, 16, si()->alt>999?" %5.0fm":" %3.1fm", si()->alt);
u8x8->drawString((10+6-strlen(buf)),2,buf); u8x8->drawString((10+6-strlen(buf)),2,buf);
snprintf(buf, 16, si()->hs>99?" %3.0f":" %2.1f", si()->hs); snprintf(buf, 16, si()->hs>99?" %3.0f":" %2.1f", si()->hs);
u8x8->drawString((10+4-strlen(buf)),3,buf); u8x8->drawString((10+4-strlen(buf)),3,buf);
@ -276,7 +305,9 @@ void Sonde::updateDisplayRXConfig() {
u8x8->drawString(0,0, sondeTypeStr[si()->type]); u8x8->drawString(0,0, sondeTypeStr[si()->type]);
snprintf(buf, 16, "%3.3f MHz", si()->freq); snprintf(buf, 16, "%3.3f MHz", si()->freq);
u8x8->drawString(5,0, buf); u8x8->drawString(5,0, buf);
//snprintf(buf, 8, "%s", si()->launchsite);
//u8x8->drawString(0,5, buf);
u8x8->drawTile(14,3,2,kmh_tiles);
} }
void Sonde::updateDisplayIP() { void Sonde::updateDisplayIP() {
@ -287,11 +318,13 @@ void Sonde::updateDisplayIP() {
// 40x.xxx MHz // 40x.xxx MHz
void Sonde::updateDisplayScanner() { void Sonde::updateDisplayScanner() {
char buf[16]; char buf[16];
u8x8->setFont(u8x8_font_7x14_1x2_r); u8x8->setFont(u8x8_font_7x14_1x2_r);
u8x8->drawString(0, 0, "Probing"); u8x8->drawString(0, 0, "Scan:");
u8x8->drawString(8, 0, sondeTypeStr[si()->type]); u8x8->drawString(8, 0, sondeTypeStr[si()->type]);
snprintf(buf, 16, "%3.3f MHz", si()->freq); snprintf(buf, 16, "%3.3f MHz", si()->freq);
u8x8->drawString(0,3, buf); u8x8->drawString(0,3, buf);
//snprintf(buf, 8, "%s", si()->launchsite);
//u8x8->drawString(0,5, buf);
updateDisplayIP(); updateDisplayIP();
} }
@ -304,7 +337,7 @@ void Sonde::updateDisplay()
updateDisplayPos2(); updateDisplayPos2();
updateDisplayRSSI(); updateDisplayRSSI();
updateStat(); updateStat();
updateDisplayIP(); updateDisplayIP();
} }
void Sonde::clearDisplay() { void Sonde::clearDisplay() {

View File

@ -13,13 +13,23 @@ enum SondeType { STYPE_DFM06, STYPE_DFM09, STYPE_RS41 };
extern const char *sondeTypeStr[5]; extern const char *sondeTypeStr[5];
typedef struct st_rdzconfig { typedef struct st_rdzconfig {
int button_pin; int button_pin; // pin number of second button (for some boards)
int oled_sda; int led_pin; // pin number of LED
int oled_scl; int oled_sda; // OLED data pin
int oled_rst; int oled_scl; // OLED clock pin
int oled_rst; // OLED reset pin
int debug; // show port and config options after reboot
int wifi; // connect to known WLAN 0=skip
int wifiap; // enable/disable WiFi AccessPoint mode 0=disable
int startfreq; // spectrum display start freq (400, 401, ...)
int channelbw; // spectrum channel bandwidth (valid: 5, 10, 20, 25, 50, 100 kHz)
int spectrum; // show freq spectrum for n seconds 0=disable
int timer; // show remaining time in spectrum 0=disable
int marker; // show freq marker in spectrum 0=disable
int maxsonde; // number of max sonde in scan (range=1-99)
int noisefloor; // for spectrum display int noisefloor; // for spectrum display
char call[9]; char call[9]; // APRS callsign
char passcode[9]; char passcode[9]; // APRS passcode
// for now, one feed for each type is enough, but might get extended to more? // for now, one feed for each type is enough, but might get extended to more?
struct st_feedinfo udpfeed; // target for AXUDP messages struct st_feedinfo udpfeed; // target for AXUDP messages
struct st_feedinfo tcpfeed; // target for APRS-IS TCP connections struct st_feedinfo tcpfeed; // target for APRS-IS TCP connections
@ -27,28 +37,29 @@ typedef struct st_rdzconfig {
typedef struct st_sondeinfo { typedef struct st_sondeinfo {
// receiver configuration // receiver configuration
bool active; bool active;
SondeType type; SondeType type;
float freq; float freq;
// decoded ID // decoded ID
char id[10]; char id[10];
bool validID; bool validID;
char *launchsite;
// decoded position // decoded position
float lat; float lat; // latitude
float lon; float lon; // longitude
float hei; float alt; // altitude
float vs; float vs; // vertical speed
float hs; float hs; // horizontal speed
float dir; // 0..360 float dir; // 0..360
uint8_t validPos; // bit pattern for validity of above 6 fields uint8_t validPos; // bit pattern for validity of above 6 fields
// RSSI from receiver // RSSI from receiver
int rssi; int rssi; // signal strength
uint8_t rxStat[20]; uint8_t rxStat[20];
} SondeInfo; } SondeInfo;
// rxState: 0=undef[empty] 1=timeout[.] 2=errro[E] 3=ok[1] // rxState: 0=undef[empty] 1=timeout[.] 2=errro[E] 3=ok[1]
#define MAXSONDE 10 #define MAXSONDE 99
class Sonde class Sonde
{ {
@ -63,7 +74,7 @@ public:
void setConfig(const char *str); void setConfig(const char *str);
void clearSonde(); void clearSonde();
void addSonde(float frequency, SondeType type, int active); void addSonde(float frequency, SondeType type, int active, char *launchsite);
void nextConfig(); void nextConfig();
void setup(); void setup();

View File

@ -251,7 +251,7 @@ static uint32_t dao91(double x)
char b[201]; char b[201];
char raw[201]; char raw[201];
char * aprs_senddata(float lat, float lon, float hei, float speed, float dir, float climb, const char *type, const char *objname, const char *usercall, const char *sym) char * aprs_senddata(float lat, float lon, float alt, float speed, float dir, float climb, const char *type, const char *objname, const char *usercall, const char *sym)
{ {
*b=0; *b=0;
aprsstr_append(b, usercall); aprsstr_append(b, usercall);
@ -281,9 +281,9 @@ char * aprs_senddata(float lat, float lon, float hei, float speed, float dir, fl
snprintf(b+i, APRS_MAXLEN-i, "%03d/%03d", realcard(dir+1.5), realcard(speed*1.0/KNOTS+0.5)); snprintf(b+i, APRS_MAXLEN-i, "%03d/%03d", realcard(dir+1.5), realcard(speed*1.0/KNOTS+0.5));
} }
#endif #endif
if(hei>0.5) { if(alt>0.5) {
i=strlen(b); i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "/A=%06d", realcard(hei*FEET+0.5)); snprintf(b+i, APRS_MAXLEN-i, "/A=%06d", realcard(alt*FEET+0.5));
} }
#if 1 #if 1
int dao=1; int dao=1;

View File

@ -20,7 +20,7 @@ typedef struct st_feedinfo {
#define APRS_MAXLEN 201 #define APRS_MAXLEN 201
void aprs_gencrctab(void); void aprs_gencrctab(void);
int aprsstr_mon2raw(const char *mon, char raw[], int raw_len); int aprsstr_mon2raw(const char *mon, char raw[], int raw_len);
char * aprs_senddata(float lat, float lon, float hei, float speed, float dir, float climb, const char *type, const char *objname, const char *usercall, const char *sym); char * aprs_senddata(float lat, float lon, float alt, float speed, float dir, float climb, const char *type, const char *objname, const char *usercall, const char *sym);
#endif #endif