diff --git a/RX_FSK/RX_FSK.ino b/RX_FSK/RX_FSK.ino index d3c87db..08a421b 100644 --- a/RX_FSK/RX_FSK.ino +++ b/RX_FSK/RX_FSK.ino @@ -13,6 +13,7 @@ #include #include #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, "
NOTE: SondeHub uploading is not enabled, detected sonde will not be visable on map
"); - if ((*s->ser == 0) && ( !isnan(sonde.config.sondehub.lat))) { - sprintf(ptr + strlen(ptr), "", sonde.config.sondehub.lat, sonde.config.sondehub.lon); + if ((*s->ser == 0) && ( !isnan(sonde.config.rxlat))) { + sprintf(ptr + strlen(ptr), "", sonde.config.rxlat, sonde.config.rxlon); } else { sprintf(ptr + strlen(ptr), "", s-> ser); } } else { - if ((*s->ser == 0) && (!isnan(sonde.config.sondehub.lat))) { - sprintf(ptr, "", sonde.config.sondehub.lat, sonde.config.sondehub.lon); + if ((*s->ser == 0) && (!isnan(sonde.config.rxlat))) { + sprintf(ptr, "", sonde.config.rxlat, sonde.config.rxlon); } else { sprintf(ptr, "", 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), "%s\n", label, idx, field); @@ -733,11 +849,55 @@ void addConfigInt8List(char *ptr, int idx, const char *label, int8_t *list) { } strcat(ptr, "\"/>\n"); } +#endif const char *createConfigForm() { char *ptr = message; strcpy(ptr, HTMLHEAD); strcat(ptr, ""); HTMLBODY(ptr, "config.html"); + strcat(ptr, "
"); + strcat(ptr, ""); + strcat(ptr, ""); +#if 0 strcat(ptr, ""); 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, "
OptionValue
"); //
"); +#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: "); diff --git a/RX_FSK/data/cfg.js b/RX_FSK/data/cfg.js new file mode 100644 index 0000000..5dc7fc3 --- /dev/null +++ b/RX_FSK/data/cfg.js @@ -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 ⋚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, ≥ 5)" ], +[ "sondehub.fimaxdist", "Import maximum distance (km, ≤ 500)" ], +[ "sondehub.fimaxage", "Import maximum age (hours, ≤ 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
(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 = "" + label + "\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 = "" + label + ""; + s += " Touch\n"; + return s; +} + +function mksep(id,label) { + return ""+label+"\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 = "\n"; + var id=0; + for(i=0; i" + } else { + tab += mkcfg("s"+id, key, lbl, cf.get(key)); + } + } else { + id++; + tab += mksep("s"+id, lbl); + } + } + tab += "
OptionValue
"+scr+"
"; + 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 + diff --git a/RX_FSK/data/livemap.js b/RX_FSK/data/livemap.js index 0ff62da..5dec83f 100644 --- a/RX_FSK/data/livemap.js +++ b/RX_FSK/data/livemap.js @@ -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 = ''; var yellowdot = ''; @@ -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; diff --git a/RX_FSK/data/style.css b/RX_FSK/data/style.css index 04e254f..4454375 100755 --- a/RX_FSK/data/style.css +++ b/RX_FSK/data/style.css @@ -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; diff --git a/RX_FSK/src/RS41.cpp b/RX_FSK/src/RS41.cpp index a73bb18..aa66d46 100644 --- a/RX_FSK/src/RS41.cpp +++ b/RX_FSK/src/RS41.cpp @@ -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; ilat = 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(); diff --git a/RX_FSK/src/RS41.h b/RX_FSK/src/RS41.h index 8284573..e0b8c46 100644 --- a/RX_FSK/src/RS41.h +++ b/RX_FSK/src/RS41.h @@ -15,6 +15,7 @@ #ifndef inttypes_h #include #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; }; diff --git a/RX_FSK/src/ShFreqImport.cpp b/RX_FSK/src/ShFreqImport.cpp index 23a372f..87b12b0 100644 --- a/RX_FSK/src/ShFreqImport.cpp +++ b/RX_FSK/src/ShFreqImport.cpp @@ -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; diff --git a/RX_FSK/src/Sonde.cpp b/RX_FSK/src/Sonde.cpp index a527e7a..56e0cfe 100644 --- a/RX_FSK/src/Sonde.cpp +++ b/RX_FSK/src/Sonde.cpp @@ -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; diff --git a/RX_FSK/src/Sonde.h b/RX_FSK/src/Sonde.h index e42b70e..d16dbd0 100644 --- a/RX_FSK/src/Sonde.h +++ b/RX_FSK/src/Sonde.h @@ -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(); diff --git a/RX_FSK/version.h b/RX_FSK/version.h index 68ea10f..cee9f1d 100644 --- a/RX_FSK/version.h +++ b/RX_FSK/version.h @@ -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;