Merge branch 'dl9rdz:devel' into devel

pull/182/head
eben80 2021-09-19 20:06:59 +02:00 zatwierdzone przez GitHub
commit 838c16c5c6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
12 zmienionych plików z 687 dodań i 246 usunięć

Wyświetl plik

@ -13,6 +13,7 @@
#include <MicroNMEA.h>
#include <Ticker.h>
#include "esp_heap_caps.h"
#include "soc/rtc_wdt.h"
#include "src/SX1278FSK.h"
#include "src/Sonde.h"
@ -22,6 +23,7 @@
#include "src/rs92gps.h"
#include "src/aprs.h"
#include "src/ShFreqImport.h"
#include "src/RS41.h"
#if FEATURE_MQTT
#include "src/mqtt.h"
@ -62,7 +64,8 @@ WiFiClient client;
#define SONDEHUB_STATION_UPDATE_TIME (60*60*1000) // 60 min
#define SONDEHUB_MOBILE_STATION_UPDATE_TIME (30*1000) // 30 sec
WiFiClient shclient; // Sondehub v2
char shImportInterval = 0, shImport = 0;
int shImportInterval = 0;
char shImport = 0;
unsigned long time_last_update = 0;
/* SH_LOC_OFF: never send position information to SondeHub
SH_LOC_FIXED: send fixed position (if specified in config) to sondehub
@ -73,8 +76,8 @@ unsigned long time_last_update = 0;
enum { SH_LOC_OFF, SH_LOC_FIXED, SH_LOC_CHASE, SH_LOC_AUTO };
/* auto mode is chase if valid GPS position and (no fixed location entered OR valid GPS position and distance in lat/lon deg to fixed location > threshold) */
#define MIN_LOC_AUTO_DIST 200 /* meter */
#define SH_LOC_AUTO_IS_CHASE ( gpsPos.valid && ( (isnan(sonde.config.sondehub.lat) || isnan(sonde.config.sondehub.lon) ) || \
calcLatLonDist( gpsPos.lat, gpsPos.lon, sonde.config.sondehub.lat, sonde.config.sondehub.lon ) > MIN_LOC_AUTO_DIST ) )
#define SH_LOC_AUTO_IS_CHASE ( gpsPos.valid && ( (isnan(sonde.config.rxlat) || isnan(sonde.config.rxlon) ) || \
calcLatLonDist( gpsPos.lat, gpsPos.lon, sonde.config.rxlat, sonde.config.rxlon ) > MIN_LOC_AUTO_DIST ) )
#endif
extern float calcLatLonDist(float lat1, float lon1, float lat2, float lon2);
@ -145,6 +148,18 @@ int readLine(Stream &stream, char *buffer, int maxlen) {
// Replaces placeholder with LED state value
String processor(const String& var) {
Serial.println(var);
if (var == "MAPCENTER") {
double lat, lon;
if(gpsPos.valid) { lat=gpsPos.lat; lon=gpsPos.lon; }
else { lat = sonde.config.rxlat; lon = sonde.config.rxlon; }
if( !isnan(lat) && !isnan(lon) ) {
char p[40];
snprintf(p, 40, "%g,%g", lat, lon);
return String(p);
} else {
return String("48,13");
}
}
if (var == "VERSION_NAME") {
return String(version_name);
}
@ -153,7 +168,7 @@ String processor(const String& var) {
}
if (var == "FULLNAMEID") {
char tmp[128];
snprintf(tmp, 128, "%s-%c%d", version_id, SPIFFS_MAJOR+'A'-1, SPIFFS_MINOR);
snprintf(tmp, 128, "%s-%c%d", version_id, SPIFFS_MAJOR + 'A' - 1, SPIFFS_MINOR);
return String(tmp);
}
if (var == "AUTODETECT_INFO") {
@ -308,13 +323,11 @@ const char *createQRGForm() {
const char *handleQRGPost(AsyncWebServerRequest *request) {
char label[10];
// parameters: a_i, f_1, t_i (active/frequency/type)
#if 1
File file = SPIFFS.open("/qrg.txt", "w");
if (!file) {
Serial.println("Error while opening '/qrg.txt' for writing");
return "Error while opening '/qrg.txt' for writing";
}
#endif
Serial.println("Handling post request");
#if 0
int params = request->params();
@ -347,9 +360,7 @@ const char *handleQRGPost(AsyncWebServerRequest *request) {
}
file.close();
Serial.println("Channel setup finished");
Serial.println();
delay(500);
Serial.println("Channel setup finished\n");
setupChannelList();
return "";
}
@ -422,14 +433,14 @@ const char *createSondeHubMap() {
HTMLBODY(ptr, "map.html");
if (!sonde.config.sondehub.active) {
strcat(ptr, "<div>NOTE: SondeHub uploading is not enabled, detected sonde will not be visable on map</div>");
if ((*s->ser == 0) && ( !isnan(sonde.config.sondehub.lat))) {
sprintf(ptr + strlen(ptr), "<iframe src=\"https://sondehub.org/#!mc=%f,%f&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:95vh\"></iframe>", sonde.config.sondehub.lat, sonde.config.sondehub.lon);
if ((*s->ser == 0) && ( !isnan(sonde.config.rxlat))) {
sprintf(ptr + strlen(ptr), "<iframe src=\"https://sondehub.org/#!mc=%f,%f&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:95vh\"></iframe>", sonde.config.rxlat, sonde.config.rxlon);
} else {
sprintf(ptr + strlen(ptr), "<iframe src=\"https://sondehub.org/%s\" style=\"border:1px solid #00A3D3;border-radius:20px;height:95vh\"></iframe>", s-> ser);
}
} else {
if ((*s->ser == 0) && (!isnan(sonde.config.sondehub.lat))) {
sprintf(ptr, "<iframe src=\"https://sondehub.org/#!mc=%f,%f&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%%\"></iframe>", sonde.config.sondehub.lat, sonde.config.sondehub.lon);
if ((*s->ser == 0) && (!isnan(sonde.config.rxlat))) {
sprintf(ptr, "<iframe src=\"https://sondehub.org/#!mc=%f,%f&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%%\"></iframe>", sonde.config.rxlat, sonde.config.rxlon);
} else {
sprintf(ptr, "<iframe src=\"https://sondehub.org/%s\" style=\"border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%%\"></iframe>", s-> ser);
}
@ -526,11 +537,9 @@ const char *createLiveJson() {
strcpy(ptr, "{");
SondeInfo *s = &sonde.sondeList[sonde.currentSonde];
if (s->validID) {
sprintf(ptr + strlen(ptr), "\"sonde\": {\"id\": \"%s\", \"freq\": %3.3f, \"type\": \"%s\", \"lat\": %.6f, \"lon\": %.6f, \"alt\": %.0f, \"speed\": %.1f, \"dir\": %.0f, \"climb\": %.1f }", s->id, s->freq, sondeTypeStr[s->type], s->lat, s->lon, s->alt, s->hs, s->dir, s->vs);
} else {
sprintf(ptr + strlen(ptr), "\"sonde\": {\"launchsite\": \"%s\",\"freq\": %3.3f, \"type\": \"%s\" }", s->launchsite, s->freq, sondeTypeStr[s->type]);
}
sprintf(ptr + strlen(ptr), "\"res\": %d, \"rssi\": %d, \"sonde\": {\"vframe\": %d, \"time\": %d,\"id\": \"%s\", \"freq\": %3.3f, \"type\": \"%s\","
"\"lat\": %.6f, \"lon\": %.6f, \"alt\": %.0f, \"speed\": %.1f, \"dir\": %.0f, \"climb\": %.1f, \"launchsite\": \"%s\" }",
s->rxStat[0], s->rssi, s->vframe, s->time, s->id, s->freq, sondeTypeStr[s->type], s->lat, s->lon, s->alt, s->hs, s->dir, s->vs, s->launchsite);
if (sonde.config.gps_rxd < 0) {
// gps disabled
@ -567,11 +576,12 @@ void setupConfigData() {
String line = readLine(file); //file.readStringUntil('\n');
sonde.setConfig(line.c_str());
}
int shII = atoi(sonde.config.sondehub.fimport);
if(shImportInterval > shII) shImportInterval = shII;
sonde.checkConfig(); // eliminate invalid entries
shImportInterval = 5; // refresh now in 5 seconds
}
#if 0
struct st_configitems config_list[] = {
/* General config settings */
{"", "Software configuration", -5, NULL},
@ -672,10 +682,116 @@ struct st_configitems config_list[] = {
{"sondehub.antenna", "Antenna (optional, visisble on SondeHub tracker)", 63, &sonde.config.sondehub.antenna},
{"sondehub.email", "SondeHub email (optional, only used to contact in case of upload errors)", 63, &sonde.config.sondehub.email},
{"sondehub.fimport", "SondeHub freq import (interval/maxdist/maxage [min/km/min])", 18, &sonde.config.sondehub.fimport},
};
#endif
#else
struct st_configitems config_list[] = {
/* General config settings */
{"wifi", 0, &sonde.config.wifi},
{"debug", 0, &sonde.config.debug},
{"maxsonde", 0, &sonde.config.maxsonde},
{"rxlat", -7, &sonde.config.rxlat},
{"rxlon", -7, &sonde.config.rxlon},
{"rxalt", -7, &sonde.config.rxalt},
{"screenfile", 0, &sonde.config.screenfile},
{"display", -6, sonde.config.display},
/* Spectrum display settings */
{"spectrum", 0, &sonde.config.spectrum},
{"startfreq", 0, &sonde.config.startfreq},
{"channelbw", 0, &sonde.config.channelbw},
{"marker", 0, &sonde.config.marker},
{"noisefloor", 0, &sonde.config.noisefloor},
/* decoder settings */
{"freqofs", 0, &sonde.config.freqofs},
{"rs41.agcbw", 0, &sonde.config.rs41.agcbw},
{"rs41.rxbw", 0, &sonde.config.rs41.rxbw},
{"rs92.rxbw", 0, &sonde.config.rs92.rxbw},
{"rs92.alt2d", 0, &sonde.config.rs92.alt2d},
{"dfm.agcbw", 0, &sonde.config.dfm.agcbw},
{"dfm.rxbw", 0, &sonde.config.dfm.rxbw},
{"m10m20.agcbw", 0, &sonde.config.m10m20.agcbw},
{"m10m20.rxbw", 0, &sonde.config.m10m20.rxbw},
{"mp3h.agcbw", 0, &sonde.config.mp3h.agcbw},
{"mp3h.rxbw", 0, &sonde.config.mp3h.rxbw},
{"ephftp", 39, &sonde.config.ephftp},
/* APRS settings */
{"call", 8, sonde.config.call},
{"passcode", 0, &sonde.config.passcode},
/* KISS tnc settings */
{"kisstnc.active", 0, &sonde.config.kisstnc.active},
{"kisstnc.idformat", -2, &sonde.config.kisstnc.idformat},
/* AXUDP settings */
{"axudp.active", -3, &sonde.config.udpfeed.active},
{"axudp.host", 63, sonde.config.udpfeed.host},
{"axudp.port", 0, &sonde.config.udpfeed.port},
{"axudp.idformat", -2, &sonde.config.udpfeed.idformat},
{"axudp.highrate", 0, &sonde.config.udpfeed.highrate},
/* APRS TCP settings, current not used */
{"tcp.active", -3, &sonde.config.tcpfeed.active},
{"tcp.host", 63, sonde.config.tcpfeed.host},
{"tcp.port", 0, &sonde.config.tcpfeed.port},
{"tcp.idformat", -2, &sonde.config.tcpfeed.idformat},
{"tcp.highrate", 0, &sonde.config.tcpfeed.highrate},
#if FEATURE_MQTT
/* MQTT */
{"mqtt.active", 0, &sonde.config.mqtt.active},
{"mqtt.id", 63, &sonde.config.mqtt.id},
{"mqtt.host", 63, &sonde.config.mqtt.host},
{"mqtt.port", 0, &sonde.config.mqtt.port},
{"mqtt.username", 63, &sonde.config.mqtt.username},
{"mqtt.password", 63, &sonde.config.mqtt.password},
{"mqtt.prefix", 63, &sonde.config.mqtt.prefix},
#endif
/* Hardware dependeing settings */
{"disptype", 0, &sonde.config.disptype},
{"norx_timeout", 0, &sonde.config.norx_timeout},
{"oled_sda", 0, &sonde.config.oled_sda},
{"oled_scl", 0, &sonde.config.oled_scl},
{"oled_rst", 0, &sonde.config.oled_rst},
{"tft_rs", 0, &sonde.config.tft_rs},
{"tft_cs", 0, &sonde.config.tft_cs},
{"tft_orient", 0, &sonde.config.tft_orient},
{"tft_spifreq", 0, &sonde.config.tft_spifreq},
{"button_pin", -4, &sonde.config.button_pin},
{"button2_pin", -4, &sonde.config.button2_pin},
{"button2_axp", 0, &sonde.config.button2_axp},
{"touch_thresh", 0, &sonde.config.touch_thresh},
{"power_pout", 0, &sonde.config.power_pout},
{"led_pout", 0, &sonde.config.led_pout},
{"gps_rxd", 0, &sonde.config.gps_rxd},
{"gps_txd", 0, &sonde.config.gps_txd},
{"batt_adc", 0, &sonde.config.batt_adc},
#if 1
{"sx1278_ss", 0, &sonde.config.sx1278_ss},
{"sx1278_miso", 0, &sonde.config.sx1278_miso},
{"sx1278_mosi", 0, &sonde.config.sx1278_mosi},
{"sx1278_sck", 0, &sonde.config.sx1278_sck},
#endif
{"mdnsname", 14, &sonde.config.mdnsname},
#if FEATURE_SONDEHUB
/* SondeHub settings */
{"sondehub.active", 0, &sonde.config.sondehub.active},
{"sondehub.chase", 0, &sonde.config.sondehub.chase},
{"sondehub.host", 63, &sonde.config.sondehub.host},
{"sondehub.callsign", 63, &sonde.config.sondehub.callsign},
{"sondehub.antenna", 63, &sonde.config.sondehub.antenna},
{"sondehub.email", 63, &sonde.config.sondehub.email},
{"sondehub.fiactive", 0, &sonde.config.sondehub.fiactive},
{"sondehub.fiinterval", 0, &sonde.config.sondehub.fiinterval},
{"sondehub.fimaxdist", 0, &sonde.config.sondehub.fimaxdist},
{"sondehub.fimaxage", 0, &sonde.config.sondehub.fimaxage},
#endif
};
#endif
const int N_CONFIG = (sizeof(config_list) / sizeof(struct st_configitems));
#if 0
// old code, no longer needed (in js now)
void addConfigStringEntry(char *ptr, int idx, const char *label, int len, char *field) {
sprintf(ptr + strlen(ptr), "<tr><td>%s</td><td><input name=\"CFG%d\" type=\"text\" value=\"%s\"/></td></tr>\n",
label, idx, field);
@ -733,11 +849,55 @@ void addConfigInt8List(char *ptr, int idx, const char *label, int8_t *list) {
}
strcat(ptr, "\"/></td></tr>\n");
}
#endif
const char *createConfigForm() {
char *ptr = message;
strcpy(ptr, HTMLHEAD); strcat(ptr, "</head>");
HTMLBODY(ptr, "config.html");
strcat(ptr, "<div id=\"cfgtab\"></div>");
strcat(ptr, "<script src=\"cfg.js\"></script>");
strcat(ptr, "<script>\n");
sprintf(ptr + strlen(ptr), "var scr=\"Using /screens%d.txt", Display::getScreenIndex(sonde.config.screenfile));
for (int i = 0; i < disp.nLayouts; i++) {
sprintf(ptr + strlen(ptr), "<br>%d=%s", i, disp.layouts[i].label);
}
strcat(ptr, "\";\n");
strcat(ptr, "var cf=new Map();\n");
for (int i = 0; i < N_CONFIG; i++) {
sprintf(ptr + strlen(ptr), "cf.set(\"%s\", \"", config_list[i].name);
switch (config_list[i].type) {
case -4:
case -3:
case -2:
case 0:
sprintf(ptr + strlen(ptr), "%d", *(int *)config_list[i].data);
break;
case -6: // list
{
int8_t *l = (int8_t *)config_list[i].data;
if (*l == -1) strcat(ptr, "0");
else {
sprintf(ptr + strlen(ptr), "%d", l[0]);
l++;
}
while (*l != -1) {
sprintf(ptr + strlen(ptr), ",%d", *l);
l++;
}
}
break;
case -7: // double
if (!isnan(*(double *)config_list[i].data))
sprintf(ptr + strlen(ptr), "%g", *(double *)config_list[i].data);
break;
default: // string
strcat(ptr, (char *)config_list[i].data);
}
strcat(ptr, "\");\n");
}
strcat(ptr, "configTable();\n </script>");
#if 0
strcat(ptr, "<table><tr><th>Option</th><th>Value</th></tr>");
for (int i = 0; i < N_CONFIG; i++) {
Serial.printf("%d: %s -- %d\n", i, config_list[i].label, strlen(ptr));
@ -773,6 +933,7 @@ const char *createConfigForm() {
}
strcat(ptr, "</table>");
//</div><div class=\"footer\"><input type=\"submit\" class=\"update\" value=\"Update\"/>");
#endif
HTMLSAVEBUTTON(ptr);
HTMLBODYEND(ptr);
Serial.printf("Config form: size=%d bytes\n", strlen(message));
@ -791,8 +952,8 @@ const char *handleConfigPost(AsyncWebServerRequest *request) {
}
#endif
Serial.println("File open for writing.");
#if 1
int params = request->params();
#if 0
for (int i = 0; i < params; i++) {
String param = request->getParam(i)->name();
Serial.println(param.c_str());
@ -801,13 +962,26 @@ const char *handleConfigPost(AsyncWebServerRequest *request) {
for (int i = 0; i < params; i++) {
String strlabel = request->getParam(i)->name();
const char *label = strlabel.c_str();
if (label[strlen(label) - 1] == '#') continue;
#if 0
if (strncmp(label, "CFG", 3) != 0) continue;
int idx = atoi(label + 3);
Serial.printf("idx is %d\n", idx);
if (config_list[idx].type == -1) continue; // skip separator entries, should not happen
#endif
AsyncWebParameter *value = request->getParam(label, true);
if (!value) continue;
String strvalue = value->value();
if ( strcmp(label, "button_pin") == 0 ||
strcmp(label, "button2_pin") == 0) {
AsyncWebParameter *touch = request->getParam(strlabel + "#", true);
if (touch) {
int i = atoi(strvalue.c_str());
if (i != -1 && i != 255) i += 128;
strvalue = String(i);
}
}
#if 0
if (config_list[idx].type == -4) { // input button port with "touch" checkbox
char tmp[10];
snprintf(tmp, 10, "TO%d", idx);
@ -819,7 +993,10 @@ const char *handleConfigPost(AsyncWebServerRequest *request) {
}
}
Serial.printf("Processing %s=%s\n", config_list[idx].name, strvalue.c_str());
int wlen = f.printf("%s=%s\n", config_list[idx].name, strvalue.c_str());
#endif
Serial.printf("Processing %s=%s\n", label, strvalue.c_str());
//int wlen = f.printf("%s=%s\n", config_list[idx].name, strvalue.c_str());
int wlen = f.printf("%s=%s\n", label, strvalue.c_str());
Serial.printf("Written bytes: %d\n", wlen);
}
Serial.printf("Flushing file\n");
@ -1324,7 +1501,7 @@ void SetupAsyncServer() {
if (url.endsWith(".gpx"))
request->send(200, "application/gpx+xml", sendGPX(request));
else {
// TODO: set correct type for .js
// TODO: set correct type for .js
request->send(SPIFFS, url, "text/html");
Serial.printf("URL is %s\n", url.c_str());
//request->send(404);
@ -1750,9 +1927,11 @@ void IRAM_ATTR button2ISR() {
int getKeyPress() {
KeyPress p = button1.pressed;
button1.pressed = KP_NONE;
#if 0
int x = digitalRead(button1.pin);
Serial.printf("Debug: bdd1=%ld, bdd2=%ld\b", bdd1, bdd2);
Serial.printf("button1 press (dbl:%d) (now:%d): %d at %ld (%d)\n", button1.doublepress, x, p, button1.keydownTime, button1.numberKeyPresses);
#endif
return p;
}
@ -1863,7 +2042,7 @@ void setup()
{
char buf[12];
// Open serial communications and wait for port to open:
Serial.begin(115200);
Serial.begin(921600 /*115200*/);
for (int i = 0; i < 39; i++) {
int v = gpio_get_level((gpio_num_t)i);
Serial.printf("%d:%d ", i, v);
@ -2111,16 +2290,8 @@ void setup()
// == setup default channel list if qrg.txt read fails =========== //
setupChannelList();
#if 0
sonde.clearSonde();
sonde.addSonde(402.700, STYPE_RS41);
sonde.addSonde(405.700, STYPE_RS41);
sonde.addSonde(405.900, STYPE_RS41);
sonde.addSonde(403.450, STYPE_DFM09);
Serial.println("No channel config file, using defaults!");
Serial.println();
#endif
setupChannelList();
/// not here, done by sonde.setup(): rs41.setup();
// == setup default channel list if qrg.txt read fails =========== //
#ifndef DISABLE_SX1278
@ -2242,9 +2413,19 @@ void loopDecoder() {
action = (int)(res >> 8);
// TODO: update displayed sonde?
#if 0
static int i = 0;
if (i++ > 20) {
i = 0;
rtc_wdt_protect_off();
rtc_wdt_disable();
heap_caps_dump(MALLOC_CAP_8BIT);
}
#endif
if (action != ACT_NONE) {
int newact = sonde.updateState(action);
Serial.printf("MAIN: loopDecoder: action %s (%d) => %d [current: main=%d, rxtask=%d]\n", action2text(action), action, newact, sonde.currentSonde, rxtask.currentSonde);
Serial.printf("MAIN: loopDecoder: action %02x (%s) => %d [current: main=%d, rxtask=%d]\n", action, action2text(action), newact, sonde.currentSonde, rxtask.currentSonde);
action = newact;
if (action != 255) {
if (action == ACT_DISPLAY_SPECTRUM) {
@ -2296,7 +2477,7 @@ void loopDecoder() {
}
#if FEATURE_SONDEHUB
sondehub_handle_fimport(&shclient);
sondehub_reply_handler(&shclient);
#endif
// wifi (axudp) or bluetooth (bttnc) active => send packet
@ -2905,7 +3086,7 @@ void execOTA() {
dispxs = dispys = 1;
char uh[17];
strncpy(uh, updateHost, 17);
uh[16]=0;
uh[16] = 0;
disp.rdis->drawString(0, 0, uh);
} else {
disp.rdis->setFont(5);
@ -2932,34 +3113,42 @@ void execOTA() {
int type = 0;
int res = fetchHTTPheader(&type);
if(res < 0) { return; }
if (res < 0) {
return;
}
// process data...
while(client.available()) {
while (client.available()) {
// get header...
char fn[128];
fn[0] = '/';
client.readBytesUntil('\n', fn+1, 128);
client.readBytesUntil('\n', fn + 1, 128);
char *sz = strchr(fn, ' ');
if(!sz) { client.stop(); return; }
if (!sz) {
client.stop();
return;
}
*sz = 0;
int len = atoi(sz+1);
int len = atoi(sz + 1);
Serial.printf("Updating file %s (%d bytes)\n", fn, len);
char fnstr[17];
memset(fnstr, ' ', 16);
strncpy(fnstr, fn, strlen(fn));
fnstr[16]=0;
fnstr[16] = 0;
disp.rdis->drawString(0, 2 * dispys, fnstr);
File f = SPIFFS.open(fn, FILE_WRITE);
// read sz bytes........
while(len>0) {
unsigned char buf[1024];
int r = client.read(buf, len>1024? 1024:len);
if(r==-1) { client.stop(); return; }
f.write(buf, r);
len -= r;
while (len > 0) {
unsigned char buf[1024];
int r = client.read(buf, len > 1024 ? 1024 : len);
if (r == -1) {
client.stop();
return;
}
f.write(buf, r);
len -= r;
}
}
client.stop();
client.stop();
Serial.print("Connecting to: "); Serial.println(updateHost);
// Connect to Update host
@ -2968,26 +3157,26 @@ void execOTA() {
return;
}
// Connection succeeded, fecthing the bin
Serial.printf("Fetching bin: %supdate.ino.bin\n", updatePrefix);
disp.rdis->drawString(0, 3 * dispys, "Fetching update");
// Connection succeeded, fecthing the bin
Serial.printf("Fetching bin: %supdate.ino.bin\n", updatePrefix);
disp.rdis->drawString(0, 3 * dispys, "Fetching update");
// Get the contents of the bin file
client.printf("GET %supdate.ino.bin HTTP/1.1\r\n"
"Host: %s\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n\r\n",
updatePrefix, updateHost);
// Get the contents of the bin file
client.printf("GET %supdate.ino.bin HTTP/1.1\r\n"
"Host: %s\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n\r\n",
updatePrefix, updateHost);
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
int validType = 0;
contentLength = fetchHTTPheader( &validType );
if(validType==1) isValidContentType = true;
if (validType == 1) isValidContentType = true;
// Check what is the contentLength and if content type is `application/octet-stream`
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
@ -3045,75 +3234,75 @@ void execOTA() {
}
int fetchHTTPheader(int *validType) {
int contentLength = -1;
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
int contentLength = -1;
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
return -1;
}
}
// Once the response is available, check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
{{BIN FILE CONTENTS}}
*/
while (client.available()) {
// read line till \n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
return -1;
}
}
// Once the response is available, check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
{{BIN FILE CONTENTS}}
*/
while (client.available()) {
// read line till \n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
return -1;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
if(validType) *validType = 1;
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
if (validType) *validType = 1;
}
}
return contentLength;
}
return contentLength;
}
@ -3220,17 +3409,13 @@ void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
// Only send email if provided
if (strlen(conf->email) != 0) {
sprintf(w,
"\"uploader_contact_email\": \"%s\",",
conf->email);
sprintf(w, "\"uploader_contact_email\": \"%s\",", conf->email);
w += strlen(w);
}
// Only send antenna if provided
if (strlen(conf->antenna) != 0) {
sprintf(w,
"\"uploader_antenna\": \"%s\",",
conf->antenna);
sprintf(w, "\"uploader_antenna\": \"%s\",", conf->antenna);
w += strlen(w);
}
@ -3248,10 +3433,11 @@ void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
}
// Otherweise, in FIXED mode we send the fixed position from config (if specified)
else if (chase == SH_LOC_FIXED) {
if ((!isnan(conf->lat)) && (!isnan(conf->lon))) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%s]",
conf->lat, conf->lon, conf->alt[0] ? conf->alt : "null");
if ((!isnan(sonde.config.rxlat)) && (!isnan(sonde.config.rxlon))) {
if (isnan(sonde.config.rxalt))
sprintf(w, "\"uploader_position\": [%.6f,%.6f,null]", sonde.config.rxlat, sonde.config.rxlon);
else
sprintf(w, "\"uploader_position\": [%.6f,%.6f,%d]", sonde.config.rxlat, sonde.config.rxlon, (int)sonde.config.rxalt);
} else {
sprintf(w, "\"uploader_position\": [null,null,null]");
}
@ -3303,22 +3489,53 @@ const char *dfmSubtypeStrSH[16] = { NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL
};
void sondehub_handle_fimport(WiFiClient *client) {
if (sonde.config.sondehub.fimport[0] != '0') {
if (shImport == 0) {
sondehub_send_fimport(&shclient);
} else if (shImport == 1) {
int res = ShFreqImport::shImportHandleReply(&shclient);
void sondehub_reply_handler(WiFiClient *client) {
// sondehub handler for tasks to be done even if no data is to be sent:
// process response messages from sondehub
// request frequency list (if active)
#define MSG_SIZE 550
char rs_msg[MSG_SIZE];
if(shImport==1) { // we are waiting for a reply to a sondehub frequency import request
// while we are waiting, we do nothing else with sondehub...
int res = ShFreqImport::shImportHandleReply(&shclient);
Serial.printf("ret: %d\n", res);
// res==0 means more data is expected, res==1 means complete reply received (or error)
if (res == 1) {
shImport = 2; // finished
shImportInterval = sonde.config.sondehub.fiinterval * 60;
}
} else if (shImport == 2) {
// waiting for next activation...
}
else {
// any reply here belongs to normal telemetry upload, lets just print it.
// and wait for a valid HTTP response
while(client->available() > 0) {
// data is available from remote server, process it...
int cnt = client->readBytesUntil('\n', rs_msg, MSG_SIZE - 1);
rs_msg[cnt] = 0;
Serial.println(rs_msg);
// If something that looks like a valid HTTP response is received, we are ready to send the next data item
if (shState == SH_CONN_WAITACK && cnt > 11 && strncmp(rs_msg, "HTTP/1", 6) == 0) {
shState = SH_CONN_IDLE;
}
}
}
// send import requests if needed
if (sonde.config.sondehub.fiactive) {
if (shImport == 2) {
Serial.printf("next sondehub frequncy import in %d seconds\n", shImportInterval);
shImportInterval --;
if (shImportInterval <= 0) {
shImport = 0;
}
}
else if (shImport == 0) {
if(shState == SH_CONN_APPENDING || shState == SH_CONN_WAITACK)
Serial.printf("Time to request next sondehub import.... but still busy with upload request");
else
sondehub_send_fimport(&shclient);
}
}
}
@ -3330,22 +3547,17 @@ void sondehub_send_fimport(WiFiClient * client) {
return;
}
// It's time to run, so check prerequisites
float lat = sonde.config.sondehub.lat, lon = sonde.config.sondehub.lon;
float lat = sonde.config.rxlat, lon = sonde.config.rxlon;
if (gpsPos.valid) {
lat = gpsPos.lat;
lon = gpsPos.lon;
}
char *ptr = strchr(sonde.config.sondehub.fimport, '/');
shImportInterval = atoi(sonde.config.sondehub.fimport) * 60;
int maxdist = 200;
int maxage = 60;
if (ptr) {
maxdist = atoi(ptr + 1);
ptr = strchr(ptr + 1, '/');
if (ptr) maxage = atoi(ptr + 1);
}
if ( !isnan(lat) && !isnan(lon) && maxdist > 0 && maxage > 0 && shImportInterval > 0 ) {
int maxdist = sonde.config.sondehub.fimaxdist; // km
int maxage = sonde.config.sondehub.fimaxage * 60; // fimaxage is hours, shImportSendRequest uses minutes
int fiinterval = sonde.config.sondehub.fiinterval;
Serial.printf("shimp : %f %f %d %d %d\n", lat, lon, maxdist, maxage, shImportInterval);
if ( !isnan(lat) && !isnan(lon) && maxdist > 0 && maxage > 0 && fiinterval > 0 ) {
int res = ShFreqImport::shImportSendRequest(&shclient, lat, lon, maxdist, maxage);
if (res == 0) shImport = 1; // Request OK: wait for response
else shImport = 2; // Request failed: wait interval, then retry
@ -3361,7 +3573,6 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
// max age of data in JSON request (in seconds)
#define SONDEHUB_MAXAGE 15
#define MSG_SIZE 550
char rs_msg[MSG_SIZE];
char *w;
struct tm ts;
@ -3381,19 +3592,6 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
if (SH_LOC_AUTO_IS_CHASE) chase = SH_LOC_CHASE; else chase = SH_LOC_FIXED;
}
// TODO: This should better be called not in sondehub_send_data, but somewhere where it is called even if no new data is decoded
// shImport==1: software is waiting for a reply to freq info requst, so reading data is handled elsewhere
while (shImport != 1 && client->available() > 0) {
// data is available from remote server, process it...
int cnt = client->readBytesUntil('\n', rs_msg, MSG_SIZE - 1);
rs_msg[cnt] = 0;
Serial.println(rs_msg);
// If something that looks like a valid HTTP response is received, we are ready to send the next data item
if (shState == SH_CONN_WAITACK && cnt > 11 && strncmp(rs_msg, "HTTP/1", 6) == 0) {
shState = SH_CONN_IDLE;
sondehub_send_fimport(client);
}
}
struct tm timeinfo;
time_t now;
@ -3486,32 +3684,42 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
if (t) sprintf(w, "\"subtype\": \"%s\",", t);
else sprintf(w, "\"subtype\": \"DFMx%X\",", s->subtype); // Unknown subtype
w += strlen(w);
} else if ( s->type == STYPE_RS41 ) {
char buf[11];
if(RS41::getSubtype(buf, 11, s)==0) {
sprintf(w, "\"subtype\": \"%s\",", buf);
w += strlen(w);
}
}
// Only send temp & humidity if provided
if (((int)s->temperature != 0) && ((int)s->relativeHumidity != 0)) {
sprintf(w,
"\"temp\": %.3f,"
"\"humidity\": %.3f,",
float(s->temperature), float(s->relativeHumidity)
);
// Only send temp if provided
if ((int)s->temperature != 0) {
sprintf(w, "\"temp\": %.3f,", float(s->temperature));
w += strlen(w);
}
// Only send humidity if provided
if ((int)s->relativeHumidity != 0) {
sprintf(w, "\"humidity\": %.3f,", float(s->relativeHumidity));
w += strlen(w);
}
// Only send burst timer if RS41 and not 0
if ((realtype == STYPE_RS41) && ((int)s->burstKT != 0)) {
sprintf(w, "\"burst_timer\": %d,", (int)s->burstKT);
w += strlen(w);
}
// Only send antenna if provided
if (strlen(conf->antenna) != 0) {
sprintf(w,
"\"uploader_antenna\": \"%s\",",
conf->antenna);
sprintf(w, "\"uploader_antenna\": \"%s\",", conf->antenna);
w += strlen(w);
}
// We send GPS position: (a) in CHASE mode, (b) in AUTO mode if no fixed location has been specified in config
if (chase == SH_LOC_CHASE) {
if (gpsPos.valid && gpsPos.lat != 0 && gpsPos.lon != 0) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%d]",
gpsPos.lat, gpsPos.lon, gpsPos.alt);
sprintf(w, "\"uploader_position\": [%.6f,%.6f,%d]", gpsPos.lat, gpsPos.lon, gpsPos.alt);
} else {
sprintf(w, "\"uploader_position\": [null,null,null]");
}
@ -3519,10 +3727,11 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
}
// Otherweise, in FIXED mode we send the fixed position from config (if specified)
else if (chase == SH_LOC_FIXED) {
if ((!isnan(conf->lat)) && (!isnan(conf->lon))) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%s]",
conf->lat, conf->lon, conf->alt[0] ? conf->alt : "null");
if ((!isnan(sonde.config.rxlat)) && (!isnan(sonde.config.rxlon))) {
if (isnan(sonde.config.rxalt))
sprintf(w, "\"uploader_position\": [%.6f,%.6f,null]", sonde.config.rxlat, sonde.config.rxlon);
else
sprintf(w, "\"uploader_position\": [%.6f,%.6f,%d]", sonde.config.rxlat, sonde.config.rxlon, (int)sonde.config.rxalt);
} else {
sprintf(w, "\"uploader_position\": [null,null,null]");
}
@ -3575,8 +3784,8 @@ void sondehub_send_header(WiFiClient * client, SondeInfo * s, struct st_sondehub
"Host: ");
Serial.println(conf->host);
Serial.print("accept: text/plain\r\n"
"Content-Type: application/json\r\n"
"Transfer-Encoding: chunked\r\n");
"Content-Type: application/json\r\n"
"Transfer-Encoding: chunked\r\n");
client->print("PUT /sondes/telemetry HTTP/1.1\r\n"
"Host: ");

166
RX_FSK/data/cfg.js 100644
Wyświetl plik

@ -0,0 +1,166 @@
var cfgs = [
[ "", "General configuration" ],
[ "wifi", "Wifi mode (0=off, 1=client, 2=AP, 3=client or AP autoselect on startup)" ],
[ "mdnsname", "Network mDNS name"],
[ "ephftp", "FTP server for ephemeris data (RS92 decoder)"],
[ "debug", "Debug mode (0/1)" ],
[ "maxsonde", "Maxumum number of QRG entries (must be &leg;50)" ],
[ "rxlat", "Receiver fixed latitude"],
[ "rxlon", "Receiver fixed longitude"],
[ "rxalt", "Receiver fixed altitude"],
[ "", "OLED/TFT display configuration" ],
[ "screenfile", "Screen config (0=automatic; 1-5=predefined; other=custom)" ],
[ "display", "Display screens (scan, default, ...)" ],
[ "norx_timeout", "No-RX-timeout in seconds (-1=disabled)"],
[ "tft_orient", "TFT orientation (0/1/2/3), OLED flip: 3"],
[ "", "Spectrum display settings" ],
[ "spectrum", "Show spectrum on start (-1=no, 0=forever, >0=time [sec])" ],
[ "startfreq", "Start frequency (MHz, default 400)" ],
[ "channelbw", "Bandwidth (kHz)" ],
[ "marker", "Spectrum MHz marker" ], // maybe remove, assume always ==1?
[ "noisefloor", "Spectrum noisefloor" ],
[ "", "Receiver configuration" ],
[ "freqofs", "RX frequency offset (Hz)"],
[ "rs41.agcbw", "RS41 AGC bandwidth"],
[ "rs41.rxbw", "RS41 RX bandwidth"],
[ "rs92.rxbw", "RS92 RX (and AGC) bandwidth"],
[ "rs92.alt2d", "RS92 2D fix default altitude"],
[ "dfm.agcbw", "DFM AGC bandwidth"],
[ "dfm.rxbw", "DFM RX bandwidth"],
[ "m10m20.agcbw", "M10/M20 AGC bandwidth"],
[ "m10m20.rxbw", "M10/M20 RX bandwidth"],
[ "mp3h.agcbw", "MP3H AGC bandwidth"],
[ "mp3h.rxbw", "MP3H RX bandwidth"],
[ "", "KISS TNC/AXUDP/AXTCP data feed configuration"],
[ "call", "Call"],
[ "passcode", "Passcode"],
[ "kisstnc.active", "KISS TNC (port 14590) (needs reboot)"],
[ "kisstnc.idformat", "KISS TNC ID format"],
[ "axudp.active", "AXUDP active"],
[ "axudp.host", "AXUDP host"],
[ "axudp.port", "AXUDP port"],
[ "axudp.idformat", "DFM ID format"],
[ "axudp.highrate", "Rate limit"],
[ "tcp.active", "APRS TCP active"],
[ "tcp.host", "ARPS TCP host"],
[ "tcp.port", "APRS TCP port"],
[ "tcp.idformat", "DFM ID format"],
[ "tcp.highrate", "Rate limit"],
[ "", "MQTT data feed configuration"],
[ "mqtt.active", "MQTT active (needs reboot)"],
[ "mqtt.id", "MQTT client ID"],
[ "mqtt.host", "MQTT server hostname"],
[ "mqtt.port", "MQTT port"],
[ "mqtt.username", "MQTT username"],
[ "mqtt.password", "MQTT password"],
[ "mqtt.prefix", "MQTT prefix"],
[ "", "SondeHub settings"],
[ "sondehub.active", "SondeHub reporting (0=disabled, 1=active)"],
[ "sondehub.chase", "SondeHub location reporting (0=off, 1=fixed, 2=chase/GPS, 3=auto)"],
[ "sondehub.host", "SondeHub host (DO NOT CHANGE)"],
[ "sondehub.callsign", "Callsign"],
[ "sondehub.antenna", "Antenna (optional, visisble on SondeHub tracker)"],
[ "sondehub.email", "SondeHub email (optional, only used to contact in case of upload errors)"],
[ "", "SondeHub frequency import" ],
[ "sondehub.fiactive", "SondeHub frequency import active (0=disabled, 1=active)" ],
[ "sondehub.fiinterval", "Import frequency (minutes, &geq; 5)" ],
[ "sondehub.fimaxdist", "Import maximum distance (km, &leq; 500)" ],
[ "sondehub.fimaxage", "Import maximum age (hours, &leq; 24)" ],
[ "", "Hardware configuration (requires reboot)"],
[ "disptype", "Display type (0=OLED/SSD1306, 1=ILI9225, 2=OLED/SH1106, 3=ILI9341, 4=ILI9342)"],
[ "oled_sda", "OLED SDA/TFT SDA"],
[ "oled_scl", "OLED SCL/TFT CLK"],
[ "oled_rst", "OLED RST/TFT RST (needs reboot)"],
[ "tft_rs", "TFT RS"],
[ "tft_cs", "TFT CS"],
[ "tft_spifreq", "TFT SPI speed"],
[ "button_pin", "Button input port"],
[ "button2_pin", "Button 2 input port"],
[ "button2_axp", "Use AXP192 PWR as Button 2"],
[ "touch_thresh", "Touch button threshold<br>(0 for calib mode)"],
[ "power_pout", "Power control port"],
[ "led_pout", "LED output port"],
[ "gps_rxd", "GPS RXD pin (-1 to disable)"],
[ "gps_txd", "GPS TXD pin (not really needed)"],
[ "batt_adc", "Battery measurement pin"],
[ "sx1278_ss", "SX1278 SS"],
[ "sx1278_miso", "SX1278 MISO"],
[ "sx1278_mosi", "SX1278 MOSI"],
[ "sx1278_sck", "SX1278 SCK"],
];
function mkcfg(id, key, label, value) {
var s = "<tr style=\"visibility: collapse;\" class=\"cfgpanel\"><td>" + label + "</td><td><input name=\"" + key + "\" type=\"text\" value=\"" + value + "\"/></td></tr>\n";
return s;
}
function mkcfgbtn(id, key, label, value) {
var touch = "";
var v = value;
if(v != -1 && (v&128)) {
touch = " checked";
v = v & 127;
}
var s = "<tr style=\"visibility:collapse\" class=\"cfgpanel\"><td>" + label + "</td><td><input name=\"" + key + "\" type=\"text\" size=\"3\" value=\"" + v + "\"/>";
s += "<input type=\"checkbox\" name=\"" + key + "#\" "+touch+"> Touch</td></tr>\n";
return s;
}
function mksep(id,label) {
return "<tr class=\"cfgheader\"><th class=\"cfg\" align=\"left\" colspan=\"2\">"+label+"</th></tr>\n";
}
function rowdisp(id,disp) {
var matches = document.querySelectorAll("tr."+id);
matches.forEach(function(e) { if(disp) e.hidden=true; else e.removeAttribute('hidden');});
hid=id; nid="N"+id;
if(!disp) { hid=nid; nid=id; }
document.querySelector("span."+hid).hidden=true;
document.querySelector("span."+nid).removeAttribute('hidden');
}
function configTable() {
// iterate over cfgs
var tab = "<table width=\"100%\"><tr><th>Option</th><th>Value</th></tr>\n";
var id=0;
for(i=0; i<cfgs.length; i++) {
var key = cfgs[i][0];
var lbl = cfgs[i][1];
if(key) {
if(key=="button_pin" || key=="button2_pin") {
tab += mkcfgbtn("s"+id, key, lbl, cf.get(key));
} else if (key=="display") {
tab += mkcfg("s"+id, key, lbl, cf.get(key));
tab += "<tr style=\"visibility:collapse\" class=\"cfgpanel\"><td>"+scr+"</td><td></td></tr>"
} else {
tab += mkcfg("s"+id, key, lbl, cf.get(key));
}
} else {
id++;
tab += mksep("s"+id, lbl);
}
}
tab += "</table>";
var cfgdiv = document.getElementById("cfgtab");
cfgdiv.innerHTML = tab;
// enable collapse / expand of items below a header
var acc = document.getElementsByClassName("cfgheader");
for(i=0; i<acc.length; i++) {
acc[i].firstChild.innerHTML = "[+] " + acc[i].firstChild.innerHTML;
acc[i].addEventListener("click", function() {
achar = "[+]";
if(this.classList.toggle("active")) achar = "[\u2212]";
this.firstChild.innerHTML = achar + this.firstChild.innerHTML.substring(3);
var panel = this;
console.log(panel);
while( panel = panel.nextElementSibling) {
console.log(panel);
if ( panel.className!="cfgpanel") { break; }
if(panel.style.visibility==="collapse") {
panel.style.visibility="visible";
} else {
console.log("none");
panel.style.visibility="collapse";
}
}
});
}
acc[0].click();
}

Wyświetl plik

@ -24,6 +24,9 @@
#oled_rst=16
#tft_rs=2
#tft_cs=0
rxlat=
rxlon=
rxalt=
tft_orient=1
#tft_spifreq=40000000
#gps_rxd=-1
@ -124,12 +127,15 @@ sondehub.active=0
sondehub.chase=3
sondehub.host=api.v2.sondehub.org
sondehub.callsign=CHANGEME_RDZTTGO
sondehub.lat=
sondehub.lon=
sondehub.alt=
sondehub.antenna=
sondehub.email=
sondehub.fimport=0/100/60
#-------------------------------#
# Sondehub freq import settings
#-------------------------------#
shfimp.active=0
shfimp.interval=60
shfimp.maxdist=150
shfimp.maxage=6
#-------------------------------#
# EOF
#-------------------------------#

Wyświetl plik

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script>var mapcenter=[%MAPCENTER%];</script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.marker.slideto@0.2.0/Leaflet.Marker.SlideTo.js"></script>

Wyświetl plik

@ -40,7 +40,8 @@ $(document).ready(function(){
}
};
map.setView([51.163361,10.447683], 5); // Mitte DE
if(mapcenter) map.setView(mapcenter, 5);
else map.setView([51.163361,10.447683], 5); // Mitte DE
var reddot = '<span class="ldot rbg"></span>';
var yellowdot = '<span class="ldot ybg"></span>';
@ -69,11 +70,12 @@ $('.leaflet-footer').append(footer);
var statbar = '';
headtxt = function(data,stat) {
console.log(data);
var staticon = (stat == '1')?greendot:yellowdot;
statbar = staticon + statbar;
if ((statbar.length) > 10*greendot.length) { statbar = statbar.substring(0,10*greendot.length); }
if (data.lat == '0.000000') { return false; }
if (data.id) {
if (data.res == 0) {
$('#sonde_id').html(data.id);
$('#sonde_alt').html(data.alt);
$('#sonde_climb').html(data.climb);
@ -154,9 +156,10 @@ headtxt = function(data,stat) {
draw = function(data) {
var stat;
console.log(data);
if (data.id) {
if ((data.lat != '0.000000' && data.lon != '0.000000') && (JSON.stringify(data) != JSON.stringify(last_data)) ) {
// data.res: 0: ok 1: no rx (timeout), 2: crc err, >2 some other error
if ((data.lat != '0.000000' && data.lon != '0.000000') && (data.res==0)) { //JSON.stringify(data) != JSON.stringify(last_data)) ) {
var location = [data.lat,data.lon,data.alt];
if (!marker) {
map.setView(location, 14);
@ -230,8 +233,10 @@ headtxt = function(data,stat) {
get_data = function() {
$('#status').html(reddot);
console.log("get_data called");
$.ajax({url: 'live.json', success: (function( data ) {
if (typeof data != "object") { data = $.parseJSON(data); }
console.log(data);
if (data.sonde) {
draw(data.sonde);
} else {
@ -321,7 +326,7 @@ headtxt = function(data,stat) {
var datetime = m.getUTCFullYear() + "-" + az(m.getUTCMonth()+1) + "-" + az(m.getUTCDate()) + "T" +
az(m.getUTCHours()) + ":" + az(m.getUTCMinutes()) + ":" + az(m.getUTCSeconds()) + "Z";
var url = 'https://predict.cusf.co.uk/api/v1/';
url += '?launch_latitude='+data.lat + '&launch_longitude='+fix_lon(data.lon);
url += '?launch_latitude='+data.lat + '&launch_longitude='+tawhiri_lon(data.lon);
url += '&launch_altitude='+data.alt + '&launch_datetime='+datetime;
url += '&ascent_rate='+ascent + '&burst_altitude=' + burst + '&descent_rate='+descent;
@ -333,11 +338,11 @@ headtxt = function(data,stat) {
draw_predict = function(prediction,data) {
var ascending = prediction.prediction[0].trajectory;
var highest = ascending[ascending.length-1];
var highest_location = [highest.latitude,fix_lon(highest.longitude)];
var highest_location = [highest.latitude,sanitize_lon(highest.longitude)];
var descending = prediction.prediction[1].trajectory;
var landing = descending[descending.length-1];
var landing_location = [landing.latitude,fix_lon(landing.longitude)];
var landing_location = [landing.latitude,sanitize_lon(landing.longitude)];
if (!marker_landing) {
marker_landing = L.marker(landing_location,{icon: icon_landing}).addTo(map)
@ -353,7 +358,7 @@ headtxt = function(data,stat) {
dots_predict=[];
if (data.climb > 0) {
ascending.forEach(p => dots_predict.push([p.latitude,fix_lon(p.longitude)]));
ascending.forEach(p => dots_predict.push([p.latitude,sanitize_lon(p.longitude)]));
if (!marker_burst) {
marker_burst = L.marker(highest_location,{icon:icon_burst}).addTo(map).bindPopup(poptxt('burst',highest),{closeOnClick:false, autoPan:false});
@ -365,7 +370,7 @@ headtxt = function(data,stat) {
}
}
descending.forEach(p => dots_predict.push([p.latitude,fix_lon(p.longitude)]));
descending.forEach(p => dots_predict.push([p.latitude,sanitize_lon(p.longitude)]));
line_predict.setLatLngs(dots_predict);
if (data.climb > 0) {
@ -378,16 +383,19 @@ headtxt = function(data,stat) {
clearTimeout(predictor);
predictor = setTimeout(function() {get_predict(last_data);}, predictor_time*1000);
};
fix_lon = function(lon) {
sanitize_lon = function(lon) {
if (lon > 180) { return lon - 360; }
return lon;
}
tawhiri_lon = function(lon) {
if (lon < 0) { return lon + 360; }
return lon;
};
}
poptxt = function(t,i) {
var lat_input = (i.id)?i.lat:i.latitude;
var lon_input = (i.id)?i.lon:i.longitude;
var lon_input = sanitize_lon((i.id)?i.lon:i.longitude);
var lat = Math.round(lat_input * 1000000) / 1000000;
var lon = Math.round(lon_input * 1000000) / 1000000;

Wyświetl plik

@ -4,6 +4,16 @@ body, html {
font-family: Arial;
}
.active, .cfgheader:hover {
background-color: #ccc;
}
.cfgpanel {
}
th.cfg {
padding:5pt
}
.hamburger {
position: relative;
display: inline-block;

Wyświetl plik

@ -195,9 +195,6 @@ static void Gencrctab(void)
int RS41::setup(float frequency)
{
#if RS41_DEBUG
Serial.println("Setup sx1278 for RS41 sonde");
#endif
if(!initialized) {
Gencrctab();
initrsc();
@ -216,11 +213,6 @@ int RS41::setup(float frequency)
RS41_DBG(Serial.println("Setting bitrate 4800bit/s FAILED"));
return 1;
}
#if RS41_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs41.agcbw)!=0) {
RS41_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs41.agcbw));
@ -255,21 +247,11 @@ int RS41::setup(float frequency)
RS41_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
#if RS41_DEBUG
Serial.print("RS41: setting RX frequency to ");
Serial.println(frequency);
#endif
int retval = sx1278.setFrequency(frequency);
dpos = 0;
#if RS41_DEBUG
RS41_DBG(Serial.println("Setting SX1278 config for RS41 finished\n"); Serial.println());
#endif
sx1278.clearIRQFlags();
// the following is already done in receivePacketTimeout()
// sx1278.setPayloadLength(RS41MAXLEN-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
// sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return retval;
}
@ -517,6 +499,7 @@ void ProcessSubframe( byte *subframeBytes, int subframeNumber ) {
}
memcpy( s->rawData+16*subframeNumber, subframeBytes, 16);
s->valid |= (1ULL << subframeNumber);
Serial.printf("subframe %d; valid: %x%032x\n", subframeNumber, (uint32_t)(s->valid>>32), (uint32_t)s->valid);
// subframeReceived[subframeNumber] = true; // mark this row of the total subframe as complete
#if 0
@ -846,11 +829,24 @@ static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
int RS41::receive() {
sx1278.setPayloadLength(RS41MAXLEN-8);
int e = sx1278.receivePacketTimeout(1000, data+8);
#if 1
if(e) { /*Serial.println("TIMEOUT");*/ return RX_TIMEOUT; }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = reverse(data[i]); }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
return decode41(data, RS41MAXLEN);
#else
// FAKE testing data
SondeInfo *si = sonde.si();
si->lat = 48;
si->lon = -100;
si->alt = 30000;
si->vs = 3.4;
si->validPos = 0x7f;
si->validID = 1;
strcpy(si->id, "A1234");
return 0;
#endif
}
int RS41::waitRXcomplete() {
@ -859,4 +855,17 @@ int RS41::waitRXcomplete() {
return 0;
}
// copy variant string to buf (max buflen chars; buflen should be 11
// return 0 if subtype is available, -1 if not
int RS41::getSubtype(char *buf, int buflen, SondeInfo *si) {
struct subframeBuffer *sf = (struct subframeBuffer *)si->extra;
if(!sf) return -1;
if( (sf->valid & (3<<21)) != (3<<21) ) return -1; // or 1 instead of 3 for the first 8 chars only, as in autorx?
if(buflen>11) buflen=11; // then buflen should be capped at 9 (8+trailing \0)
strncpy(buf, sf->value.names.variant, buflen);
buf[buflen-1]=0;
if(*buf==0) return -1;
return 0;
}
RS41 rs41 = RS41();

Wyświetl plik

@ -15,6 +15,7 @@
#ifndef inttypes_h
#include <inttypes.h>
#endif
#include "Sonde.h"
/* Main class */
class RS41
@ -61,6 +62,8 @@ public:
int waitRXcomplete();
//int receiveFrame();
static int getSubtype(char *buf, int buflen, SondeInfo *si);
int use_ecc = 1;
};

Wyświetl plik

@ -114,10 +114,12 @@ void ShFreqImport::cleanup() {
#define BUFLEN 128
#define VALLEN 20
int ShFreqImport::handleChar(char c) {
Serial.print(c);
switch(importState) {
case START:
// wait for initial '{'
if(c=='{') {
Serial.println("{ found");
lat = NAN; lon = NAN; freq = NAN; *type = 0;
importState++;
}
@ -125,7 +127,11 @@ int ShFreqImport::handleChar(char c) {
case BEFOREID:
// what for first '"' in { "A1234567" : { ... } }; or detect end
if(c=='"') { idpos = 0; importState++; }
if(c=='}') { importState = ENDREACHED; }
if(c=='}') {
importState = ENDREACHED;
cleanup();
return 1;
}
break;
case COPYID:
// copy ID "A1234567" until second '"' is earched
@ -196,6 +202,7 @@ int ShFreqImport::handleChar(char c) {
else if (c=='}') { importState = ENDREACHED; cleanup(); return 1; }
break;
case ENDREACHED:
Serial.println("REPLY: END REACHED");
return 1;
}
return 0;
@ -228,6 +235,7 @@ int ShFreqImport::shImportSendRequest(WiFiClient *client, float lat, float lon,
// return 0 if more data should be read (later), 1 if finished (close connection...)
int ShFreqImport::shImportHandleReply(WiFiClient *client) {
if(!client->connected()) return 1;
while(client->available()) {
int res = handleChar(client->read());
if(res) return res;

Wyświetl plik

@ -80,6 +80,10 @@ void Sonde::defaultConfig() {
sondeList = (SondeInfo *)malloc((MAXSONDE+1)*sizeof(SondeInfo));
memset(sondeList, 0, (MAXSONDE+1)*sizeof(SondeInfo));
for(int i=0; i<(MAXSONDE+1); i++) {
sondeList[i].freq=400;
sondeList[i].type=STYPE_RS41;
}
config.touch_thresh = 70;
config.led_pout = -1;
config.power_pout = -1;
@ -263,6 +267,13 @@ void Sonde::defaultConfig() {
extern struct st_configitems config_list[];
extern const int N_CONFIG;
void Sonde::checkConfig() {
if(config.maxsonde > MAXSONDE) config.maxsonde = MAXSONDE;
if(config.sondehub.fiinterval<5) config.sondehub.fiinterval = 5;
if(config.sondehub.fimaxdist>500) config.sondehub.fimaxdist = 500;
if(config.sondehub.fimaxdist==0) config.sondehub.fimaxdist = 150;
if(config.sondehub.fimaxage==0) config.sondehub.fimaxage = 2;
}
void Sonde::setConfig(const char *cfg) {
while(*cfg==' '||*cfg=='\t') cfg++;
if(*cfg=='#') return;
@ -288,11 +299,14 @@ void Sonde::setConfig(const char *cfg) {
case -3: // integer (boolean on/off swith in web form)
case -2: // integer (ID type)
*(int *)config_list[i].data = atoi(val);
if(config.maxsonde > MAXSONDE) config.maxsonde = MAXSONDE;
break;
case -7: // double
*(double *)config_list[i].data = *val==0 ? NAN : atof(val);
{
double d = atof(val);
if(*val == 0 || d==0) d = NAN;
*(double *)config_list[i].data = d;
break;
}
case -6: // display list
{
int idx = 0;
@ -343,14 +357,17 @@ void Sonde::addSonde(float frequency, SondeType type, int active, char *launchsi
Serial.printf("Adding %f - %d - %d - %s\n", frequency, type, active, launchsite);
// reset all data if type or frequency has changed
if(type != sondeList[nSonde].type || frequency != sondeList[nSonde].freq) {
//TODO: Check for potential race condition with decoders
// do not clear extra while decoder is potentiall still accessing it!
if(sondeList[nSonde].extra) free(sondeList[nSonde].extra);
memset(&sondeList[nSonde], 0, sizeof(SondeInfo));
sondeList[nSonde].type = type;
sondeList[nSonde].typestr[0] = 0;
sondeList[nSonde].freq = frequency;
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
}
sondeList[nSonde].type = type;
sondeList[nSonde].typestr[0] = 0;
sondeList[nSonde].freq = frequency;
sondeList[nSonde].active = active;
strncpy(sondeList[nSonde].launchsite, launchsite, 17);
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++;
}
@ -379,7 +396,7 @@ void Sonde::nextRxSonde() {
if(rxtask.currentSonde>=config.maxsonde) rxtask.currentSonde=0;
}
}
Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
//Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
}
void Sonde::nextRxFreq(int addkhz) {
// last entry is for the variable frequency
@ -409,7 +426,7 @@ void Sonde::setup() {
}
// update receiver config
Serial.print("Sonde.setup() on sonde index ");
Serial.print("Sonde::setup() start on index ");
Serial.println(rxtask.currentSonde);
switch(sondeList[rxtask.currentSonde].type) {
case STYPE_RS41:
@ -433,7 +450,7 @@ void Sonde::setup() {
int freq = (int)sx1278.getFrequency();
int afcbw = (int)sx1278.getAFCBandwidth();
int rxbw = (int)sx1278.getRxBandwidth();
Serial.printf("Sonde.setup(): Freq %d, AFC BW: %d, RX BW: %d\n", freq, afcbw, rxbw);
Serial.printf("Sonde::setup() done: Type %s Freq %f, AFC BW: %d, RX BW: %d\n", sondeTypeStr[sondeList[rxtask.currentSonde].type], 0.000001*freq, afcbw, rxbw);
// reset rxtimer / norxtimer state
sonde.sondeList[sonde.currentSonde].lastState = -1;
@ -472,7 +489,7 @@ void Sonde::receive() {
}
} else { // RX not ok
if(res==RX_ERROR) flashLed(100);
Serial.printf("RX result %d (%s), laststate was %d\n", res, (res<=3)?RXstr[res]:"?", si->lastState);
//Serial.printf("Sonde::receive(): result %d (%s), laststate was %d\n", res, (res<=3)?RXstr[res]:"?", si->lastState);
if(si->lastState != 0) {
si->norxStart = millis();
si->lastState = 0;
@ -509,8 +526,8 @@ void Sonde::receive() {
rxtask.activate = ACT_SONDE(rxtask.currentSonde);
}
}
Serial.printf("Sonde:receive(): result %d (%s), event %02x => action %02x\n", res, (res<=3)?RXstr[res]:"?", event, action);
res = (action<<8) | (res&0xff);
Serial.printf("Sonde:receive(): Event %02x: action %02x, res %02x => %04x\n", event, action, res&0xff, res);
// let waitRXcomplete resume...
rxtask.receiveResult = res;
}
@ -574,22 +591,22 @@ uint8_t Sonde::timeoutEvent(SondeInfo *si) {
now, si->norxStart, disp.layout->timeouts[2], si->lastState);
#endif
if(disp.layout->timeouts[0]>=0 && now - si->viewStart >= disp.layout->timeouts[0]) {
Serial.println("Sonde.timeoutEvent: View");
Serial.println("Sonde::timeoutEvent: View");
return EVT_VIEWTO;
}
if(si->lastState==1 && disp.layout->timeouts[1]>=0 && now - si->rxStart >= disp.layout->timeouts[1]) {
Serial.println("Sonde.timeoutEvent: RX");
Serial.println("Sonde::timeoutEvent: RX");
return EVT_RXTO;
}
if(si->lastState==0 && disp.layout->timeouts[2]>=0 && now - si->norxStart >= disp.layout->timeouts[2]) {
Serial.println("Sonde.timeoutEvent: NORX");
Serial.println("Sonde::timeoutEvent: NORX");
return EVT_NORXTO;
}
return 0;
}
uint8_t Sonde::updateState(uint8_t event) {
Serial.printf("Sonde::updateState for event %d\n", event);
//Serial.printf("Sonde::updateState for event %02x\n", event);
// No change
if(event==ACT_NONE) return 0xFF;

Wyświetl plik

@ -191,12 +191,12 @@ struct st_sondehub {
int chase;
char host[64];
char callsign[64];
double lat;
double lon;
char alt[20];
char antenna[64];
char email[64];
char fimport[20];
int fiactive;
int fiinterval;
int fimaxdist;
int fimaxage;
};
// to be extended
@ -228,6 +228,9 @@ typedef struct st_rdzconfig {
int sx1278_sck; // SPI SCK for sx1278
// software configuration
int debug; // show port and config options after reboot
double rxlat;
double rxlon;
double rxalt;
int wifi; // connect to known WLAN 0=skip
int screenfile;
int8_t display[30]; // list of display mode (0:scanner, 1:default, 2,... additional modes)
@ -261,7 +264,7 @@ typedef struct st_rdzconfig {
struct st_configitems {
const char *name;
const char *label;
// const char *label; => now handled in JS
int type; // 0: numeric; i>0 string of length i; -1: separator; -2: type selector
void *data;
};
@ -271,7 +274,7 @@ extern struct st_configitems config_list[];
extern const int N_CONFIG;
#define MAXSONDE 99
#define MAXSONDE 50
extern int fingerprintValue[];
extern const char *fingerprintText[];
@ -292,6 +295,7 @@ public:
Sonde();
void defaultConfig();
void checkConfig();
void setConfig(const char *str);
void clearSonde();

Wyświetl plik

@ -1,4 +1,4 @@
const char *version_name = "rdzTTGOsonde";
const char *version_id = "devel20210917";
const char *version_id = "devel20210919";
const int SPIFFS_MAJOR=2;
const int SPIFFS_MINOR=16;