kopia lustrzana https://github.com/dl9rdz/rdz_ttgo_sonde
Merge branch 'dl9rdz:devel' into devel
commit
e75e30768c
|
@ -63,6 +63,8 @@ commit_website_files() {
|
||||||
git add ${BRANCH}/${VERSION}-changelog.txt
|
git add ${BRANCH}/${VERSION}-changelog.txt
|
||||||
echo "<html><body><p>${VERSION}</p></body></html>" > ${BRANCH}/update-info.html
|
echo "<html><body><p>${VERSION}</p></body></html>" > ${BRANCH}/update-info.html
|
||||||
git add ${BRANCH}/update-info.html
|
git add ${BRANCH}/update-info.html
|
||||||
|
${MYPATH}/scripts/makefsupdate.py ${MYPATH}/RX_FSK/data/ > ${BRANCH}/update.fs.bin
|
||||||
|
git add ${BRANCH}/update.fs.bin
|
||||||
git commit --message "Travis build: $TRAVIS_BUILD_NUMBER"
|
git commit --message "Travis build: $TRAVIS_BUILD_NUMBER"
|
||||||
}
|
}
|
||||||
upload_files() {
|
upload_files() {
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "src/geteph.h"
|
#include "src/geteph.h"
|
||||||
#include "src/rs92gps.h"
|
#include "src/rs92gps.h"
|
||||||
#include "src/aprs.h"
|
#include "src/aprs.h"
|
||||||
|
#include "src/ShFreqImport.h"
|
||||||
|
|
||||||
#if FEATURE_MQTT
|
#if FEATURE_MQTT
|
||||||
#include "src/mqtt.h"
|
#include "src/mqtt.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -39,11 +41,13 @@ AXP20X_Class axp;
|
||||||
SemaphoreHandle_t axpSemaphore;
|
SemaphoreHandle_t axpSemaphore;
|
||||||
bool pmu_irq = false;
|
bool pmu_irq = false;
|
||||||
|
|
||||||
String updateHost = "rdzsonde.mooo.com";
|
const char *updateHost = "rdzsonde.mooo.com";
|
||||||
int updatePort = 80;
|
int updatePort = 80;
|
||||||
String updateBinM = "/master/update.ino.bin";
|
|
||||||
String updateBinD = "/devel/update.ino.bin";
|
const char *updatePrefixM = "/master/";
|
||||||
String *updateBin = &updateBinM;
|
const char *updatePrefixD = "/devel/";
|
||||||
|
const char *updatePrefix = updatePrefixM;
|
||||||
|
|
||||||
|
|
||||||
#define LOCALUDPPORT 9002
|
#define LOCALUDPPORT 9002
|
||||||
//Get real UTC time from NTP server
|
//Get real UTC time from NTP server
|
||||||
|
@ -58,6 +62,7 @@ WiFiClient client;
|
||||||
#define SONDEHUB_STATION_UPDATE_TIME (60*60*1000) // 60 min
|
#define SONDEHUB_STATION_UPDATE_TIME (60*60*1000) // 60 min
|
||||||
#define SONDEHUB_MOBILE_STATION_UPDATE_TIME (30*1000) // 30 sec
|
#define SONDEHUB_MOBILE_STATION_UPDATE_TIME (30*1000) // 30 sec
|
||||||
WiFiClient shclient; // Sondehub v2
|
WiFiClient shclient; // Sondehub v2
|
||||||
|
char shImportInterval = 0, shImport = 0;
|
||||||
unsigned long time_last_update = 0;
|
unsigned long time_last_update = 0;
|
||||||
/* SH_LOC_OFF: never send position information to SondeHub
|
/* SH_LOC_OFF: never send position information to SondeHub
|
||||||
SH_LOC_FIXED: send fixed position (if specified in config) to sondehub
|
SH_LOC_FIXED: send fixed position (if specified in config) to sondehub
|
||||||
|
@ -146,6 +151,11 @@ String processor(const String& var) {
|
||||||
if (var == "VERSION_ID") {
|
if (var == "VERSION_ID") {
|
||||||
return String(version_id);
|
return String(version_id);
|
||||||
}
|
}
|
||||||
|
if (var == "FULLNAMEID") {
|
||||||
|
char tmp[128];
|
||||||
|
snprintf(tmp, 128, "%s-%c%d", version_id, SPIFFS_MAJOR+'A'-1, SPIFFS_MINOR);
|
||||||
|
return String(tmp);
|
||||||
|
}
|
||||||
if (var == "AUTODETECT_INFO") {
|
if (var == "AUTODETECT_INFO") {
|
||||||
char tmpstr[128];
|
char tmpstr[128];
|
||||||
const char *fpstr;
|
const char *fpstr;
|
||||||
|
@ -258,9 +268,18 @@ void HTMLSAVEBUTTON(char *ptr) {
|
||||||
const char *createQRGForm() {
|
const char *createQRGForm() {
|
||||||
char *ptr = message;
|
char *ptr = message;
|
||||||
strcpy(ptr, HTMLHEAD);
|
strcpy(ptr, HTMLHEAD);
|
||||||
strcat(ptr, "<script src=\"rdz.js\"/> <script> window.onload = prep; </script></head>");
|
strcat(ptr, "<script src=\"rdz.js\"></script></head>");
|
||||||
HTMLBODY(ptr, "qrg.html");
|
HTMLBODY(ptr, "qrg.html");
|
||||||
//strcat(ptr, "<body><form class=\"wrapper\" action=\"qrg.html\" method=\"post\"><div class=\"content\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>");
|
//strcat(ptr, "<body><form class=\"wrapper\" action=\"qrg.html\" method=\"post\"><div class=\"content\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>");
|
||||||
|
strcat(ptr, "<script>\nvar qrgs = [];\n");
|
||||||
|
for (int i = 0; i < sonde.config.maxsonde; i++) {
|
||||||
|
SondeInfo *si = &sonde.sondeList[i];
|
||||||
|
sprintf(ptr + strlen(ptr), "qrgs.push([%d, \"%.3f\", \"%s\", \"%c\"]);\n", si->active, si->freq, si->launchsite, sondeTypeChar[si->type] );
|
||||||
|
}
|
||||||
|
strcat(ptr, "</script>\n");
|
||||||
|
strcat(ptr, "<div id=\"divTable\"></div>");
|
||||||
|
strcat(ptr, "<script> qrgTable() </script>\n");
|
||||||
|
#if 0
|
||||||
strcat(ptr, "<table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>");
|
strcat(ptr, "<table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>");
|
||||||
for (int i = 0; i < sonde.config.maxsonde; i++) {
|
for (int i = 0; i < sonde.config.maxsonde; i++) {
|
||||||
//String s = sondeTypeSelect(i >= sonde.nSonde ? 2 : sonde.sondeList[i].type);
|
//String s = sondeTypeSelect(i >= sonde.nSonde ? 2 : sonde.sondeList[i].type);
|
||||||
|
@ -278,6 +297,7 @@ const char *createQRGForm() {
|
||||||
//i + 1, s.c_str());
|
//i + 1, s.c_str());
|
||||||
}
|
}
|
||||||
strcat(ptr, "</table>");
|
strcat(ptr, "</table>");
|
||||||
|
#endif
|
||||||
//</div><div class=\"footer\"><input type=\"submit\" class=\"update\" value=\"Update\"/>");
|
//</div><div class=\"footer\"><input type=\"submit\" class=\"update\" value=\"Update\"/>");
|
||||||
HTMLSAVEBUTTON(ptr);
|
HTMLSAVEBUTTON(ptr);
|
||||||
HTMLBODYEND(ptr);
|
HTMLBODYEND(ptr);
|
||||||
|
@ -490,8 +510,8 @@ const char *createStatusForm() {
|
||||||
strcpy(ptr, HTMLHEAD);
|
strcpy(ptr, HTMLHEAD);
|
||||||
strcat(ptr, "<meta http-equiv=\"refresh\" content=\"5\"></head><body>");
|
strcat(ptr, "<meta http-equiv=\"refresh\" content=\"5\"></head><body>");
|
||||||
|
|
||||||
for (int i = 0; i < sonde.nSonde; i++) {
|
for (int i = 0; i < sonde.config.maxsonde; i++) {
|
||||||
int snum = (i + sonde.currentSonde) % sonde.nSonde;
|
int snum = (i + sonde.currentSonde) % sonde.config.maxsonde;
|
||||||
if (sonde.sondeList[snum].active) {
|
if (sonde.sondeList[snum].active) {
|
||||||
addSondeStatus(ptr, snum);
|
addSondeStatus(ptr, snum);
|
||||||
}
|
}
|
||||||
|
@ -547,6 +567,8 @@ void setupConfigData() {
|
||||||
String line = readLine(file); //file.readStringUntil('\n');
|
String line = readLine(file); //file.readStringUntil('\n');
|
||||||
sonde.setConfig(line.c_str());
|
sonde.setConfig(line.c_str());
|
||||||
}
|
}
|
||||||
|
int shII = atoi(sonde.config.sondehub.fimport);
|
||||||
|
if(shImportInterval > shII) shImportInterval = shII;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -649,6 +671,7 @@ struct st_configitems config_list[] = {
|
||||||
{"sondehub.alt", "Altitude (optional, visible on SondeHub tracker)", 19, &sonde.config.sondehub.alt},
|
{"sondehub.alt", "Altitude (optional, visible on SondeHub tracker)", 19, &sonde.config.sondehub.alt},
|
||||||
{"sondehub.antenna", "Antenna (optional, visisble on SondeHub tracker)", 63, &sonde.config.sondehub.antenna},
|
{"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.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
|
#endif
|
||||||
};
|
};
|
||||||
const int N_CONFIG = (sizeof(config_list) / sizeof(struct st_configitems));
|
const int N_CONFIG = (sizeof(config_list) / sizeof(struct st_configitems));
|
||||||
|
@ -1055,14 +1078,14 @@ const char *handleUpdatePost(AsyncWebServerRequest *request) {
|
||||||
Serial.println(param.c_str());
|
Serial.println(param.c_str());
|
||||||
if (param.equals("devel")) {
|
if (param.equals("devel")) {
|
||||||
Serial.println("equals devel");
|
Serial.println("equals devel");
|
||||||
updateBin = &updateBinD;
|
updatePrefix = updatePrefixD;
|
||||||
}
|
}
|
||||||
else if (param.equals("master")) {
|
else if (param.equals("master")) {
|
||||||
Serial.println("equals master");
|
Serial.println("equals master");
|
||||||
updateBin = &updateBinM;
|
updatePrefix = updatePrefixM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Serial.println("Updating: " + *updateBin);
|
Serial.printf("Updating: %supdate.ino.bin\n", updatePrefix);
|
||||||
enterMode(ST_UPDATE);
|
enterMode(ST_UPDATE);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -1097,8 +1120,8 @@ const char *createKMLDynamic() {
|
||||||
|
|
||||||
strcpy(ptr, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><kml xmlns=\"http://www.opengis.net/kml/2.2\"><Document>");
|
strcpy(ptr, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><kml xmlns=\"http://www.opengis.net/kml/2.2\"><Document>");
|
||||||
|
|
||||||
for (int i = 0; i < sonde.nSonde; i++) {
|
for (int i = 0; i < sonde.config.maxsonde; i++) {
|
||||||
int snum = (i + sonde.currentSonde) % sonde.nSonde;
|
int snum = (i + sonde.currentSonde) % sonde.config.maxsonde;
|
||||||
if (sonde.sondeList[snum].active) {
|
if (sonde.sondeList[snum].active) {
|
||||||
addSondeStatusKML(ptr, snum);
|
addSondeStatusKML(ptr, snum);
|
||||||
}
|
}
|
||||||
|
@ -1289,6 +1312,10 @@ void SetupAsyncServer() {
|
||||||
request->send(200, "application/vnd.google-earth.kml+xml", createKMLDynamic());
|
request->send(200, "application/vnd.google-earth.kml+xml", createKMLDynamic());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on("/upd.html", HTTP_GET, [](AsyncWebServerRequest * request) {
|
||||||
|
request->send(SPIFFS, "/upd.html", String(), false, processor);
|
||||||
|
});
|
||||||
|
|
||||||
server.onNotFound([](AsyncWebServerRequest * request) {
|
server.onNotFound([](AsyncWebServerRequest * request) {
|
||||||
if (request->method() == HTTP_OPTIONS) {
|
if (request->method() == HTTP_OPTIONS) {
|
||||||
request->send(200);
|
request->send(200);
|
||||||
|
@ -1297,6 +1324,7 @@ void SetupAsyncServer() {
|
||||||
if (url.endsWith(".gpx"))
|
if (url.endsWith(".gpx"))
|
||||||
request->send(200, "application/gpx+xml", sendGPX(request));
|
request->send(200, "application/gpx+xml", sendGPX(request));
|
||||||
else {
|
else {
|
||||||
|
// TODO: set correct type for .js
|
||||||
request->send(SPIFFS, url, "text/html");
|
request->send(SPIFFS, url, "text/html");
|
||||||
Serial.printf("URL is %s\n", url.c_str());
|
Serial.printf("URL is %s\n", url.c_str());
|
||||||
//request->send(404);
|
//request->send(404);
|
||||||
|
@ -2266,6 +2294,11 @@ void loopDecoder() {
|
||||||
}
|
}
|
||||||
Serial.println("");
|
Serial.println("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if FEATURE_SONDEHUB
|
||||||
|
sondehub_handle_fimport(&shclient);
|
||||||
|
#endif
|
||||||
|
|
||||||
// wifi (axudp) or bluetooth (bttnc) active => send packet
|
// wifi (axudp) or bluetooth (bttnc) active => send packet
|
||||||
SondeInfo *s = &sonde.sondeList[rxtask.receiveSonde];
|
SondeInfo *s = &sonde.sondeList[rxtask.receiveSonde];
|
||||||
if ((res & 0xff) == 0 && (connected || tncclient.connected() )) {
|
if ((res & 0xff) == 0 && (connected || tncclient.connected() )) {
|
||||||
|
@ -2844,7 +2877,7 @@ void loopWifiScan() {
|
||||||
}
|
}
|
||||||
if (hasRS92) {
|
if (hasRS92) {
|
||||||
geteph();
|
geteph();
|
||||||
if(ephstate==EPH_PENDING) ephstate=EPH_ERROR;
|
if (ephstate == EPH_PENDING) ephstate = EPH_ERROR;
|
||||||
get_eph("/brdc");
|
get_eph("/brdc");
|
||||||
}
|
}
|
||||||
delay(3000);
|
delay(3000);
|
||||||
|
@ -2867,30 +2900,84 @@ void execOTA() {
|
||||||
bool isValidContentType = false;
|
bool isValidContentType = false;
|
||||||
sonde.clearDisplay();
|
sonde.clearDisplay();
|
||||||
uint8_t dispxs, dispys;
|
uint8_t dispxs, dispys;
|
||||||
if( ISOLED(sonde.config) ) {
|
if ( ISOLED(sonde.config) ) {
|
||||||
disp.rdis->setFont(FONT_SMALL);
|
disp.rdis->setFont(FONT_SMALL);
|
||||||
dispxs = dispys = 1;
|
dispxs = dispys = 1;
|
||||||
|
char uh[17];
|
||||||
|
strncpy(uh, updateHost, 17);
|
||||||
|
uh[16]=0;
|
||||||
|
disp.rdis->drawString(0, 0, uh);
|
||||||
} else {
|
} else {
|
||||||
disp.rdis->setFont(5);
|
disp.rdis->setFont(5);
|
||||||
dispxs = 18;
|
dispxs = 18;
|
||||||
dispys = 20;
|
dispys = 20;
|
||||||
|
disp.rdis->drawString(0, 0, updateHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
String dispHost = updateHost.substring(0, 16);
|
Serial.print("Connecting to: "); Serial.println(updateHost);
|
||||||
disp.rdis->drawString(0, 0, dispHost.c_str());
|
|
||||||
|
|
||||||
Serial.println("Connecting to: " + updateHost);
|
|
||||||
// Connect to Update host
|
// Connect to Update host
|
||||||
if (client.connect(updateHost.c_str(), updatePort)) {
|
if (!client.connect(updateHost, updatePort)) {
|
||||||
|
Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, update file system
|
||||||
|
Serial.println("Fetching fs update");
|
||||||
|
disp.rdis->drawString(0, 1 * dispys, "Fetching fs...");
|
||||||
|
client.printf("GET %supdate.fs.bin HTTP/1.1\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"Cache-Control: no-cache\r\n"
|
||||||
|
"Connection: close\r\n\r\n", updatePrefix, updateHost);
|
||||||
|
// see if we get some data....
|
||||||
|
|
||||||
|
int type = 0;
|
||||||
|
int res = fetchHTTPheader(&type);
|
||||||
|
if(res < 0) { return; }
|
||||||
|
// process data...
|
||||||
|
while(client.available()) {
|
||||||
|
// get header...
|
||||||
|
char fn[128];
|
||||||
|
fn[0] = '/';
|
||||||
|
client.readBytesUntil('\n', fn+1, 128);
|
||||||
|
char *sz = strchr(fn, ' ');
|
||||||
|
if(!sz) { client.stop(); return; }
|
||||||
|
*sz = 0;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.stop();
|
||||||
|
|
||||||
|
Serial.print("Connecting to: "); Serial.println(updateHost);
|
||||||
|
// Connect to Update host
|
||||||
|
if (!client.connect(updateHost, updatePort)) {
|
||||||
|
Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Connection succeeded, fecthing the bin
|
// Connection succeeded, fecthing the bin
|
||||||
Serial.println("Fetching bin: " + String(*updateBin));
|
Serial.printf("Fetching bin: %supdate.ino.bin\n", updatePrefix);
|
||||||
disp.rdis->drawString(0, 1 * dispys, "Fetching update");
|
disp.rdis->drawString(0, 3 * dispys, "Fetching update");
|
||||||
|
|
||||||
// Get the contents of the bin file
|
// Get the contents of the bin file
|
||||||
client.print(String("GET ") + *updateBin + " HTTP/1.1\r\n" +
|
client.printf("GET %supdate.ino.bin HTTP/1.1\r\n"
|
||||||
"Host: " + updateHost + "\r\n" +
|
"Host: %s\r\n"
|
||||||
"Cache-Control: no-cache\r\n" +
|
"Cache-Control: no-cache\r\n"
|
||||||
"Connection: close\r\n\r\n");
|
"Connection: close\r\n\r\n",
|
||||||
|
updatePrefix, updateHost);
|
||||||
|
|
||||||
// Check what is being sent
|
// Check what is being sent
|
||||||
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
|
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
|
||||||
|
@ -2898,101 +2985,27 @@ void execOTA() {
|
||||||
// "Cache-Control: no-cache\r\n" +
|
// "Cache-Control: no-cache\r\n" +
|
||||||
// "Connection: close\r\n\r\n");
|
// "Connection: close\r\n\r\n");
|
||||||
|
|
||||||
unsigned long timeout = millis();
|
int validType = 0;
|
||||||
while (client.available() == 0) {
|
contentLength = fetchHTTPheader( &validType );
|
||||||
if (millis() - timeout > 5000) {
|
if(validType==1) isValidContentType = true;
|
||||||
Serial.println("Client Timeout !");
|
|
||||||
client.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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") {
|
|
||||||
isValidContentType = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Connect to updateHost failed
|
|
||||||
// May be try?
|
|
||||||
// Probably a choppy network?
|
|
||||||
Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup");
|
|
||||||
// retry??
|
|
||||||
// execOTA();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check what is the contentLength and if content type is `application/octet-stream`
|
// Check what is the contentLength and if content type is `application/octet-stream`
|
||||||
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
|
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
|
||||||
disp.rdis->drawString(0, 2 * dispys, "Len: ");
|
disp.rdis->drawString(0, 4 * dispys, "Len: ");
|
||||||
String cls = String(contentLength);
|
String cls = String(contentLength);
|
||||||
disp.rdis->drawString(5 * dispxs, 2 * dispys, cls.c_str());
|
disp.rdis->drawString(5 * dispxs, 4 * dispys, cls.c_str());
|
||||||
|
|
||||||
// check contentLength and content type
|
// check contentLength and content type
|
||||||
if (contentLength && isValidContentType) {
|
if (contentLength && isValidContentType) {
|
||||||
// Check if there is enough to OTA Update
|
// Check if there is enough to OTA Update
|
||||||
bool canBegin = Update.begin(contentLength);
|
bool canBegin = Update.begin(contentLength);
|
||||||
disp.rdis->drawString(0, 4 * dispys, "Starting update");
|
|
||||||
|
|
||||||
// If yes, begin
|
// If yes, begin
|
||||||
if (canBegin) {
|
if (canBegin) {
|
||||||
|
disp.rdis->drawString(0, 5 * dispys, "Starting update");
|
||||||
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
|
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
|
||||||
// No activity would appear on the Serial monitor
|
// No activity would appear on the Serial monitor
|
||||||
// So be patient. This may take 2 - 5mins to complete
|
// So be patient. This may take 2 - 5mins to complete
|
||||||
disp.rdis->drawString(0, 5 * dispys, "Please wait!");
|
|
||||||
size_t written = Update.writeStream(client);
|
size_t written = Update.writeStream(client);
|
||||||
|
|
||||||
if (written == contentLength) {
|
if (written == contentLength) {
|
||||||
|
@ -3031,6 +3044,77 @@ void execOTA() {
|
||||||
enterMode(ST_DECODER);
|
enterMode(ST_DECODER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3137,16 +3221,16 @@ void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
|
||||||
// Only send email if provided
|
// Only send email if provided
|
||||||
if (strlen(conf->email) != 0) {
|
if (strlen(conf->email) != 0) {
|
||||||
sprintf(w,
|
sprintf(w,
|
||||||
"\"uploader_contact_email\": \"%s\",",
|
"\"uploader_contact_email\": \"%s\",",
|
||||||
conf->email);
|
conf->email);
|
||||||
w += strlen(w);
|
w += strlen(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only send antenna if provided
|
// Only send antenna if provided
|
||||||
if (strlen(conf->antenna) != 0) {
|
if (strlen(conf->antenna) != 0) {
|
||||||
sprintf(w,
|
sprintf(w,
|
||||||
"\"uploader_antenna\": \"%s\",",
|
"\"uploader_antenna\": \"%s\",",
|
||||||
conf->antenna);
|
conf->antenna);
|
||||||
w += strlen(w);
|
w += strlen(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3219,6 +3303,55 @@ const char *dfmSubtypeStrSH[16] = { NULL, NULL, 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);
|
||||||
|
if (res == 1) {
|
||||||
|
shImport = 2; // finished
|
||||||
|
}
|
||||||
|
} else if (shImport == 2) {
|
||||||
|
// waiting for next activation...
|
||||||
|
shImportInterval --;
|
||||||
|
if (shImportInterval <= 0) {
|
||||||
|
shImport = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sondehub_send_fimport(WiFiClient * client) {
|
||||||
|
if (shState == SH_CONN_APPENDING || shState == SH_CONN_WAITACK) {
|
||||||
|
// Currently busy with SondeHub data upload
|
||||||
|
// So do nothing here.
|
||||||
|
// sond_fimport will be re-sent later, when shState becomes SH_CONN_IDLE
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// It's time to run, so check prerequisites
|
||||||
|
float lat = sonde.config.sondehub.lat, lon = sonde.config.sondehub.lon;
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// in hours.... max allowed diff UTC <-> sonde time
|
// in hours.... max allowed diff UTC <-> sonde time
|
||||||
#define SONDEHUB_TIME_THRESHOLD (3)
|
#define SONDEHUB_TIME_THRESHOLD (3)
|
||||||
void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf) {
|
void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf) {
|
||||||
|
@ -3248,7 +3381,9 @@ 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;
|
if (SH_LOC_AUTO_IS_CHASE) chase = SH_LOC_CHASE; else chase = SH_LOC_FIXED;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (client->available() > 0) {
|
// 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...
|
// data is available from remote server, process it...
|
||||||
int cnt = client->readBytesUntil('\n', rs_msg, MSG_SIZE - 1);
|
int cnt = client->readBytesUntil('\n', rs_msg, MSG_SIZE - 1);
|
||||||
rs_msg[cnt] = 0;
|
rs_msg[cnt] = 0;
|
||||||
|
@ -3256,6 +3391,7 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
|
||||||
// If something that looks like a valid HTTP response is received, we are ready to send the next data item
|
// 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) {
|
if (shState == SH_CONN_WAITACK && cnt > 11 && strncmp(rs_msg, "HTTP/1", 6) == 0) {
|
||||||
shState = SH_CONN_IDLE;
|
shState = SH_CONN_IDLE;
|
||||||
|
sondehub_send_fimport(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3365,8 +3501,8 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
|
||||||
// Only send antenna if provided
|
// Only send antenna if provided
|
||||||
if (strlen(conf->antenna) != 0) {
|
if (strlen(conf->antenna) != 0) {
|
||||||
sprintf(w,
|
sprintf(w,
|
||||||
"\"uploader_antenna\": \"%s\",",
|
"\"uploader_antenna\": \"%s\",",
|
||||||
conf->antenna);
|
conf->antenna);
|
||||||
w += strlen(w);
|
w += strlen(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3400,7 +3536,7 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
|
||||||
sprintf(w, "}");
|
sprintf(w, "}");
|
||||||
|
|
||||||
if (shState != SH_CONN_APPENDING) {
|
if (shState != SH_CONN_APPENDING) {
|
||||||
sondehub_send_header(client, s, conf);
|
sondehub_send_header(client, s, conf, &timeinfo);
|
||||||
sondehub_send_next(client, s, conf, rs_msg, strlen(rs_msg), 1);
|
sondehub_send_next(client, s, conf, rs_msg, strlen(rs_msg), 1);
|
||||||
shState = SH_CONN_APPENDING;
|
shState = SH_CONN_APPENDING;
|
||||||
shStart = now;
|
shStart = now;
|
||||||
|
@ -3431,19 +3567,36 @@ void sondehub_finish_data(WiFiClient * client, SondeInfo * s, struct st_sondehub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sondehub_send_header(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf) {
|
static const char *DAYS[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||||
|
static const char *MONTHS[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Noc", "Dec"};
|
||||||
|
|
||||||
|
void sondehub_send_header(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf, struct tm *now) {
|
||||||
Serial.print("PUT /sondes/telemetry HTTP/1.1\r\n"
|
Serial.print("PUT /sondes/telemetry HTTP/1.1\r\n"
|
||||||
"Host: ");
|
"Host: ");
|
||||||
Serial.println(conf->host);
|
Serial.println(conf->host);
|
||||||
Serial.println("accept: text/plain\r\n"
|
Serial.print("accept: text/plain\r\n"
|
||||||
"Content-Type: application/json\r\n"
|
"Content-Type: application/json\r\n"
|
||||||
"Transfer-Encoding: chunked\r\n");
|
"Transfer-Encoding: chunked\r\n");
|
||||||
|
|
||||||
client->print("PUT /sondes/telemetry HTTP/1.1\r\n"
|
client->print("PUT /sondes/telemetry HTTP/1.1\r\n"
|
||||||
"Host: ");
|
"Host: ");
|
||||||
client->println(conf->host);
|
client->println(conf->host);
|
||||||
client->println("accept: text/plain\r\n"
|
client->print("accept: text/plain\r\n"
|
||||||
"Content-Type: application/json\r\n"
|
"Content-Type: application/json\r\n"
|
||||||
"Transfer-Encoding: chunked\r\n");
|
"Transfer-Encoding: chunked\r\n");
|
||||||
|
if (now) {
|
||||||
|
Serial.printf("Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
|
||||||
|
DAYS[now->tm_wday], now->tm_mday, MONTHS[now->tm_mon], now->tm_year + 1900,
|
||||||
|
now->tm_hour, now->tm_min, now->tm_sec);
|
||||||
|
client->printf("Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
|
||||||
|
DAYS[now->tm_wday], now->tm_mday, MONTHS[now->tm_mon], now->tm_year + 1900,
|
||||||
|
now->tm_hour, now->tm_min, now->tm_sec);
|
||||||
|
}
|
||||||
|
client->print("User-agent: ");
|
||||||
|
client->print(version_name);
|
||||||
|
client->print("/");
|
||||||
|
client->println(version_id);
|
||||||
|
client->println(""); // another cr lf as indication of end of header
|
||||||
}
|
}
|
||||||
void sondehub_send_next(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf, char *chunk, int chunklen, int first) {
|
void sondehub_send_next(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf, char *chunk, int chunklen, int first) {
|
||||||
// send next chunk of JSON request
|
// send next chunk of JSON request
|
||||||
|
|
|
@ -129,6 +129,7 @@ sondehub.lon=
|
||||||
sondehub.alt=
|
sondehub.alt=
|
||||||
sondehub.antenna=
|
sondehub.antenna=
|
||||||
sondehub.email=
|
sondehub.email=
|
||||||
|
sondehub.fimport=0/100/60
|
||||||
#-------------------------------#
|
#-------------------------------#
|
||||||
# EOF
|
# EOF
|
||||||
#-------------------------------#
|
#-------------------------------#
|
||||||
|
|
|
@ -6,7 +6,7 @@ stypes.set('M', 'M10');
|
||||||
stypes.set('2', 'M20');
|
stypes.set('2', 'M20');
|
||||||
stypes.set('3', 'MP3H');
|
stypes.set('3', 'MP3H');
|
||||||
|
|
||||||
/* Used by qrg.html in RX_FSK.ino */
|
/* (no longer) Used by qrg.html in RX_FSK.ino */
|
||||||
function prep() {
|
function prep() {
|
||||||
var stlist=document.querySelectorAll("input.stype");
|
var stlist=document.querySelectorAll("input.stype");
|
||||||
for(txt of stlist){
|
for(txt of stlist){
|
||||||
|
@ -24,4 +24,19 @@ function prep() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = prep;
|
function qrgTable() {
|
||||||
|
var tab=document.getElementById("divTable");
|
||||||
|
|
||||||
|
var table = "<table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>";
|
||||||
|
for(i=0; i<qrgs.length; i++) {
|
||||||
|
var ck = "";
|
||||||
|
if(qrgs[i][0]) ck="checked";
|
||||||
|
table += "<tr><td>" + (i+1) + "</td><td><input name=\"A" + (i+1) + "\" type=\"checkbox\" " + ck + "/></td>";
|
||||||
|
table += "<td><input name=\"F" + (i+1) + "\" type=\"text\" width=12 value=\"" + qrgs[i][1] + "\"></td>";
|
||||||
|
table += "<td><input name=\"S" + (i+1) + "\" type=\"text\" value=\"" + qrgs[i][2] +"\"></td>";
|
||||||
|
table += "<td><input class=\"stype\" name=\"T" + (i+1) + "\" value=\"" + qrgs[i][3] + "\"></td></tr>";
|
||||||
|
}
|
||||||
|
table += "</table>";
|
||||||
|
tab.innerHTML = table;
|
||||||
|
prep();
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<form action="update.html" method="post"><p>Currently installed: devel20210908-B14</p>
|
<form action="update.html" method="post"><p>Currently installed: %FULLNAMEID%</p>
|
||||||
<p>
|
<p>
|
||||||
Available master: <span id="masterDiv">(...checking...)</span>
|
Available master: <span id="masterDiv">(...checking...)</span>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "ShFreqImport.h"
|
||||||
|
#include "Sonde.h"
|
||||||
|
|
||||||
|
static int ppos;
|
||||||
|
static int quotes;
|
||||||
|
static char id[20];
|
||||||
|
static int idpos;
|
||||||
|
static float lat, lon, freq;
|
||||||
|
static char type[20];
|
||||||
|
|
||||||
|
static uint8_t inuse[1+99/8]; // MAXSONDE is 99
|
||||||
|
|
||||||
|
static char keyword[40];
|
||||||
|
static int keywordpos;
|
||||||
|
static char value[40];
|
||||||
|
static int valuepos;
|
||||||
|
|
||||||
|
static int importState;
|
||||||
|
static float homelat, homelon;
|
||||||
|
|
||||||
|
|
||||||
|
// Map SondeHub type string to Stype. -1 for not supported types.
|
||||||
|
int ShFreqImport::stringToStype(const char *type) {
|
||||||
|
if(type[2]=='4') return STYPE_RS41;
|
||||||
|
if(type[2]=='9') return STYPE_RS92;
|
||||||
|
if(type[1]=='1') return STYPE_M10;
|
||||||
|
if(type[1]=='2') return STYPE_M20;
|
||||||
|
if(type[0]=='D') return STYPE_DFM;
|
||||||
|
if(type[2]=='3') return STYPE_MP3H; // TODO: check if '3' is correct
|
||||||
|
return -1; // iMet is not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
// in Display.cpp
|
||||||
|
extern float calcLatLonDist(float lat1, float lon1, float lat2, float lon2);
|
||||||
|
|
||||||
|
void ShFreqImport::setLabel(int idx, char *id, float lat, float lon) {
|
||||||
|
snprintf(sonde.sondeList[idx].launchsite, 18, "@%s/%d", id, (int)(calcLatLonDist(homelat, homelon, lat, lon)/1000));
|
||||||
|
sonde.sondeList[idx].launchsite[17] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShFreqImport::usekeyvalue() {
|
||||||
|
if(strcmp(keyword,"lat")==0) lat = atof(value);
|
||||||
|
if(strcmp(keyword,"lon")==0) lon = atof(value);
|
||||||
|
if(strcmp(keyword,"frequency")==0) freq = atof(value);
|
||||||
|
if(strcmp(keyword,"type")==0) strcpy(type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* populate qrg.txt with frequency of near sonde */
|
||||||
|
void ShFreqImport::populate(char *id, float lat, float lon, float freq, const char *type)
|
||||||
|
{
|
||||||
|
//printf(" ID %s: %.5f, %.5f f=%.3f, type=%s \n", id, lat, lon, freq, type);
|
||||||
|
// Skip if freq already exists
|
||||||
|
int stype = stringToStype(type);
|
||||||
|
if(stype<0) return; // unsupported type
|
||||||
|
|
||||||
|
// check if frequency exists already
|
||||||
|
// don't do anything if its a static entry
|
||||||
|
// update label if its a dynamic SH entry
|
||||||
|
int i;
|
||||||
|
for(i=0; i<sonde.config.maxsonde; i++) {
|
||||||
|
if( abs(sonde.sondeList[i].freq-freq)<0.0015 ) { // exists already, max error 1500 Hz
|
||||||
|
Serial.printf("id %s close to %d\n", id, i);
|
||||||
|
if( sonde.sondeList[i].type == stype) {
|
||||||
|
char *l = sonde.sondeList[i].launchsite;
|
||||||
|
if( *l=='@' || *l==' ' || *l==0 ) {
|
||||||
|
setLabel(i, id, lat, lon);
|
||||||
|
inuse[i/8] |= (1<<(i&7));
|
||||||
|
}
|
||||||
|
sonde.sondeList[i].active = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find slot
|
||||||
|
// slots with empty launchsite are considered available for automated entries
|
||||||
|
while(ppos < sonde.config.maxsonde) {
|
||||||
|
if( *sonde.sondeList[ppos].launchsite==' ' || *sonde.sondeList[ppos].launchsite== 0 ) break;
|
||||||
|
ppos++;
|
||||||
|
}
|
||||||
|
if(ppos >= sonde.config.maxsonde) {
|
||||||
|
Serial.println("populate: out of free slots");
|
||||||
|
return;
|
||||||
|
} // no more free slots
|
||||||
|
|
||||||
|
sonde.sondeList[ppos].active = 1;
|
||||||
|
sonde.sondeList[ppos].freq = freq;
|
||||||
|
sonde.sondeList[ppos].type = (SondeType)stype;
|
||||||
|
setLabel(ppos, id, lat, lon);
|
||||||
|
inuse[ppos/8] |= (1<<(ppos&7));
|
||||||
|
ppos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clears all remaining automatically filled slots (no longer in SH data)
|
||||||
|
void ShFreqImport::cleanup() {
|
||||||
|
//Serial.println("Cleanup called ********");
|
||||||
|
for(int i=0; i<sonde.config.maxsonde; i++) {
|
||||||
|
if( (((inuse[i/8]>>(i&7))&1) == 0) && *sonde.sondeList[i].launchsite=='@' ) {
|
||||||
|
Serial.printf("removing #%d\n", i);
|
||||||
|
sonde.sondeList[i].launchsite[0] = 0;
|
||||||
|
sonde.sondeList[i].active = 0;
|
||||||
|
sonde.sondeList[i].freq = 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BUFLEN 128
|
||||||
|
#define VALLEN 20
|
||||||
|
int ShFreqImport::handleChar(char c) {
|
||||||
|
switch(importState) {
|
||||||
|
case START:
|
||||||
|
// wait for initial '{'
|
||||||
|
if(c=='{') {
|
||||||
|
lat = NAN; lon = NAN; freq = NAN; *type = 0;
|
||||||
|
importState++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BEFOREID:
|
||||||
|
// what for first '"' in { "A1234567" : { ... } }; or detect end
|
||||||
|
if(c=='"') { idpos = 0; importState++; }
|
||||||
|
if(c=='}') { importState = ENDREACHED; }
|
||||||
|
break;
|
||||||
|
case COPYID:
|
||||||
|
// copy ID "A1234567" until second '"' is earched
|
||||||
|
if(c=='"') { id[idpos] = 0; printf("ID: %s\n", id); importState++; }
|
||||||
|
else id[idpos++] = c;
|
||||||
|
break;
|
||||||
|
case AFTERID:
|
||||||
|
// wait for '{' in '"A1234567": { ...'
|
||||||
|
if(c=='{') importState++;
|
||||||
|
break;
|
||||||
|
case BEFOREKEY:
|
||||||
|
if(c=='"') { keywordpos = 0; importState++; }
|
||||||
|
break;
|
||||||
|
case COPYKEY:
|
||||||
|
if(c=='"') { importState++; keyword[keywordpos] = 0; /* printf("Key: >%s<\n", keyword);*/ }
|
||||||
|
else keyword[keywordpos++] = c;
|
||||||
|
break;
|
||||||
|
case AFTERKEY:
|
||||||
|
if(c==':') {
|
||||||
|
valuepos = 0;
|
||||||
|
quotes = 0;
|
||||||
|
if(strcmp(keyword,"lat")==0 || strcmp(keyword, "lon")==0 || strcmp(keyword, "frequency")==0 )
|
||||||
|
importState = BEFORENUMVAL;
|
||||||
|
else {
|
||||||
|
if (strcmp(keyword, "type")==0)
|
||||||
|
importState = BEFORESTRINGVAL;
|
||||||
|
else
|
||||||
|
importState = SKIPVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BEFORENUMVAL:
|
||||||
|
if( (c>='0'&&c<='9') || c=='-') { value[0] = c; valuepos=1; importState++; }
|
||||||
|
break;
|
||||||
|
case COPYNUMVAL:
|
||||||
|
if( !(c>='0'&&c<='9') && c!='-' && c!='.' ) {
|
||||||
|
value[valuepos]=0; importState=SKIPVAL; usekeyvalue();
|
||||||
|
if(c!=',' && c!='}') break;
|
||||||
|
}
|
||||||
|
else { value[valuepos++] = c; break; }
|
||||||
|
// intenionall fall-through
|
||||||
|
case SKIPVAL:
|
||||||
|
// This is rather fragile, we *should* handle more escaping and so on but do not do so so far, only simple quotes
|
||||||
|
if(c=='"') quotes = !quotes;
|
||||||
|
if(quotes) break;
|
||||||
|
if(c==',') importState = BEFOREKEY;
|
||||||
|
if(c=='}') {
|
||||||
|
// we have an ID and all key/value pairs, check if its good....
|
||||||
|
if( !isnan(lat) && !isnan(lon) && !isnan(freq) && type[0] ) {
|
||||||
|
printf("SondeHub import: populate %s %f %f %f %s\n", id, lat, lon, freq, type);
|
||||||
|
populate(id, lat, lon, freq, type);
|
||||||
|
} else {
|
||||||
|
printf("Skipping incomplete %s\n", id);
|
||||||
|
}
|
||||||
|
importState = ENDORNEXT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BEFORESTRINGVAL:
|
||||||
|
if(c=='"') importState++;
|
||||||
|
break;
|
||||||
|
case COPYSTRINGVAL:
|
||||||
|
if(c=='"') { importState=SKIPVAL; value[valuepos]=0; usekeyvalue(); }
|
||||||
|
else value[valuepos++] = c;
|
||||||
|
break;
|
||||||
|
case ENDORNEXT:
|
||||||
|
// next we have to see either a final "}', or a comma before the next id
|
||||||
|
if(c==',') importState = BEFOREID;
|
||||||
|
else if (c=='}') { importState = ENDREACHED; cleanup(); return 1; }
|
||||||
|
break;
|
||||||
|
case ENDREACHED:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lat lon in deg, dist in km, time in minutes
|
||||||
|
int ShFreqImport::shImportSendRequest(WiFiClient *client, float lat, float lon, int dist, int time) {
|
||||||
|
if(!client->connected()) {
|
||||||
|
if(!client->connect(sonde.config.sondehub.host, 80)) {
|
||||||
|
Serial.println("Connection FAILED");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("Sending SondeHub import request");
|
||||||
|
char req[300];
|
||||||
|
snprintf(req, 200, "GET /sondes?lat=%f&lon=%f&distance=%d&last=%d HTTP/1.1\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"Accept: application/json\r\n"
|
||||||
|
"Cache-Control: no-cache\r\n\r\n",
|
||||||
|
lat, lon, dist*1000, time*60, sonde.config.sondehub.host);
|
||||||
|
client->print(req);
|
||||||
|
Serial.print(req);
|
||||||
|
importState = START;
|
||||||
|
homelat = lat;
|
||||||
|
homelon = lon;
|
||||||
|
memset(inuse, 0, sizeof(inuse));
|
||||||
|
ppos = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return 0 if more data should be read (later), 1 if finished (close connection...)
|
||||||
|
int ShFreqImport::shImportHandleReply(WiFiClient *client) {
|
||||||
|
while(client->available()) {
|
||||||
|
int res = handleChar(client->read());
|
||||||
|
if(res) return res;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef SH_FREQ_IMPORT_
|
||||||
|
#define SH_FREQ_IMPORT_H
|
||||||
|
// Automated frequency import from SondeHub
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
|
enum ImportState { START, BEFOREID, COPYID, AFTERID, BEFOREKEY, COPYKEY, AFTERKEY, SKIPVAL, BEFORENUMVAL, COPYNUMVAL, BEFORESTRINGVAL, COPYSTRINGVAL, AFTERPAYLOAD, ENDORNEXT, ENDREACHED };
|
||||||
|
|
||||||
|
class ShFreqImport {
|
||||||
|
public:
|
||||||
|
// Fetch data from sondehub and populate qrg.txt with result
|
||||||
|
// return: 0: ok; 1: failure
|
||||||
|
static int shImportSendRequest(WiFiClient *client, float lat, float lon, int dist, int time);
|
||||||
|
|
||||||
|
// return 0: ok, need more data; 1: finished/failure, close connection
|
||||||
|
// Asynchronous I/O. Handle data if available
|
||||||
|
static int shImportHandleReply(WiFiClient *client);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int stringToStype(const char *type);
|
||||||
|
static void setLabel(int idx, char *id, float lat, float lon);
|
||||||
|
static void usekeyvalue();
|
||||||
|
static int handleChar(char c);
|
||||||
|
|
||||||
|
// add one entry on available slot at or after ppos
|
||||||
|
static void populate(char *id, float lat, float lon, float freq, const char *type);
|
||||||
|
static void cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -357,26 +357,26 @@ void Sonde::addSonde(float frequency, SondeType type, int active, char *launchsi
|
||||||
// called by updateState (only)
|
// called by updateState (only)
|
||||||
void Sonde::nextConfig() {
|
void Sonde::nextConfig() {
|
||||||
currentSonde++;
|
currentSonde++;
|
||||||
if(currentSonde>=nSonde) {
|
if(currentSonde>=config.maxsonde) {
|
||||||
currentSonde=0;
|
currentSonde=0;
|
||||||
}
|
}
|
||||||
// Skip non-active entries (but don't loop forever if there are no active ones)
|
// Skip non-active entries (but don't loop forever if there are no active ones)
|
||||||
for(int i=0; i<config.maxsonde - 1; i++) {
|
for(int i=0; i<config.maxsonde - 1; i++) {
|
||||||
if(!sondeList[currentSonde].active) {
|
if(!sondeList[currentSonde].active) {
|
||||||
currentSonde++;
|
currentSonde++;
|
||||||
if(currentSonde>=nSonde) currentSonde=0;
|
if(currentSonde>=config.maxsonde) currentSonde=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Sonde::nextRxSonde() {
|
void Sonde::nextRxSonde() {
|
||||||
rxtask.currentSonde++;
|
rxtask.currentSonde++;
|
||||||
if(rxtask.currentSonde>=nSonde) {
|
if(rxtask.currentSonde>=config.maxsonde) {
|
||||||
rxtask.currentSonde=0;
|
rxtask.currentSonde=0;
|
||||||
}
|
}
|
||||||
for(int i=0; i<config.maxsonde - 1; i++) {
|
for(int i=0; i<config.maxsonde - 1; i++) {
|
||||||
if(!sondeList[rxtask.currentSonde].active) {
|
if(!sondeList[rxtask.currentSonde].active) {
|
||||||
rxtask.currentSonde++;
|
rxtask.currentSonde++;
|
||||||
if(rxtask.currentSonde>=nSonde) rxtask.currentSonde=0;
|
if(rxtask.currentSonde>=config.maxsonde) rxtask.currentSonde=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
|
Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
|
||||||
|
@ -402,7 +402,7 @@ void Sonde::setup() {
|
||||||
for(int i=0; i<config.maxsonde - 1; i++) {
|
for(int i=0; i<config.maxsonde - 1; i++) {
|
||||||
if(!sondeList[rxtask.currentSonde].active) {
|
if(!sondeList[rxtask.currentSonde].active) {
|
||||||
rxtask.currentSonde++;
|
rxtask.currentSonde++;
|
||||||
if(rxtask.currentSonde>=nSonde) rxtask.currentSonde=0;
|
if(rxtask.currentSonde>=config.maxsonde) rxtask.currentSonde=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sonde.currentSonde = rxtask.currentSonde;
|
sonde.currentSonde = rxtask.currentSonde;
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
#ifndef Sonde_h
|
#ifndef Sonde_h
|
||||||
#define Sonde_h
|
#define Sonde_h
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
enum DbgLevel { DEBUG_OFF=0, DEBUG_INFO=1, DEBUG_SPARSER=16, DEBUG_DISPLAY=8 }; // to be extended for configuring serial debug output
|
enum DbgLevel { DEBUG_OFF=0, DEBUG_INFO=1, DEBUG_SPARSER=16, DEBUG_DISPLAY=8 }; // to be extended for configuring serial debug output
|
||||||
extern uint8_t debug;
|
extern uint8_t debug;
|
||||||
|
|
||||||
|
@ -194,6 +196,7 @@ struct st_sondehub {
|
||||||
char alt[20];
|
char alt[20];
|
||||||
char antenna[64];
|
char antenna[64];
|
||||||
char email[64];
|
char email[64];
|
||||||
|
char fimport[20];
|
||||||
};
|
};
|
||||||
|
|
||||||
// to be extended
|
// to be extended
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const char *version_name = "rdzTTGOsonde";
|
const char *version_name = "rdzTTGOsonde";
|
||||||
const char *version_id = "devel20210914";
|
const char *version_id = "devel20210916";
|
||||||
const int SPIFFS_MAJOR=2;
|
const int SPIFFS_MAJOR=2;
|
||||||
const int SPIFFS_MINOR=14;
|
const int SPIFFS_MINOR=16;
|
||||||
|
|
|
@ -20,17 +20,13 @@ lib_deps_external =
|
||||||
olikraus/U8g2 @ ^2.28.8
|
olikraus/U8g2 @ ^2.28.8
|
||||||
AXP202X_Library
|
AXP202X_Library
|
||||||
stevemarple/MicroNMEA @ ^2.0.5
|
stevemarple/MicroNMEA @ ^2.0.5
|
||||||
; nkawu/TFT 22 ILI9225 @ ^1.4.4
|
|
||||||
me-no-dev/ESP Async WebServer @ ^1.2.3
|
me-no-dev/ESP Async WebServer @ ^1.2.3
|
||||||
https://github.com/moononournation/Arduino_GFX#v1.1.5
|
https://github.com/moononournation/Arduino_GFX#v1.1.5
|
||||||
; most recent has compile error (initialization order wrong)
|
|
||||||
; https://github.com/moononournation/Arduino_GFX
|
|
||||||
https://github.com/dx168b/async-mqtt-client
|
https://github.com/dx168b/async-mqtt-client
|
||||||
|
|
||||||
[env:ttgo-lora32]
|
[env:ttgo-lora32]
|
||||||
platform = https://github.com/platformio/platform-espressif32.git
|
platform = https://github.com/platformio/platform-espressif32.git
|
||||||
board = ttgo-lora32-v1
|
board = ttgo-lora32-v1
|
||||||
; board_build.partitions = partition.csv
|
|
||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
@ -38,3 +34,6 @@ lib_deps =
|
||||||
${extra.lib_deps_external}
|
${extra.lib_deps_external}
|
||||||
paulstoffregen/Time@^1.6.0
|
paulstoffregen/Time@^1.6.0
|
||||||
lib_ignore = Time
|
lib_ignore = Time
|
||||||
|
; board_build.partitions = partition.csv
|
||||||
|
;build_flags = -Wl,-Map,output.map
|
||||||
|
;check_tool = clangtidy
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from os import listdir
|
||||||
|
import sys
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
|
||||||
|
# create update.fs.bin with content:
|
||||||
|
# filename size CR LF
|
||||||
|
# data for filename ...
|
||||||
|
|
||||||
|
files = listdir(path)
|
||||||
|
files = list(filter(lambda x: x.endswith('.js') or x.endswith('.html') or x.endswith('.css'), files))
|
||||||
|
for f in files:
|
||||||
|
with open(path+"/"+f,"rb") as myf:
|
||||||
|
data=myf.read(-1)
|
||||||
|
head = str.encode(f + " " + str(len(data)) + "\r\n")
|
||||||
|
sys.stdout.buffer.write(head)
|
||||||
|
sys.stdout.buffer.write(data)
|
||||||
|
|
Ładowanie…
Reference in New Issue