Merge branch 'dl9rdz:devel' into devel

pull/170/head
Michael Carter 2021-08-16 08:15:58 +01:00 zatwierdzone przez GitHub
commit c55b97c809
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
20 zmienionych plików z 1863 dodań i 285 usunięć

Wyświetl plik

@ -61,6 +61,8 @@ commit_website_files() {
git add ${BRANCH}/update.ino.bin
echo "${TRAVIS_COMMIT_MESSAGE}" >> ${BRANCH}/${VERSION}-changelog.txt
git add ${BRANCH}/${VERSION}-changelog.txt
echo "<html><body><p>${VERSION}</p></body></html>" > ${BRANCH}/update-info.html
git add ${BRANCH}/update-info.html
git commit --message "Travis build: $TRAVIS_BUILD_NUMBER"
}
upload_files() {

Wyświetl plik

@ -59,7 +59,19 @@ WiFiClient client;
#define SONDEHUB_MOBILE_STATION_UPDATE_TIME (30*1000) // 30 sec
WiFiClient shclient; // Sondehub v2
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) or GPS position (if there is a GPS fix) as fixed station position (no chase mode) to sondehub
SH_LOC_CHASE: always activate chase mode and send GPS position (if available)
SH_LOC_AUTO: if there is no valid GPS position, or GPS position < MIN_LOC_AUTO_DIST away from known fixed position: use FIXED mode
otherwise, i.e. if there is a valid GPS position and (either no fixed position in config, or GPS position is far away from fixed position), use CHASE mode.
*/
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 ) )
#endif
extern float calcLatLonDist(float lat1, float lon1, float lat2, float lon2);
// KISS over TCP for communicating with APRSdroid
WiFiServer tncserver(14580);
@ -99,6 +111,8 @@ static unsigned long specTimer;
void enterMode(int mode);
void WiFiEvent(WiFiEvent_t event);
char buffer[85];
MicroNMEA nmea(buffer, sizeof(buffer));
// Read line from file, independent of line termination (LF or CR LF)
String readLine(Stream &stream) {
@ -186,7 +200,6 @@ void setupChannelList() {
while (file.available()) {
String line = readLine(file); //file.readStringUntil('\n');
String sitename;
if (!file.available()) break;
if (line[0] == '#') continue;
char *space = strchr(line.c_str(), ' ');
if (!space) continue;
@ -417,14 +430,14 @@ const char *createSondeHubMap() {
HTMLBODY(ptr, "map.html");
if (!sonde.config.sondehub.active) {
strcat(ptr, "<div>NOTE: SondeHub uploading is not enabled, detected sonde will not be visable on map</div>");
if ((*s->ser == 0) && (sonde.config.sondehub.lat[0] != '\0')) {
sprintf(ptr + strlen(ptr), "<iframe src=\"https://sondehub.org/#!mc=%s,%s&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:95vh\"></iframe>", sonde.config.sondehub.lat, sonde.config.sondehub.lon);
if ((*s->ser == 0) && ( !isnan(sonde.config.sondehub.lat))) {
sprintf(ptr + strlen(ptr), "<iframe src=\"https://sondehub.org/#!mc=%f,%f&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:95vh\"></iframe>", sonde.config.sondehub.lat, sonde.config.sondehub.lon);
} else {
sprintf(ptr + strlen(ptr), "<iframe src=\"https://sondehub.org/%s\" style=\"border:1px solid #00A3D3;border-radius:20px;height:95vh\"></iframe>", s-> ser);
}
} else {
if ((*s->ser == 0) && (sonde.config.sondehub.lat[0] != '\0')) {
sprintf(ptr, "<iframe src=\"https://sondehub.org/#!mc=%s,%s&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%%\"></iframe>", sonde.config.sondehub.lat, sonde.config.sondehub.lon);
if ((*s->ser == 0) && (!isnan(sonde.config.sondehub.lat))) {
sprintf(ptr, "<iframe src=\"https://sondehub.org/#!mc=%f,%f&mz=8\" style=\"border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%%\"></iframe>", sonde.config.sondehub.lat, sonde.config.sondehub.lon);
} else {
sprintf(ptr, "<iframe src=\"https://sondehub.org/%s\" style=\"border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%%\"></iframe>", s-> ser);
}
@ -516,6 +529,39 @@ const char *createStatusForm() {
return message;
}
const char *createLiveJson() {
char *ptr = message;
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]);
}
if (sonde.config.gps_rxd < 0) {
// gps disabled
} else {
long sat = nmea.getNumSatellites();
long speed = nmea.getSpeed();
long dir = nmea.getCourse();
long lat = nmea.getLatitude();
long lon = nmea.getLongitude();
long alt = -1;
/*bool b = */nmea.getAltitude(alt);
bool valid = nmea.isValid();
uint8_t hdop = nmea.getHDOP();
if (valid) {
strcat(ptr, ",");
sprintf(ptr + strlen(ptr), "\"gps\": {\"lat\": %ld, \"lon\": %ld, \"alt\": %ld, \"sat\": %ld, \"speed\": %ld, \"dir\": %ld, \"hdop\": %d }", lat, lon, alt, sat, speed, dir, hdop);
}
}
strcat(ptr, "}");
return message;
}
///////////////////// Config form
@ -542,10 +588,10 @@ struct st_configitems {
struct st_configitems config_list[] = {
/* General config settings */
{"", "Software configuration", -5, NULL},
{"wifi", "Wifi mode (0/1/2/3)", 0, &sonde.config.wifi},
{"wifi", "Wifi mode (0-Off/1-Client/2-Access Point/3-Debug)", 0, &sonde.config.wifi},
{"debug", "Debug mode (0/1)", 0, &sonde.config.debug},
{"maxsonde", "Maxsonde", 0, &sonde.config.maxsonde},
{"screenfile", "Screen config (0=old, 1=OLED, 2=TFT, 3=TFT[port])", 0, &sonde.config.screenfile},
{"maxsonde", "Maxsonde (max # QRG entries)", 0, &sonde.config.maxsonde},
{"screenfile", "Screen config (0=automatic; 1-5=predefined; other=custom)", 0, &sonde.config.screenfile},
{"display", "Display screens (scan,default,...)", -6, sonde.config.display},
/* Spectrum display settings */
{"spectrum", "Show spectrum (-1=no, 0=forever, >0=seconds)", 0, &sonde.config.spectrum},
@ -555,7 +601,6 @@ struct st_configitems config_list[] = {
{"noisefloor", "Spectrum noisefloor", 0, &sonde.config.noisefloor},
/* decoder settings */
{"", "Receiver configuration", -5, NULL},
{"showafc", "Show AFC value", 0, &sonde.config.showafc},
{"freqofs", "RX frequency offset (Hz)", 0, &sonde.config.freqofs},
{"rs41.agcbw", "RS41 AGC bandwidth", 0, &sonde.config.rs41.agcbw},
{"rs41.rxbw", "RS41 RX bandwidth", 0, &sonde.config.rs41.rxbw},
@ -601,7 +646,7 @@ struct st_configitems config_list[] = {
/* Hardware dependeing settings */
{"", "Hardware configuration (requires reboot)", -5, NULL},
{"disptype", "Display type (0=OLED/SSD1306, 1=TFT/ILI9225, 2=OLED/SH1106)", 0, &sonde.config.disptype},
{"disptype", "Display type (0=OLED/SSD1306, 1=ILI9225, 2=OLED/SH1106, 3=ILI9341, 4=ILI9342)", 0, &sonde.config.disptype},
{"norx_timeout", "No-RX-Timeout in seconds (-1=disabled)", 0, &sonde.config.norx_timeout},
{"oled_sda", "OLED SDA/TFT SDA", 0, &sonde.config.oled_sda},
{"oled_scl", "OLED SCL/TFT CLK", 0, &sonde.config.oled_scl},
@ -609,7 +654,7 @@ struct st_configitems config_list[] = {
{"tft_rs", "TFT RS", 0, &sonde.config.tft_rs},
{"tft_cs", "TFT CS", 0, &sonde.config.tft_cs},
{"tft_orient", "TFT orientation (0/1/2/3), OLED flip: 3", 0, &sonde.config.tft_orient},
{"tft_modeflip", "TFT modeflip (usually 0)", 0, &sonde.config.tft_modeflip},
{"tft_spifreq", "TFT SPI speed", 0, &sonde.config.tft_spifreq},
{"button_pin", "Button input port", -4, &sonde.config.button_pin},
{"button2_pin", "Button 2 input port", -4, &sonde.config.button2_pin},
{"button2_axp", "Use AXP192 PWR as Button 2", 0, &sonde.config.button2_axp},
@ -618,20 +663,26 @@ struct st_configitems config_list[] = {
{"led_pout", "LED output port", 0, &sonde.config.led_pout},
{"gps_rxd", "GPS RXD pin (-1 to disable)", 0, &sonde.config.gps_rxd},
{"gps_txd", "GPS TXD pin (not really needed)", 0, &sonde.config.gps_txd},
#if 1
{"sx1278_ss", "SX1278 SS", 0, &sonde.config.sx1278_ss},
{"sx1278_miso", "SX1278 MISO", 0, &sonde.config.sx1278_miso},
{"sx1278_mosi", "SX1278 MOSI", 0, &sonde.config.sx1278_mosi},
{"sx1278_sck", "SX1278 SCK", 0, &sonde.config.sx1278_sck},
#endif
{"mdnsname", "mDNS name", 14, &sonde.config.mdnsname},
#if FEATURE_SONDEHUB
/* Sondehub v2 settings */
{"", "Sondehub v2 settings", -5, NULL},
{"sondehub.active", "Sondehub reporting active", 0, &sonde.config.sondehub.active},
{"sondehub.chase", "Sondehub chase location active", 0, &sonde.config.sondehub.chase},
{"sondehub.host", "Sondehub host", 63, &sonde.config.sondehub.host},
/* SondeHub settings */
{"", "SondeHub settings", -5, NULL},
{"sondehub.active", "SondeHub reporting (0=disabled, 1=active)", 0, &sonde.config.sondehub.active},
{"sondehub.chase", "SondeHub location reporting (0=off, 1=fixed, 2=chase/GPS, 3=auto)", 0, &sonde.config.sondehub.chase},
{"sondehub.host", "SondeHub host (DO NOT CHANGE)", 63, &sonde.config.sondehub.host},
{"sondehub.callsign", "Callsign", 63, &sonde.config.sondehub.callsign},
{"sondehub.lat", "Latitude", 19, &sonde.config.sondehub.lat},
{"sondehub.lon", "Longitude", 19, &sonde.config.sondehub.lon},
{"sondehub.alt", "Altitude", 19, &sonde.config.sondehub.alt},
{"sondehub.antenna", "Antenna", 63, &sonde.config.sondehub.antenna},
{"sondehub.email", "Sondehub email", 63, &sonde.config.sondehub.email},
{"sondehub.lat", "Latitude (optional, required to show station on SondeHub Tracker)", -7, &sonde.config.sondehub.lat},
{"sondehub.lon", "Longitude (optional, required to show station on SondeHub Tracker)", -7, &sonde.config.sondehub.lon},
{"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.email", "SondeHub email (optional, only used to contact in case of upload errors)", 63, &sonde.config.sondehub.email},
#endif
};
const static int N_CONFIG = (sizeof(config_list) / sizeof(struct st_configitems));
@ -644,6 +695,10 @@ void addConfigNumEntry(char *ptr, int idx, const char *label, int *value) {
sprintf(ptr + strlen(ptr), "<tr><td>%s</td><td><input name=\"CFG%d\" type=\"text\" value=\"%d\"/></td></tr>\n",
label, idx, *value);
}
void addConfigDblEntry(char *ptr, int idx, const char *label, double *value) {
sprintf(ptr + strlen(ptr), "<tr><td>%s</td><td><input name=\"CFG%d\" type=\"text\" value=\"%f\"/></td></tr>\n",
label, idx, *value);
}
void addConfigButtonEntry(char *ptr, int idx, const char *label, int *value) {
int v = *value, ck = 0;
if (v == 255) v = -1;
@ -671,7 +726,7 @@ void addConfigHeading(char *ptr, const char *label) {
strcat(ptr, "</th></tr>\n");
}
void addConfigInt8List(char *ptr, int idx, const char *label, int8_t *list) {
sprintf(ptr + strlen(ptr), "<tr><td>%s", label);
sprintf(ptr + strlen(ptr), "<tr><td>%s using /screens%d.txt", label, Display::getScreenIndex(sonde.config.screenfile));
for (int i = 0; i < disp.nLayouts; i++) {
sprintf(ptr + strlen(ptr), "<br>%d=%s", i, disp.layouts[i].label);
}
@ -718,6 +773,9 @@ const char *createConfigForm() {
case -4:
addConfigButtonEntry(ptr, i, config_list[i].label, (int *)config_list[i].data);
break;
case -7: /* double for lat/lon */
addConfigDblEntry(ptr, i, config_list[i].label, (double *)config_list[i].data);
break;
default:
addConfigStringEntry(ptr, i, config_list[i].label, config_list[i].type, (char *)config_list[i].data);
break;
@ -1010,7 +1068,11 @@ const char *createUpdateForm(boolean run) {
if (run) {
strcat(ptr, "<p>Doing update, wait until reboot</p>");
} else {
sprintf(ptr + strlen(ptr), "<p>Currently installed: %s-%c%d</p>\n", version_id, SPIFFS_MAJOR + 'A' - 1, SPIFFS_MINOR);
strcat(ptr, "<p>Available master:: <iframe src=\"http://rdzsonde.mooo.com/master/update-info.html\" style=\"height:40px;width:400px\"></iframe><br>"
"Available devel: <iframe src=\"http://rdzsonde.mooo.com/devel/update-info.html\" style=\"height:40px;width:400px\"></iframe></p>");
strcat(ptr, "<input type=\"submit\" name=\"master\" value=\"Master-Update\"></input><br><input type=\"submit\" name=\"devel\" value=\"Devel-Update\">");
strcat(ptr, "<br><p>Note: If suffix is the same, update should work fully. If the number is different, update contains changes in the file system. A full re-flash is required to get all new features, but the update should not break anything. If the letter is different, a full re-flash is mandatory, update will not work</p>");
}
strcat(ptr, "</form></body></html>");
Serial.printf("Update form: size=%d bytes\n", strlen(message));
@ -1173,6 +1235,15 @@ void SetupAsyncServer() {
server.on("/status.html", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(200, "text/html", createStatusForm());
});
server.on("/live.json", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(200, "text/json", createLiveJson());
});
server.on("/livemap.html", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, "/livemap.html", String(), false, processor);
});
server.on("/livemap.js", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, "/livemap.js", String(), false, processor);
});
server.on("/update.html", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(200, "text/html", createUpdateForm(0));
});
@ -1218,7 +1289,7 @@ void SetupAsyncServer() {
server.on("/edit.html", HTTP_POST, [](AsyncWebServerRequest * request) {
const char *ret = handleEditPost(request);
if (ret == NULL)
request->send(200, "text/html", "<html><head>ERROR</head><body><p>Something went wrong. Uploaded file is empty.</p></body></hhtml>");
request->send(200, "text/html", "<html><head>ERROR</head><body><p>Something went wrong (probably ESP32 out of memory). Uploaded file is empty.</p></body></hhtml>");
else {
String f = request->getParam(0)->value();
request->redirect("/edit.html?file=" + f);
@ -1368,8 +1439,6 @@ void initTouch() {
}
}
char buffer[85];
MicroNMEA nmea(buffer, sizeof(buffer));
@ -1781,6 +1850,7 @@ int scanI2Cdevice(void)
extern int initlevels[40];
extern xSemaphoreHandle globalLock;
#ifdef ESP_MEM_DEBUG
typedef void (*esp_alloc_failed_hook_t) (size_t size, uint32_t caps, const char * function_name);
@ -1865,7 +1935,14 @@ void setup()
Serial.println("AXP192 Begin FAIL");
}
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON);
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON);
if(sonde.config.type == TYPE_M5_CORE2) {
// Display backlight on M5 Core2
axp.setPowerOutPut(AXP192_DCDC3, AXP202_ON);
axp.setDCDC3Voltage(3300);
} else {
// GPS on T-Beam, buzzer on M5 Core2
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON);
}
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
@ -1980,9 +2057,30 @@ void setup()
}
// == show initial values from config.txt ========================= //
#if 0
#if 1
if(sonde.config.type == TYPE_M5_CORE2) {
// Core2 uses Pin 38 for MISO
SPI.begin(18, 38, 23, -1);
} else {
SPI.begin();
}
//Set most significant bit first
SPI.setBitOrder(MSBFIRST);
//Divide the clock frequency
SPI.setClockDivider(SPI_CLOCK_DIV2);
//Set data mode
SPI.setDataMode(SPI_MODE0);
sx1278.setup(globalLock);
uint8_t state = 2;
int i=0;
while(++i<3) {
delay(500);
// == check the radio chip by setting default frequency =========== //
if (rs41.setFrequency(402700000) == 0) {
sx1278.ON();
if (sx1278.setFrequency(402700000) == 0) {
Serial.println(F("Setting freq: SUCCESS "));
} else {
Serial.println(F("Setting freq: ERROR "));
@ -1991,6 +2089,7 @@ void setup()
Serial.print("Frequency set to ");
Serial.println(f);
// == check the radio chip by setting default frequency =========== //
}
#endif
//sx1278.setLNAGain(-48);
@ -2064,9 +2163,12 @@ void enterMode(int mode) {
// trigger activation of background task
// currentSonde should be set before enterMode()
rxtask.activate = ACT_SONDE(sonde.currentSonde);
//
Serial.println("clearing and updating display");
sonde.clearDisplay();
sonde.updateDisplay();
}
printf("enterMode ok\n");
}
static char text[40];
@ -2233,7 +2335,9 @@ void loopDecoder() {
}
#endif
} else {
#if FEATURE_SONDEHUB
sondehub_finish_data(&shclient, s, &sonde.config.sondehub);
#endif
}
// always send data, even if not valid....
if (rdzclient.connected()) {
@ -2435,8 +2539,8 @@ void enableNetwork(bool enable) {
#endif
#if FEATURE_SONDEHUB
if (sonde.config.sondehub.active && wifi_state != WIFI_APMODE) {
time_last_update = millis() + 1000; /* force sending update */
sondehub_station_update(&shclient, &sonde.config.sondehub);
time_last_update = millis();
}
#endif
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
@ -2445,6 +2549,7 @@ void enableNetwork(bool enable) {
MDNS.end();
connected = false;
}
Serial.println("enableNetwork done");
}
// Events used only for debug output right now
@ -2686,7 +2791,7 @@ void loopTouchCalib() {
// 2: access point mode in background. directly start initial mode (spectrum or scanner)
// 3: traditional sync. WifiScan. Tries to connect to a network, in case of failure activates AP.
// Mode 3 shows more debug information on serial port and display.
#define MAXWIFIDELAY 20
#define MAXWIFIDELAY 40
static const char* _scan[2] = {"/", "\\"};
void loopWifiScan() {
if (sonde.config.wifi == 0) { // no Wifi
@ -2701,6 +2806,7 @@ void loopWifiScan() {
}
if (sonde.config.wifi == 2) { // AP mode, setup in background
startAP();
enableNetwork(true);
initialMode();
return;
}
@ -2741,17 +2847,6 @@ void loopWifiScan() {
while (WiFi.status() != WL_CONNECTED && cnt < MAXWIFIDELAY) {
delay(500);
Serial.print(".");
#if 0
if (cnt == 5) {
// my FritzBox needs this for reconnecting
WiFi.disconnect(true);
delay(500);
WiFi.begin(fetchWifiSSID(index), fetchWifiPw(index));
Serial.print("Reconnecting to: "); Serial.print(fetchWifiSSID(index));
Serial.print(" with password "); Serial.println(fetchWifiPw(index));
delay(500);
}
#endif
disp.rdis->drawString(15 * dispxs, lastl + dispys, _scan[cnt & 1]);
cnt++;
}
@ -3005,17 +3100,8 @@ void loop() {
#if FEATURE_SONDEHUB
if (sonde.config.sondehub.active) {
unsigned long time_now = millis();
// time_delta will be correct, even if time_now overflows
unsigned long time_delta = time_now - time_last_update;
if ((sonde.config.sondehub.chase == 0) && (time_delta >= SONDEHUB_STATION_UPDATE_TIME) && (wifi_state != WIFI_APMODE)) { // 60 min
sondehub_station_update(&shclient, &sonde.config.sondehub);
time_last_update = time_now;
}
else if ((sonde.config.sondehub.chase == 1) && (time_delta >= SONDEHUB_MOBILE_STATION_UPDATE_TIME) && (wifi_state != WIFI_APMODE)) { // 30 sec
sondehub_station_update(&shclient, &sonde.config.sondehub);
time_last_update = time_now;
}
// interval check moved to sondehub_station_update to avoid having to calculate distance in auto mode twice
sondehub_station_update(&shclient, &sonde.config.sondehub);
}
#endif
}
@ -3025,12 +3111,33 @@ void loop() {
/*
Update station data to the sondehub v2 DB
*/
/* which_pos: 0=none, 1=fixed, 2=gps */
void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
#define STATION_DATA_LEN 300
char data[STATION_DATA_LEN];
char *w;
// If there is no connection to some WiFi AP, we cannot upload any data at all....
if ( wifi_state != WIFI_CONNECTED ) return;
unsigned long time_now = millis();
// time_delta will be correct, even if time_now overflows
unsigned long time_delta = time_now - time_last_update;
int chase = conf->chase;
// automatically decided if CHASE or FIXED mode is used (for config AUTO)
if (chase == SH_LOC_AUTO) {
if (SH_LOC_AUTO_IS_CHASE) chase = SH_LOC_CHASE; else chase = SH_LOC_FIXED;
}
// Use 30sec update time in chase mode, 60 min in station mode.
unsigned long update_time = (chase == SH_LOC_CHASE) ? SONDEHUB_MOBILE_STATION_UPDATE_TIME : SONDEHUB_STATION_UPDATE_TIME;
// If it is not yet time to send another update. do nothing....
if ( (time_delta <= update_time) ) return;
Serial.println("sondehub_station_update()");
time_last_update = time_now;
if (!client->connected()) {
if (!client->connect(conf->host, 80)) {
@ -3040,7 +3147,7 @@ void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
}
w = data;
memset(w, 0, STATION_DATA_LEN);
// not necessary... memset(w, 0, STATION_DATA_LEN);
sprintf(w,
"{"
@ -3050,31 +3157,35 @@ void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
"\"uploader_contact_email\": \"%s\",",
version_name, version_id, conf->callsign, conf->email);
w += strlen(w);
if ((conf->chase == 0) && (conf->lat[0] != '\0') && (conf->lon[0] != '\0')) {
if (conf->alt[0] != '\0') {
// We send GPS position: (a) in CHASE mode, (b) in FIXED mode if no fixed location has been specified in config
if (chase == SH_LOC_CHASE || (chase == SH_LOC_FIXED && (isnan(conf->lat) || isnan(conf->lon)) ) ) {
if (gpsPos.valid && gpsPos.lat != 0 && gpsPos.lon != 0) {
sprintf(w,
"\"uploader_position\": [%s,%s,%s],"
"\"uploader_antenna\": \"%s\""
"\"uploader_position\": [%.6f,%.6f,%d],"
"\"uploader_antenna\": \"%s\","
"\"mobile\": true"
"}",
conf->lat, conf->lon, conf->alt, conf->antenna);
} else {
sprintf(w,
"\"uploader_position\": [%s,%s,null],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->antenna);
gpsPos.lat, gpsPos.lon, gpsPos.alt, conf->antenna);
}
}
else if (gpsPos.valid && gpsPos.lat != 0 && gpsPos.lon != 0) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%d],"
"\"uploader_antenna\": \"%s\","
"\"mobile\": true"
"}",
gpsPos.lat, gpsPos.lon, gpsPos.alt, conf->antenna);
// 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],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->alt[0] ? conf->alt : "null", conf->antenna);
}
}
else {
return;
// otherwise (in SH_LOC_NONE mode) we dont include any position info
sprintf(w,
"\"uploader_position\": [null,null,null],"
"\"uploader_antenna\": \"%s\""
"}",
conf->antenna);
}
client->println("PUT /listeners HTTP/1.1");
@ -3092,7 +3203,7 @@ void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
String response = client->readString();
Serial.println(response);
Serial.println("Response done...");
//client->stop();r
//client->stop();
}
/*
@ -3118,7 +3229,7 @@ const char *dfmSubtypeStrSH[16] = { NULL, NULL, NULL, NULL, NULL, NULL,
// in hours.... max allowed diff UTC <-> sonde time
#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) {
Serial.println("sondehub_send_data()");
Serial.printf("shState = %d\n", shState);
@ -3129,6 +3240,10 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
char rs_msg[MSG_SIZE];
char *w;
struct tm ts;
uint8_t realtype = s->type;
// config setting M10 and M20 will both decode both types, so use the real type that was decoded
if(TYPE_IS_METEO(realtype)) { realtype = s->subtype==1 ? STYPE_M10 : STYPE_M20; }
// For DFM, s->time is data from subframe DAT8 (gps date/hh/mm), and sec is from DAT1 (gps sec/usec)
// For all others, sec should always be 0 and time the exact time in seconds
time_t t = s->time + s->sec;
@ -3158,7 +3273,7 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
if (((int)s->lat == 0) && ((int)s->lon == 0)) return; // Sometimes these values are zeroes. Don't send those to the sondehub
if ((int)s->alt > 50000) return; // If alt is too high don't send to SondeHub
// M20 data does not include #sat information
if ( s->type != STYPE_M20 && (int)s->sats < 4) return; // If not enough sats don't send to SondeHub
if ( realtype != STYPE_M20 && (int)s->sats < 4) return; // If not enough sats don't send to SondeHub
// If not connected to sondehub, try reconnecting.
// TODO: do this outside of main loop
@ -3185,7 +3300,7 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
// DFM uses UTC. Most of the other radiosondes use GPS time
// SondeHub expect datetime to be the same time sytem as the sonde transmits as time stamp
if ( s->type == STYPE_RS41 || s->type == STYPE_RS92 || s->type == STYPE_M20 ) {
if ( realtype == STYPE_RS41 || realtype == STYPE_RS92 || realtype == STYPE_M20 ) {
t += 18; // convert back to GPS time from UTC time +18s
}
@ -3213,25 +3328,25 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
"\"rssi\": %.1f,",
version_name, version_id, conf->callsign,
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,
manufacturer_string[s->type], s->ser,
manufacturer_string[realtype], s->ser,
ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec + s->sec,
(float)s->lat, (float)s->lon, (float)s->alt, (float)s->freq, (float)s->hs, (float)s->vs,
(float)s->dir, -((float)s->rssi / 2)
);
w += strlen(w);
if (s->type != STYPE_M20) {
if (realtype != STYPE_M20) {
sprintf(w, "\"sats\": %d,", (int)s->sats);
w += strlen(w);
}
if ( TYPE_IS_DFM(s->type) || TYPE_IS_METEO(s->type) || s->type == STYPE_MP3H ) {
if ( TYPE_IS_DFM(realtype) || TYPE_IS_METEO(realtype) || realtype == STYPE_MP3H ) {
// send frame as gps timestamp for these sonde, identical to autorx
// For M10, this is real GPS time (seconds since Jqn 6 1980, without adjusting for leap seconds)
// DFM and MP3H send real UTC (with leap seconds considered), so for them the frame number actually
// is gps time plus number of leap seconds since the beginning of GPS time.
int frame = (int)(t - 315964800);
if (s->type == STYPE_M10) {
if (realtype == STYPE_M10) {
frame += 18;
};
sprintf(w, "\"frame\": %d,", frame);
@ -3240,7 +3355,7 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
}
w += strlen(w);
sprintf(w, "\"type\": \"%s\",", sondeTypeStrSH[s->type]);
sprintf(w, "\"type\": \"%s\",", sondeTypeStrSH[realtype]);
w += strlen(w);
/* if there is a subtype (DFM only) */
@ -3261,17 +3376,17 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
w += strlen(w);
}
if ((conf->chase == 0) && (conf->lat[0] != '\0') && (conf->lon[0] != '\0')) {
if ((conf->chase == 0) && (!isnan(conf->lat)) && (!isnan(conf->lon))) {
if (conf->alt[0] != '\0') {
sprintf(w,
"\"uploader_position\": [%s,%s,%s],"
"\"uploader_position\": [%.6f,%.6f,%s],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->alt, conf->antenna
);
} else {
sprintf(w,
"\"uploader_position\": [%s,%s,null],"
"\"uploader_position\": [%.6f,%.6f,null],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->antenna
@ -3312,7 +3427,7 @@ void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
//Serial.println(response);
}
void sondehub_finish_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
void sondehub_finish_data(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf) {
// If there is an "old" pending collection of JSON data sets, send it even if no now data is received
if (shState == SH_CONN_APPENDING) {
time_t now;
@ -3325,7 +3440,7 @@ void sondehub_finish_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *
}
}
void sondehub_send_header(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
void sondehub_send_header(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf) {
Serial.print("PUT /sondes/telemetry HTTP/1.1\r\n"
"Host: ");
Serial.println(conf->host);
@ -3339,7 +3454,7 @@ void sondehub_send_header(WiFiClient *client, SondeInfo *s, struct st_sondehub *
"Content-Type: application/json\r\n"
"Transfer-Encoding: chunked\r\n");
}
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
client->printf("%x\r\n", chunklen + 1);
client->write(first ? "[" : ",", 1);
@ -3347,11 +3462,11 @@ void sondehub_send_next(WiFiClient *client, SondeInfo *s, struct st_sondehub *co
client->print("\r\n");
Serial.printf("%x\r\n", chunklen + 1);
Serial.write(first ? "[" : ",", 1);
Serial.write(chunk, chunklen);
Serial.write((const uint8_t *)(first ? "[" : ","), 1);
Serial.write((const uint8_t *)chunk, chunklen);
Serial.print("\r\n");
}
void sondehub_send_last(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
void sondehub_send_last(WiFiClient * client, SondeInfo * s, struct st_sondehub * conf) {
// last chunk. just the closing "]" of the json request
client->printf("1\r\n]\r\n0\r\n\r\n");
Serial.printf("1\r\n]\r\n0\r\n\r\n");

Wyświetl plik

@ -25,11 +25,9 @@
#tft_rs=2
#tft_cs=0
tft_orient=1
#tft_modeflip=0
#tft_spifreq=40000000
#gps_rxd=-1
#gps_txd=-1
# Show AFC value (for RS41 and M10/M20, maybe also DFM, but not useful for RS92)
showafc=1
# Frequency correction, in Hz
# freqofs=0
#-------------------------------#
@ -42,8 +40,8 @@ wifi=3
# TCP/IP KISS TNC in port 14590 for APRSdroid (0=disabled, 1=enabled)
kisstnc.active = 1
# which screens file to use (0: screens.txt, i>0: screens${i}.txt
# 0: old version; 1: for OLED, 2: for TFT; 3: for TFT (portrait mode)
# which screens file to use (0: automated selection based on display type and orientation, i>0: screens${i}.txt
# predefined: 1: for OLED, 2: for ILI9225; 3: for ILI9225 (portrait mode); 4: for ILI9431; 5: for ILI9431 (portrait mode)
# screenfile=2
# display configuration. List of "displays"
# first entry: "Scanner" display

4
RX_FSK/data/index.html 100644 → 100755
Wyświetl plik

@ -15,6 +15,7 @@
<button class="tablinks" onclick="selTab(event,'QRG')" id="defaultTab">QRG</button>
<button class="tablinks" onclick="selTab(event,'WiFi')">WiFi</button>
<button class="tablinks" onclick="selTab(event,'Data')">Data</button>
<button class="tablinks" onclick="document.location.href='livemap.html'">LiveMap</button>
<button class="tablinks" onclick="selTab(event,'Map')">Map</button>
<button class="tablinks" onclick="selTab(event,'Config')">Config</button>
<button class="tablinks" onclick="selTab(event,'Control')">Control</button>
@ -51,6 +52,9 @@
%VERSION_NAME%<br>
Copyright &copy; 2019-2021 by Hansi Reiser, DL9RDZ<br>
(version %VERSION_ID%)<br><br>
<a href="/update.html">Check for update (requires TTGO internet connection via WiFi)</a><br><br>
with contributions by Vigor and Xavier (M20 support),
<a href="https://github.com/LukePrior">Luke Prior</a> and <a href="https://github.com/oh3bsg">OH3BSG</a> (SondeHub support),
<a href="https://www.dl2mf.de/" target="_blank">Meinhard Guenther, DL2MF</a>,

Wyświetl plik

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>rdzTTGOSonde Server LiveMap</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.marker.slideto@0.2.0/Leaflet.Marker.SlideTo.js"></script>
<script src="livemap.js"></script>
</head>
<body>
<div id="map"></div>
</body>
</html>

Wyświetl plik

@ -0,0 +1,478 @@
$(document).ready(function(){
var map = L.map('map', { attributionControl: false, zoomControl: false });
map.on('mousedown touchstart',function () { follow=false; });
L.control.scale().addTo(map);
L.control.attribution({prefix:false}).addTo(map);
var osm = L.tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
attribution: '<div><a href="https://leafletjs.com/">Leaflet</a> &middot; Map: <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a></div>',
minZoom: 1,
maxZoom: 19
});
var esri = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: '<div><a href="https://leafletjs.com/">Leaflet</a> &middot; Map: <a href="https://www.esri.com/">Esri</a> &middot; Earthstar Geographics</div>',
minZoom: 1,
maxZoom: 20
});
var basemap = 'osm';
osm.addTo(map);
basemap_change = function () {
if (basemap == 'osm') {
map.removeLayer(osm);
map.addLayer(esri);
basemap = 'esri';
} else {
map.removeLayer(esri);
map.addLayer(osm);
basemap = 'osm';
}
};
map.setView([51.163361,10.447683], 5); // Mitte DE
var reddot = '<span class="ldot rbg"></span> ';
var yellowdot = '<span class="ldot ybg"></span> ';
var greendot = '<span class="ldot gbg"></span> ';
$('#map .leaflet-control-container').append(L.DomUtil.create('div', 'leaflet-top leaflet-center leaflet-header'));
var header = '';
header += '<div id="sonde_main"><b>rdzTTGOSonde LiveMap</b><br />🎈 <b><span id="sonde_id"></span> - <span id="sonde_freq"></span> MHz - <span id="sonde_type"></span></b></div>';
header += '<div id="sonde_detail"><span id="sonde_alt"></span>m | <span id="sonde_climb"></span>m/s | <span id="sonde_speed"></span>km/h</div>';
header += '<div id="sonde_status"><small><span id="sonde_statbar"></span></small></div>';
header += '<div id="settings"><br /><b>Prediction-Settings</b><br />';
header += '<label for="burst">Burst at:</label><input type="text" id="burst" maxlength="5" value="..." /> m<br />';
header += '<label for="overwrite_descend">Descending:</label><input type="text" id="overwrite_descend" maxlength="2" value="..." /> m/s<br />';
header += '<label for="overwrite_descend_till">Use this descending until:</label><input type="text" id="overwrite_descend_till" maxlength="5" value="..." /> m<br />';
header += '<small>after the transmitted descend will be used</small>';
header += '<div id="submit"><input type="button" value="save" onclick="settings_save();"/>&nbsp;&nbsp;&nbsp;<input type="button" id="submit" value="reset" onclick="settings_reset();"/></div>';
header += '</div>';
$('.leaflet-header').append(header);
$('#map .leaflet-control-container').append(L.DomUtil.create('div', 'leaflet-bottom leaflet-center leaflet-footer'));
var footer = '';
footer += '<div id="gps_main"><b>Direction: </b><span class="gps_dir">...</span>°<br /><b>Distance: </b><span class="gps_dist">...</span>m</div>';
$('.leaflet-footer').append(footer);
var statbar = '';
headtxt = function(data,stat) {
//var staticon = (stat == '1')?'🟢':'🟡';
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) {
$('#sonde_id').html(data.id);
$('#sonde_alt').html(data.alt);
$('#sonde_climb').html(data.climb);
$('#sonde_speed').html( mr(data.speed * 3.6 * 10) / 10 );
$('#sonde_detail').show();
} else {
$('#sonde_id').html(data.launchsite.trim());
$('#sonde_detail').hide();
}
$('#sonde_freq').html(data.freq);
$('#sonde_type').html(data.type);
$('#sonde_statbar').html('&nbsp;'+statbar);
};
map.addControl(new L.Control.Button([ { position: 'topleft', text: '🔙', href: 'index.html' } ]));
L.control.zoom({ position:'topleft' }).addTo(map);
map.addControl(new L.Control.Button([ { position: 'topleft', text: '🗺️', href: 'javascript:basemap_change();' } ]));
map.addControl(new L.Control.Button([ { position: 'topright', id: "status", text: '🔴', href: 'javascript:get_data();' } ]));
map.addControl(new L.Control.Button([
{ position:'topright', text: '🎈', href: 'javascript:show(marker,\'marker\');' },
{ text: '〰️', href: 'javascript:show_line();' },
{ text: '💥', href: 'javascript:show(marker_burst,\'burst\');' },
{ text: '🎯', href: 'javascript:show(marker_landing,\'landing\');' }
]));
map.addControl(new L.Control.Button([ { position:'topright', text: '⚙️', href: 'javascript:show_settings();' } ]));
show = function(e,p) {
if (p == 'landing') { get_predict(last_data); }
if (e) {
map.closePopup();
map.setView(map._layers[e._leaflet_id].getLatLng());
map._layers[e._leaflet_id].openPopup();
follow = p;
}
};
getTwoBounds = function (a,b) {
var sW = new L.LatLng((a._southWest.lat > b._southWest.lat)?b._southWest.lat:a._southWest.lat, (a._southWest.lng > b._southWest.lng)?b._southWest.lng:a._southWest.lng);
var nE = new L.LatLng((a._northEast.lat < b._northEast.lat)?b._northEast.lat:a._northEast.lat, (a._northEast.lng < b._northEast.lng)?b._northEast.lng:a._northEast.lng);
return new L.LatLngBounds(sW, nE);
};
show_line = function() {
$('.i_position, .i_landing').remove();
map.closePopup();
if (line._latlngs.length != 0 && line_predict._latlngs.length != 0) {
map.fitBounds(getTwoBounds(line.getBounds(),line_predict.getBounds()));
} else if (line._latlngs.length != 0) {
map.fitBounds(line.getBounds());
} else if (line_predict._latlngs.length != 0) {
map.fitBounds(line_predict.getBounds());
}
};
last_data = false;
follow = 'marker';
marker_landing = false;
icon_landing = L.divIcon({className: 'leaflet-landing'});
dots_predict = [];
line_predict = L.polyline(dots_predict,{color: 'yellow'}).addTo(map);
marker_burst = false;
icon_burst = L.divIcon({className: 'leaflet-burst'});
marker = false;
dots = [];
line = L.polyline(dots).addTo(map);
draw = function(data) {
var stat;
if (data.id) {
if ((data.lat != '0.000000' && data.lon != '0.000000') && (JSON.stringify(data) != JSON.stringify(last_data)) ) {
var location = [data.lat,data.lon,data.alt];
if (!marker) {
map.setView(location, 14);
marker = L.marker(location).addTo(map)
.bindPopup(poptxt('position',data),{closeOnClick:false, autoPan:false}).openPopup();
get_predict(data);
} else {
marker.slideTo(location, {
duration: 500,
keepAtCenter: (follow=='marker')?true:false
})
.setPopupContent(poptxt('position',data));
if (last_data.id != data.id) {
storage_remove();
dots = [];
get_predict(data);
}
}
dots.push(location);
line.setLatLngs(dots);
storage_write(data);
//$('#status').html('🟢');
$('#status').html(greendot);
stat = 1;
} else {
//$('#status').html('🟡');
$('#status').html(yellowdot);
stat = 0;
}
headtxt(data,stat);
last_data = data;
} else {
//$('#status').html('🟡');
$('#status').html(yellowdot);
headtxt(data,0);
}
};
marker_gps = false;
icon_gps = L.divIcon({className: 'leaflet-gps'});
circ_gps = false;
gps = function(e) {
gps_location = [e.lat/1000000,e.lon/1000000];
gps_accuracy = e.hdop*2;
if (last_data && last_data.lat != '0.000000') {
if ($('.leaflet-footer').css('display') == 'none') { $('.leaflet-footer').show(); }
var distance = Math.round(map.distance(gps_location,[last_data.lat, last_data.lon]));
distance = (distance > 1000)?(distance / 1000) + 'k':distance;
$('.leaflet-footer .gps_dist').html(distance);
$('.leaflet-footer .gps_dir').html( bearing(gps_location,[last_data.lat, last_data.lon]) );
}
if (!marker_gps) {
map.addControl(new L.Control.Button([{ position: 'topleft', text: '🛰️', href: 'javascript:show(marker_gps,\'gps\');' }]));
marker_gps = L.marker(gps_location,{icon:icon_gps}).addTo(map)
.bindPopup(poptxt('gps',e),{closeOnClick:false, autoPan:false});
circ_gps = L.circle(gps_location, gps_accuracy).addTo(map);
} else {
marker_gps.slideTo(gps_location, {
duration: 500,
keepAtCenter: (follow=='gps')?true:false
})
.setPopupContent(poptxt('gps',e));
circ_gps.slideTo(gps_location, { duration: 500 });
circ_gps.setRadius(gps_accuracy);
}
};
get_data = function() {
//$('#status').html('🔴');
$('#status').html(reddot);
$.ajax({url: 'live.json', success: (function( data ) {
if (typeof data != "object") { data = $.parseJSON(data); }
if (data.sonde) {
draw(data.sonde);
} else {
//setTimeout(function() {$('#status').html('🟡');},100);
setTimeout(function() {$('#status').html(yellowdot);},100);
}
if (data.gps) {
gps(data.gps);
}
}),
timeout: 1000}
);
};
storage = (typeof(Storage) !== "undefined")?true:false;
settings_std = {
burst: 32500,
overwrite_descend: 6,
overwrite_descend_till: 12000
};
settings_read = function() {
if (storage) {
if (sessionStorage.settings) {
return JSON.parse(sessionStorage.settings);
} else {
settings_write(settings_std);
return settings_std;
}
} else {
return settings_std;
}
return false;
};
settings_write = function (data) {
if (storage) {
sessionStorage.settings = JSON.stringify(data);
settings = data;
}
};
settings = settings_read();
settings_save = function() {
settings.burst = parseInt($('#settings #burst').val());
settings.overwrite_descend = parseInt($('#settings #overwrite_descend').val());
settings.overwrite_descend_till = parseInt($('#settings #overwrite_descend_till').val());
if (Number.isInteger(settings.burst) && Number.isInteger(settings.overwrite_descend) && Number.isInteger(settings.overwrite_descend_till)) {
settings_write(settings);
$("#settings").slideUp();
get_predict(last_data);
} else {
alert('Error: only numeric values allowed!');
}
};
settings_reset = function() {
if (confirm('Reset to default?')) {
settings_write(settings_std);
show_settings();
}
};
show_settings = function() {
$('#settings #burst').val(settings.burst);
$('#settings #overwrite_descend').val(settings.overwrite_descend);
$('#settings #overwrite_descend_till').val(settings.overwrite_descend_till);
$("#settings").slideToggle();
};
predictor = false;
get_predict = function(data) {
if (!data) { return; }
var ascent = (data.climb > 0)? data.climb : 15;
var descent = (data.climb > 0)? settings.overwrite_descend : data.climb * -1;
var burst;
if (data.climb > 0) {
burst = (data.alt > settings.burst )?data.alt + 100 : settings.burst;
} else {
burst = parseInt(data.alt) + 7;
if (data.alt > settings.overwrite_descend_till ) { descent = settings.overwrite_descend; }
}
var m = new Date();
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='+data.lon;
url += '&launch_altitude='+data.alt + '&launch_datetime='+datetime;
url += '&ascent_rate='+ascent + '&burst_altitude=' + burst + '&descent_rate='+descent;
$.getJSON(url, function( prediction ) {
draw_predict(prediction,data);
});
};
draw_predict = function(prediction,data) {
var ascending = prediction.prediction[0].trajectory;
var highest = ascending[ascending.length-1];
var highest_location = [highest.latitude,highest.longitude];
var descending = prediction.prediction[1].trajectory;
var landing = descending[descending.length-1];
var landing_location = [landing.latitude,landing.longitude];
if (!marker_landing) {
marker_landing = L.marker(landing_location,{icon: icon_landing}).addTo(map)
.bindPopup(poptxt('landing',landing),{closeOnClick:false, autoPan:false});
} else {
marker_landing.slideTo(landing_location, {
duration: 500,
keepAtCenter: (follow=='landing')?true:false
})
.setPopupContent(poptxt('landing',landing));
}
dots_predict=[];
if (data.climb > 0) {
ascending.forEach(p => dots_predict.push([p.latitude,p.longitude]));
if (!marker_burst) {
marker_burst = L.marker(highest_location,{icon:icon_burst}).addTo(map).bindPopup(poptxt('burst',highest),{closeOnClick:false, autoPan:false});
} else {
marker_burst.slideTo(highest_location, {
duration: 500,
keepAtCenter: (follow=='burst')?true:false
}).setPopupContent(poptxt('burst',highest));
}
}
descending.forEach(p => dots_predict.push([p.latitude,p.longitude]));
line_predict.setLatLngs(dots_predict);
if (data.climb > 0) {
predictor_time = 5 * 60; // ascending, every 5 min
} else if (data.climb < 0 && data.alt > 5000) {
predictor_time = 2 * 60; // descending, above 5km, every 2 min
} else {
predictor_time = 30; // descending, below 5km, every 30 sec
}
clearTimeout(predictor);
predictor = setTimeout(function() {get_predict(last_data);}, predictor_time*1000);
};
poptxt = function(t,i) {
var lat_input = (i.id)?i.lat:i.latitude;
var lon_input = (i.id)?i.lon:i.longitude;
var lat = Math.round(lat_input * 1000000) / 1000000;
var lon = Math.round(lon_input * 1000000) / 1000000;
var add =
'<br /><b>Position:</b> '+lat+', '+lon+'<br />'+
'<b>Open:</b> <a href="https://www.google.de/maps/?q='+lat+', '+lon+'" target="_blank">GMaps</a> | <a href="https://www.openstreetmap.org/?mlat='+lat+'&mlon='+lon+'&zoom=15" target="_blank">OSM</a> | <a href="mapsme://map?ll='+lat+','+lon+'">Maps.me</a>';
if (t == 'position') { return '<div class="i_position"><b>🎈 '+i.id+'</b>'+add+'</div>'; }
if (t == 'burst') { return '<div class="i_burst"><b>💥 Predicted Burst:</b><br />'+fd(i.datetime)+' in '+mr(i.altitude)+'m'+add+'</div>'; }
if (t == 'highest') { return '<div class="i_burst"><b>💥 Burst:</b> '+mr(i.altitude)+'m'+add+'</div>';}
if (t == 'landing') { return '<div class="i_landing"><b>🎯 Predicted Landing:</b><br />'+fd(i.datetime)+' at '+mr(i.altitude)+'m'+add+'</div>'; }
if (t == 'gps') { return '<div class="i_gps">Position: '+(i.lat/1000000)+','+(i.lon/1000000)+'<br />Altitude: '+mr(i.alt/1000)+'m<br />Speed: '+mr(i.speed/1000 * 1.852 * 10)/10+'km/h '+mr(i.dir/1000)+'°<br />Sat: '+i.sat+' Hdop:'+(i.hdop/10)+'</div>'; }
};
fd = function(date) {
var d = new Date(Date.parse(date));
return az(d.getUTCHours()) +':'+ az(d.getUTCMinutes())+' UTC';
};
az = function(n) { return (n<10)?'0'+n:n; };
mr = function(n) { return Math.round(n); };
storage = (typeof(Storage) !== "undefined")?true:false;
storage_write = function (data) {
if (storage) {
if (sessionStorage.sonde) {
storage_data = JSON.parse(sessionStorage.sonde);
} else {
storage_data = [];
}
if (JSON.stringify(data) != JSON.stringify(storage_data[storage_data.length - 1])) {
storage_data.push(data);
sessionStorage.sonde = JSON.stringify(storage_data);
}
}
};
storage_read = function() {
if (storage) {
if (sessionStorage.sonde) {
storage_data = JSON.parse(sessionStorage.sonde);
return storage_data;
}
}
return false;
};
storage_remove = function() {
sessionStorage.removeItem('sonde');
};
session_storage = storage_read();
if (session_storage) {
session_storage.forEach(function(d) {
dots.push([d.lat,d.lon,d.alt]);
session_storage_last = d;
});
draw(session_storage_last);
}
setInterval(get_data,1000);
});
L.Control.Button = L.Control.extend({
onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
options = this.options;
Object.keys(options).forEach(function(key) {
this.link = L.DomUtil.create('a', '', container);
this.link.text = options[key].text;
this.link.href = options[key].href;
this.link.id = options[key].id;
});
this.options.position = this.options[0].position;
return container;
}
});
// https://github.com/makinacorpus/Leaflet.GeometryUtil/blob/master/src/leaflet.geometryutil.js#L682
// modified to fit
function bearing(latlng1, latlng2) {
var rad = Math.PI / 180,
lat1 = latlng1[0] * rad,
lat2 = latlng2[0] * rad,
lon1 = latlng1[1] * rad,
lon2 = latlng2[1] * rad,
y = Math.sin(lon2 - lon1) * Math.cos(lat2),
x = Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360;
bearing = bearing < 0 ? bearing-360 : bearing;
return Math.round(bearing);
}

Wyświetl plik

@ -0,0 +1,574 @@
## screens2.txt: TFT display (landscape)
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg) [not yet implemented, maybe some day in future]
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
############
# Scan display for large 2" TFT dispaly
@ScannerTFT
scale=30,18
timer=-1,0,0
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
fonts=5,6
0,0=XScan
0,5,-3=S#:
0,9,5.5=T
3,0=F MHz
5,0,16=S
7,5=n
############
@MainTFT
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
color=FFD700
0,0,10.5=Is
color=0000FF
0,11,-5.5=f
1,0,4=t
1,10.5,-6=c
color=00ff00
2,0,7=L
4,0,7=O
color=FFA500
2,9.5,-7=A
3,9.5,-7=vm/s
color=AA5522
4,9.5,-7=hkkm/h
color=FFFFFF
6,2=r
6.3,10=Q4
7,0=xd=
7,2,6=gD
7,12=gI
############
@PeilungTFT
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
color=ffff00,000033
color=bbbbbb,000000
0,2=xN Top:
0,8=xCourse Top:
color=ffff00,000033
1,0=g0NCS,48,ffff00,000044,6,33ff33,5,eeaa00
1,8=g0CCS,48,ffff00,000044,6,55ff55,5,eeaa00
color=ffffff,000000
6,0=xDirection:
6,8,4=gI
7,0=xCOG:
7,4,4=gC
7,8=xturn:
7,12,4=gB
############
@GPSdataTFT
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=xOn-board GPS:
1,0,8=gA
2,0,8=gO
3,0,8=gH
4,0,8=gC
5,0=xGPS vs Sonde:
6,0,8=gD
7,0,8=gI
7,8,8=gB
############
@BatteryTFT
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=xBattery status:
0,14=bS
1,0=xBatt:
1,5,5=bVV
2,0,16=bCmA(charging)
3,0,16=bDmA(discharging)
4.4,0=xUSB:
4.4,5,5=bUV
5.4,0,10=bImA
6.4,0=xTemp:
6.4,5,5=bT C
### Alternative display layouts based on https://gist.github.com/bazjo
# Scan display for large 2" TFT dispaly
@Scan.TFT.Bazjo
timer=-1,0,0
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
scale=11,10
fonts=0,2
color=e0e0e0
#Row 1
0.5,0=XScanning...
#Row 2
3,0=xIndex
4,0,8=S/
3,9=xSite
4,9=S
#Row 3
6,0=xType
7,0,6=T
6,9=xFrequency
7,9=F
#Row 4
9,0=xWeb UI IP
10,0=N
#Row 5
#Footer
color=6C757D
15,0=xScan Mode
15,18=bVV
############
@Decode/General.TFT.Bazjo
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
scale=11,10
fonts=0,2
#Row 1
color=996A06
0,0=xSerial
0,5=t
color=FFB10B
1,0=Is
color=996A06
0,11=xFreq.
0,16=c
color=FFB10B
1,11=F
#Row 2
color=3C5C99
3,0=xLatitude
color=639AFF
4,0=L
color=3C5C99
3,11=xLongitude
color=639AFF
4,11=O
#Row 3
color=3C5C99
6,0=xHoriz. Speed
color=639AFF
7,0=Hkkm/h
color=3C5C99
6,11=xVert. Speed
color=639AFF
7,11=Vm/s
#Row 4
color=99004A
9,0=xAltitude
color=FF007B
10,0=A
color=99004A
9,11=xBearing
color=FF007B
10,11=GB
#Row 5
color=06998E
12,0=xRSSI
color=0AFFEF
13,0=R
color=06998E
12,11=xHistory
color=0AFFEF
13.5,11=Q4
#Footer
color=6C757D
15,0=xDecode Mode / General View
15,18=bVV
############
@Decode/Battery.TFT.Bazjo
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
scale=11,10
fonts=0,2
#Row 1
color=99001F
0,0=xBattery Status
0,11=xBattery Voltage
color=FF0035
1,0=BS
1,11=BVV
#Row 2
color=99001F
3,0=xCharge Current
3,11=xDischarge Current
color=FF0035
4,0=BCmA
4,11=BDmA
#Row 3
color=99001F
6,0=xUSB Voltage
6,11=xUSB Current
color=FF0035
7,0=BUV
7,11=BImA
#Row 4
color=99001F
9,0=xIC Temperature
#9,11=xKey
color=FF0035
10,0=BTC
#10,11=XValue
#Row 5
#12,0=xKey
#12,11=xKey
#13,0=XValue
#13,11=XValue
#Footer
color=99001F
15,0=xDecode Mode/Battery View
15,18=bVV
# based on https://github.com/puspis/rdz_ttgo_sonde
##########
@Scanner.Puspis
timer=-1,0,4
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
scale=13,10
fonts=0,1
#Row 1
color=90EE90
0.5,3=XFREQUENCY SCAN
#Row 2
color=00FF00
3,0=xMEMORY
3,9=xLAUNCH SITE
color=639AFF
4,0,9=S/
4,9=S
#Row 3
color=00FF00
6,0=xTYPE
6,9=xFREQUENCY
color=639AFF
7,0,9=T
7,9=F MHz
#Row 4
fonts=0,5
color=285454
11.5,0=xIP ADDRESS:
10.9,7,-15=N
#Footer
color=FF0000
12.7,18=bVV
############
@Main.Puspis
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
scale=11,10
fonts=0,2
#Row 1
color=00FF00
0,0=xSONDE ID
0,11=xFREQUENCY
color=90EE90
0,7.4=t
1,0=Is
1,11=F
#Row 2
fonts=0,1
color=00FF00
3,0=xLATITUDE
3,11=xLONGITUDE
color=FF007B
4,0=L
4,11=O
#Row 3
color=00FF00
6,0=xWIND SPEED
6,11=xCLIMB RATE
color=639AFF
7,0=Hkkm/h
7,11=Vm/s
#Row 4
color=00FF00
9,0=xRX ALTITUDE
9,11=xSONDE ALTITUDE
color=639AFF
10,0=GH
10,11=A
#Row 5
color=00FF00
12,0=xDISTANCE
12,11=xFRAMES
color=FFFFFF
13,0=GD
13.5,11=Q4
#Footer
color=FF0000
15,0.2=xIP:
15,2.5=n
15,18=bVV
############
@JotaEme.Puspis
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
scale=11,10
fonts=0,1
#Row 1
color=90EE90
0,0=Is
0.7,10.5=t
0,14=F
#Row 2
color=00FF00
2,1.2=xWIND SPEED
2,14=xCLIMB RATE
color=639AFF
3,1=Hkkm/h
3,13=Vm/s
#Row 3
color=00FF00
5,1=xRX ALTITUDE
5,12.5=xSONDE ALTITUDE
color=639AFF
6,2=GH
6,14=A
#Row 4
color=00FF00
8,0=xSONDE POSITION
color=FF007B
9,2=l
10,2=o
#Row 5
color=00FF00
11.4,2=xDISTANCE
color=FFFFFF
12.4,1.5=GD
#Circle
color=EEAA00,000033
8,13.8=g0CCS,28,FFFF00,000033,5,9ACD32,5,EEAA00
#Footer
color=FF0000
15,0=n
color=FFFFFF,000000
15,8.7=Q4
color=FF0000
15,18.4=bVV
############
@CompassTFT.Puspis
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
scale=13,10
fonts=0,1
#Row 1
color=90EE90
0.5,1.5=XCOMPASS
#Row 2
color=00FF00
4,2=xRX HEADING
color=639AFF
5,3.8=GC
#Row 3
color=00FF00
9.5,3=xDISTANCE
9.5,13.5=xBEARING
color=639AFF
10.5,3.4=GD
10.5,14.3=GI
#Circle
color=EEAA00,000033
0.2,10=g0CCS,52,FFFF00,000033,10,9ACD32,6,EEAA00
#Footer
color=FF0000,000000
12.7,1=Q4
12.7,18=bVV
############
@GPSdataTFT.Puspis
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
scale=13,10
fonts=0,1
#Row 1
color=90EE90
0.5,0.5=XGPS RECEIVER STATION
#Row 2
color=00FF00
3,0=xRX LATITUDE
3,12=xRX LONGITUDE
color=639AFF
4,0=GA
4,12=GO
#Row 3
color=00FF00
6,0=xRX ALTITUDE
6,12=xRX HEADING
color=639AFF
7,0=GH
7,12=GC
#Row 4
color=00FF00
9,0=xDISTANCE
9,12=xBEARING
color=639AFF
10,0=GD
10,12=GB
#Footer
color=FF0000,000000
12.7,0.4=Q4
12.7,18=bVV
############
@BatteryTFT.Puspis
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
scale=13,10
fonts=0,1
#Row 1
color=90EE90
0.5,4=XBATTERY STATUS
#Row 2
color=00FF00
3,0=x(C)HARGE/(B)ATT
3,11.5=xBATTERY VOLTAGE
color=639AFF
4,4=BS
4,11.5=BVV
#Row 3
color=00FF00
6,0=xCHARGE CURRENT
6,11.5=xDISCHG. CURRENT
color=639AFF
7,0=BCmA
7,11.5=BDmA
#Row 3
color=00FF00
9,0=xIC TEMPERATURE
9,11.5=xFREQ. OFFSET
color=639AFF
10,0=BTC
10,10=C
#Footer
color=FF0000,000000
12.7,0.4=Q4
12.7,18=bVV

Wyświetl plik

@ -0,0 +1,217 @@
## screens3.txt: TFT display (portrait)
## based on http://www.p1337.synology.me/dokuwiki/doku.php?id=public:wettersonden
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg) [not yet implemented, maybe some day in future]
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
#
# Default configuration for "Scanner" display:
# - view timer disabled; rx timer=0; norx timer = 0
# => after 1 second immediately an action is triggered
# (norx: go to next sonde; rx: go to default receiver display)
# - key1 actions: D,0,F,W
# => Button press activates default receiver view, double press does nothing
# Mid press activates Spectrum display, long press activates Wifi scan
# - key2 has no function
@ScannerPortrait
timer=-1,0,0
key1action=D,#,F,W
key2action=>,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9,4.5=T
6,0=XHoehe
6,5=GH
color=ffff00
2,0=F MHz
4,0=S
color=00ff00,444444
7,5=n
7,0=bV
############
# Default configuration for "Legacy" display:
# - view timer=-1, rx timer=-1 (disabled); norx timer=20 (or -1 for "old" behaviour)
# => norx timer fires after not receiving a singla for 20 seconds
# - key1 actions: +,0,F,W
# => Button1 press: next sonde; double press => @Scanner display
# => Mid press activates Spectrum display, long press activates Wifi scan
# - key2 actions: 2,#,#,#
# => BUtton2 activates display 2 (@Field)
# - timer actions: #,#,0
# (norx timer: if no signal for >20 seconds: go back to scanner mode)
#
@LegacyPortrait
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
9,10=f
9,0=r
9,4=Q
5,0=g0NCS,35,ffff00,000044,6,33ff33,5,eeaa00
5,7=g0CCS,35,ffff00,000044,6,55ff55,5,eeaa00
0,0=s
0,9=is
2,0=L
3,0=O
color=FFFF00
1,6=Hk km/h
color=FF0000
1,0=GD
color=FFFFFF
4,9=GH
3,9=V
4,0=A
############
@PeilungTFTPortrait
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
color=ffff00,000033
color=bbbbbb,000000
0,2=xN Top:
0,8=xCourse Top:
color=ffff00,000033
1,0=g0NCS,35,ffff00,000044,6,33ff33,5,eeaa00
1,7=g0CCS,35,ffff00,000044,6,55ff55,5,eeaa00
color=ffffff,000000
6,0=xDirection:
6,8,4=gI
7,0=xCOG:
7,4,4=gC
7,8=xturn:
7,12,4=gB
############
@GPSdataTFTPortrait
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=xOn-board GPS:
1,0,8=gA
2,0,8=gO
3,0,8=gH
4,0,8=gC
5,0=xGPS vs Sonde:
6,0,8=gD
7,0,8=gI
7,8,8=gB
############
@BatteryTFTPortrait
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=xBattery status:
0,14=bS
1,0=xBatt:
1,5,5=bVV
2,0,16=bCmA(charging)
3,0,16=bDmA(discharging)
4.4,0=xUSB:
4.4,5,5=bUV
5.4,0,10=bImA
6.4,0=xTemp:
6.4,5,5=bT C

128
RX_FSK/data/style.css 100644 → 100755
Wyświetl plik

@ -44,7 +44,7 @@ td#sfreq {
outline: none;
cursor: pointer;
padding: 10px 10px;
width: 14vw;
width: 12vw;
transition: 0.3s;
}
@ -128,3 +128,129 @@ p{
margin: 0;
display: block;
}
#map {
height: 100%;
}
.leaflet-popup-content table, .leaflet-popup-content table td {
border:0;
background-color: white;
}
.leaflet-popup-content table td:nth-child(2),.leaflet-popup-content table td:nth-child(5) {
text-align: right;
padding-left: 3px;
}
.leaflet-popup-content table td:nth-child(3),.leaflet-popup-content table td:nth-child(6) {
text-align: left;
padding-right: 10px;
}
.leaflet-gps{animation:fading 1s infinite}@keyframes fading{0%{opacity:0.7}50%{opacity:1}100%{opacity:0.7}}
.leaflet-gps::after {
content: '🔵';
}
.leaflet-gps {
margin-left: -7px !important;
margin-top: -9px !important;
}
.leaflet-burst::after {
content: '💥';
}
.leaflet-burst {
margin-left: -20px !important;
margin-top: -22px !important;
font-weight: bold;
font-size: 30px;
}
.leaflet-landing::after {
content: '×';
}
.leaflet-landing {
margin-left: -13px !important;
margin-top: -30px !important;
font-weight: bold;
font-size: 40px;
}
.leaflet-header {
text-align: center;
width: 250px;
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
pointer-events: auto !important;
}
.leaflet-header #settings {
display: none;
}
.leaflet-header label {
display: block;
margin-top: 5px;
}
.leaflet-header input {
width: 80px;
margin: 0 auto;
}
.leaflet-header #submit {
margin: 3px auto;
}
.leaflet-footer {
display:none;
text-align: center;
width: 180px;
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-center {
left:0;
right:0;
margin: 0 auto;
padding: 5px;
background: #fff;
background: rgba(255, 255, 255, 0.8);
}
.leaflet-header #sonde_detail {
display:none;
}
@media screen and (max-width: 600px) {
.leaflet-control-attribution {
-moz-transform: rotate(-90deg) translateX(100%);
-ms-transform: rotate(-90deg) translateX(100%);
-o-transform: rotate(-90deg) translateX(100%);;
-webkit-transform: rotate(-90deg) translateX(100%);
transform: rotate(-90deg) translateX(100%);
-webkit-transform-origin: 100% 100%;
-moz-transform-origin: 100% 100%;
-ms-transform-origin: 100% 100%;
-o-transform-origin: 100% 100%;
transform-origin: 100% 100%;
}
}
.ldot {
height: 1em;
width: 1em;
border-radius: 50%;
display: inline-block;
}
.ybg { background-color: #E0E000; }
.gbg { background-color: green; }
.rbg { background-color: red; }

Wyświetl plik

@ -1,4 +1,4 @@
const char *version_name = "rdzTTGOsonde";
const char *version_id = "devel20210728";
const char *version_id = "devel20210815";
const int SPIFFS_MAJOR=2;
const int SPIFFS_MINOR=11;
const int SPIFFS_MINOR=14;

Wyświetl plik

@ -585,7 +585,7 @@ int DFM::receiveNew() {
delay(2);
}
}
return RX_TIMEOUT;
return rxframes == 4 ? RX_TIMEOUT : RX_OK;
}
int DFM::receiveOld() {

Wyświetl plik

@ -1,3 +1,4 @@
#include "../../RX_FSK/features.h"
#include <U8x8lib.h>
#include <U8g2lib.h>
#include <SPIFFS.h>
@ -24,9 +25,16 @@ extern AXP20X_Class axp;
extern bool axp192_found;
extern SemaphoreHandle_t axpSemaphore;
extern xSemaphoreHandle globalLock;
#define SPI_MUTEX_LOCK() \
do \
{ \
} while (xSemaphoreTake(globalLock, portMAX_DELAY) != pdPASS)
#define SPI_MUTEX_UNLOCK() xSemaphoreGive(globalLock)
struct GpsPos gpsPos;
SPIClass spiDisp(HSPI);
//SPIClass spiDisp(HSPI);
const char *sondeTypeStr[NSondeTypes] = { "DFM ", "DFM9", "RS41", "RS92", "M10 ", "M20 ", "DFM6", "MP3H" };
const char *sondeTypeLongStr[NSondeTypes] = { "DFM (all)", "DFM9 (old)", "RS41", "RS92", "M10 ", "M20 ", "DFM6 (old)", "MP3-H1" };
@ -312,14 +320,8 @@ void U8x8Display::drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t /*size*/, ui
const GFXfont *gfl[] = {
#ifdef ALT9225
&Terminal11x16Font, // 3 (replacement for 1 or 2 with old library)
&Terminal11x16Font, // 4 (replacement for 1 or 2 with old library)
#else
// nobody was using them, so removed with new library
&FreeMono9pt7b, // 3
&FreeMono12pt7b, // 4
#endif
&FreeSans9pt7b, // 5
&FreeSans12pt7b, // 6
&Picopixel, // 7
@ -331,13 +333,8 @@ struct gfxoffset_t {
// first value: offset: max offset from font glyphs (last column * (-1)) (check /, \, `, $)`
// yclear:max height: max of (height in 3rd column) + (yofs + 6th column) (check j)
const struct gfxoffset_t gfxoffsets[]={
#ifdef ALT9225
{ 16, 18},
{ 16, 18},
#else
{ 11, 15 }, // 13+11-9 "j"
{ 15, 20 }, // 19+15-14
#endif
{ 13, 18 }, // 17+13-12 "j"
{ 17, 23 }, // 23+17-17
{ 4, 6}, // 6+4-4
@ -348,55 +345,49 @@ static int ngfx = sizeof(gfl)/sizeof(GFXfont *);
#define TFT_LED 0 // 0 if wired to +5V directly
#define TFT_BRIGHTNESS 100 // Initial brightness of TFT backlight (optional)
#ifdef ALT9225
Arduino_DataBus *bus;
#endif
void ILI9225Display::begin() {
#ifdef ALT9225
Serial.println("ILI9225 init (alt driver)");
bus = new Arduino_ESP32SPI( sonde.config.tft_rs, sonde.config.tft_cs,
sonde.config.oled_scl, sonde.config.oled_sda, -1, HSPI);
tft = new Arduino_ILI9225(bus, sonde.config.oled_rst);
Serial.println("ILI9225 init (alt driver): done");
tft->begin();
Serial.println("ILI9225/ILI9341 init");
// On the M5, the display and the Lora chip are on the same SPI interface (VSPI default pins),
// we must use the same SPI bus with correct locking
if(sonde.config.type == TYPE_M5_CORE2) {
bus = new Arduino_ESP32SPI( sonde.config.tft_rs, sonde.config.tft_cs,
sonde.config.oled_scl, sonde.config.oled_sda, 38, VSPI);
} else {
bus = new Arduino_ESP32SPI( sonde.config.tft_rs, sonde.config.tft_cs,
sonde.config.oled_scl, sonde.config.oled_sda, -1, HSPI);
}
if(_type == 3)
tft = new Arduino_ILI9341(bus, sonde.config.oled_rst);
else if(_type == 4)
tft = new Arduino_ILI9342(bus, sonde.config.oled_rst);
else
tft = new Arduino_ILI9225(bus, sonde.config.oled_rst);
Serial.println("ILI9225/ILI9341 init: done");
tft->begin(sonde.config.tft_spifreq);
tft->fillScreen(BLACK);
tft->setRotation(sonde.config.tft_orient);
tft->setTextWrap(false);
#else
tft = new MY_ILI9225(sonde.config.oled_rst, sonde.config.tft_rs, sonde.config.tft_cs,
sonde.config.oled_sda, sonde.config.oled_scl, TFT_LED, TFT_BRIGHTNESS);
tft->setModeFlip(sonde.config.tft_modeflip);
tft->begin(spiDisp);
tft->setOrientation(sonde.config.tft_orient);
#endif
if(sonde.config.type == TYPE_M5_CORE2)
tft->invertDisplay(true);
}
void ILI9225Display::clear() {
#ifdef ALT9225
tft->fillScreen(0);
#else
tft->clear();
#endif
SPI_MUTEX_LOCK();
tft->fillScreen(BLACK);
SPI_MUTEX_UNLOCK();
}
// for now, 0=small=FreeSans9pt7b, 1=large=FreeSans18pt7b
void ILI9225Display::setFont(uint8_t fontindex) {
#ifdef ALT9225
if(fontindex==1 || fontindex==2) { fontindex=3; }
#endif
findex = fontindex;
switch(fontindex) {
#ifdef ALT9225
case 0: tft->setFont(NULL); tft->setTextSize(1); break;
case 1: tft->setFont(NULL); tft->setTextSize(2); break;
case 2: tft->setFont(NULL); tft->setTextSize(2); break;
default: tft->setFont(gfl[fontindex-3]);
#else
case 0: tft->setFont(Terminal6x8); break;
case 1: tft->setFont(Terminal11x16); break;
case 2: tft->setFont(Terminal12x16); break;
default: tft->setGFXFont(gfl[fontindex-3]);
#endif
}
}
@ -418,7 +409,6 @@ void ILI9225Display::getDispSize(uint8_t *height, uint8_t *width, uint8_t *lines
break;
default: // get size from GFX Font
{
#ifdef ALT9225
int16_t x, y;
uint16_t w, h;
tft->getTextBounds("|", 0, 0, &x, &y, &w, &h);
@ -426,13 +416,6 @@ void ILI9225Display::getDispSize(uint8_t *height, uint8_t *width, uint8_t *lines
tft->getTextBounds("A", 0, 0, &x, &y, &w, &h);
if(colskip) *colskip = w+2;
if(lineskip&&colskip) { Serial.printf("skip size from bounds: %d, %d\n", *lineskip, *colskip); }
#else
int16_t w,h,a;
tft->getGFXCharExtent('|',&w,&h,&a);
if(lineskip) *lineskip = h+2;
tft->getGFXCharExtent('A',&w,&h,&a);
if(colskip) *colskip = w+2; // just an approximation
#endif
}
}
}
@ -448,16 +431,12 @@ void ILI9225Display::drawString(uint8_t x, uint8_t y, const char *s, int16_t wid
}
// Standard font
if(findex<3) {
SPI_MUTEX_LOCK();
DebugPrintf(DEBUG_DISPLAY, "Simple Text %s at %d,%d [%d]\n", s, x, y, width);
#ifdef ALT9225
// for gpx fonts and new library, cursor is at baseline!!
int h = 6; if(findex>1) h=12;
#else
tft->setBackgroundColor(bg);
int h = tft->getFont().height;
#endif
if( alignright ) {
#ifdef ALT9225
#if 1
//w = tft->getTextWidth(s);
/// TODO
if( width==WIDTH_AUTO ) { width = w; }
@ -476,7 +455,6 @@ void ILI9225Display::drawString(uint8_t x, uint8_t y, const char *s, int16_t wid
tft->drawText(x + width - w, y, s, fg);
#endif
} else {
#ifdef ALT9225
tft->setCursor(x, y);
tft->setTextColor(fg, bg);
tft->print(s);
@ -486,25 +464,16 @@ void ILI9225Display::drawString(uint8_t x, uint8_t y, const char *s, int16_t wid
//if(curx < x + width) {
// tft->fillRectangle(curx, y, x + width - 1, y + h - 1, bg);
//}
#else
int curx = tft->drawText(x, y, s, fg);
if( width==WIDTH_AUTO ) { return; }
if(curx < x + width) {
tft->fillRectangle(curx, y, x + width - 1, y + h - 1, bg);
}
#endif
}
SPI_MUTEX_UNLOCK();
return;
}
// GFX font
SPI_MUTEX_LOCK();
int16_t x1, y1;
if(1||width==WIDTH_AUTO || alignright) {
#ifdef ALT9225
tft->getTextBounds(s, x, y + gfxoffsets[findex-3].yofs, &x1, &y1, (uint16_t *)&w, (uint16_t *)&h);
w += x1 - x + 1;
#else
tft->getGFXTextExtent(s, x, y + gfxoffsets[findex-3].yofs, &w, &h);
#endif
if(width==WIDTH_AUTO) { width=w; }
if(alignright) {
if(w > width) {
@ -530,7 +499,7 @@ void ILI9225Display::drawString(uint8_t x, uint8_t y, const char *s, int16_t wid
}
#else
// Text by drawing bitmap.... => less "flicker"
#ifdef ALT9225
#if 1
//TODO
tft->setCursor( alignright? x+width-w : x, y + gfxoffsets[findex-3].yofs);
tft->setTextColor( fg, bg );
@ -568,11 +537,12 @@ void ILI9225Display::drawString(uint8_t x, uint8_t y, const char *s, int16_t wid
free(bitmap);
#endif
#endif
SPI_MUTEX_UNLOCK();
}
void ILI9225Display::drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr) {
#ifdef ALT9225
int i,j;
SPI_MUTEX_LOCK();
tft->startWrite();
for(i=0; i<cnt*8; i++) {
uint8_t v = tile_ptr[i];
@ -582,9 +552,7 @@ void ILI9225Display::drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_p
}
}
tft->endWrite();
#else
tft->drawTile(x, y, cnt, tile_ptr);
#endif
SPI_MUTEX_UNLOCK();
#if 0
int i,j;
tft->startWrite();
@ -602,26 +570,24 @@ void ILI9225Display::drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_p
}
void ILI9225Display::drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color, boolean fill) {
SPI_MUTEX_LOCK();
if(fill)
tft->fillTriangle(x1, y1, x2, y2, x3, y3, color);
else
tft->drawTriangle(x1, y1, x2, y2, x3, y3, color);
SPI_MUTEX_UNLOCK();
}
void ILI9225Display::drawBitmap(uint16_t x1, uint16_t y1, const uint16_t* bitmap, int16_t w, int16_t h) {
#ifdef ALT9225
SPI_MUTEX_LOCK();
tft->draw16bitRGBBitmap(x1, y1, bitmap, w, h);
#else
tft->drawBitmap(x1, y1, bitmap, w, h);
#endif
SPI_MUTEX_UNLOCK();
}
void ILI9225Display::welcome() {
#ifdef ALT9225
SPI_MUTEX_LOCK();
tft->fillScreen(0);
#else
tft->clear();
#endif
SPI_MUTEX_UNLOCK();
setFont(6);
drawString(0, 0*22, version_name, WIDTH_AUTO, 0xff00);
setFont(5);
@ -669,8 +635,9 @@ void ILI9225Display::drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t size, uin
#define pgm_read_pointer(addr) ((void *)pgm_read_dword(addr))
#ifdef ALT9225
#if 1
#else
// TO BE REMOVED
void MY_ILI9225::drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr) {
int i,j;
startWrite();
@ -731,8 +698,8 @@ RawDisplay *Display::rdis = NULL;
//TODO: maybe merge with initFromFile later?
void Display::init() {
Serial.printf("disptype is %d\n",sonde.config.disptype);
if(sonde.config.disptype==1) {
rdis = new ILI9225Display();
if(sonde.config.disptype==1 || sonde.config.disptype==3 || sonde.config.disptype==4 ) {
rdis = new ILI9225Display(sonde.config.disptype);
} else {
rdis = new U8x8Display(sonde.config.disptype);
}
@ -972,17 +939,33 @@ int Display::countEntries(File f) {
return n;
}
int Display::getScreenIndex(int index) {
if(index!=0) return index;
switch(sonde.config.disptype) {
case 1: // ILI9225
index = 2; // landscape mode (orient=1/3)
if( (sonde.config.tft_orient&0x01)==0 ) index++; // portrait mode (0/2)
break;
case 3: // ILI9341
case 4: // ILI9342
index = 4; // landscape mode (orient=1/3)
if( (sonde.config.tft_orient&0x01)==0 ) index++; // portrait mode (0/2)
break;
case 0: case 2: // small OLED display (SD1306/SH1106)
default:
index = 1; break;
}
return index;
}
void Display::initFromFile(int index) {
File d;
if(index>0) {
char file[20];
snprintf(file, 20, "/screens%d.txt", index);
Serial.printf("Reading %s\n", file);
d = SPIFFS.open(file, "r");
if(!d || d.available()==0 ) { Serial.printf("%s not found, using /screens.txt\n", file); }
}
if(!d || d.available()==0 ) d = SPIFFS.open("/screens.txt", "r");
if(!d) return;
char file[20];
index = getScreenIndex(index); // auto selection for index==0
snprintf(file, 20, "/screens%d.txt", index);
Serial.printf("Reading %s\n", file);
d = SPIFFS.open(file, "r");
if(!d || d.available()==0 ) { Serial.printf("%s not found\n", file); return; }
DispInfo *newlayouts = (DispInfo *)malloc(MAXSCREENS * sizeof(DispInfo));
if(!newlayouts) {
@ -1086,7 +1069,7 @@ void Display::initFromFile(int index) {
char text[61];
n=sscanf(s, "%f,%f,%f", &y, &x, &w);
sscanf(ptr+1, "%60[^\r\n]", text);
if(sonde.config.disptype==1) { x*=xscale; y*=yscale; w*=xscale; }
if(sonde.config.disptype==1 || sonde.config.disptype==3 || sonde.config.disptype==4 ) { x*=xscale; y*=yscale; w*=xscale; }
newlayouts[idx].de[what].x = x;
newlayouts[idx].de[what].y = y;
newlayouts[idx].de[what].width = n>2 ? w : WIDTH_AUTO;
@ -1244,6 +1227,7 @@ void Display::drawID(DispEntry *de) {
}
void Display::drawRSSI(DispEntry *de) {
rdis->setFont(de->fmt);
// TODO.... 3/4!!!!!
if(sonde.config.disptype!=1) {
snprintf(buf, 16, "-%d ", sonde.si()->rssi/2);
int len=strlen(buf)-3;
@ -1275,10 +1259,7 @@ void Display::drawFreq(DispEntry *de) {
drawString(de, buf);
}
void Display::drawAFC(DispEntry *de) {
if(!sonde.config.showafc) return;
rdis->setFont(de->fmt);
//if(sonde.si()->afc==0) { strcpy(buf, " "); }
//else
{ snprintf(buf, 15, " %+3.2fk", sonde.si()->afc*0.001); }
drawString(de, buf+strlen(buf)-8);
}
@ -1291,7 +1272,7 @@ void Display::drawSite(DispEntry *de) {
switch(de->extra[0]) {
case '#':
// currentSonde is index in array starting with 0;
// but we draw "1" for the first entrie and so on...
// but we draw "1" for the first entry and so on...
snprintf(buf, 3, "%2d", sonde.currentSonde+1);
buf[2]=0;
break;
@ -1361,6 +1342,15 @@ void Display::drawKilltimer(DispEntry *de) {
#define FAKEGPS 0
extern int lastCourse; // from RX_FSK.ino
float calcLatLonDist(float lat1, float lon1, float lat2, float lon2) {
float x = radians(lon1-lon2) * cos( radians((lat1+lat2)/2) );
float y = radians(lat2-lat1);
float d = sqrt(x*x+y*y)*EARTH_RADIUS;
return d;
}
void Display::calcGPS() {
// base data
#if 0
@ -1392,12 +1382,7 @@ static int tmpc=0;
#endif
// distance
if( gpsPos.valid && (sonde.si()->validPos&0x03)==0x03 && (layout->usegps&GPSUSE_DIST)) {
float lat1 = gpsPos.lat;
float lat2 = sonde.si()->lat;
float x = radians(gpsPos.lon-sonde.si()->lon) * cos( radians((lat1+lat2)/2) );
float y = radians(lat2-lat1);
float d = sqrt(x*x+y*y)*EARTH_RADIUS;
gpsDist = (int)d;
gpsDist = (int)calcLatLonDist(gpsPos.lat, gpsPos.lon, sonde.si()->lat, sonde.si()->lon);
} else {
gpsDist = -1;
}
@ -1538,7 +1523,8 @@ void Display::drawGPS(DispEntry *de) {
}
Serial.printf("GPS0: %c%c%c N=%d, A=%d, B=%d\n", circinfo->top, circinfo->arr, circinfo->bul, angN, angA, angB);
// "N" in direction angN
#ifdef ALT9225
#if 1
// TODO
#else
static_cast<ILI9225Display *>(rdis)->tft->drawGFXcharBM(x0 + circinfo->radius*sin(angN*PI/180)-6, y0 - circinfo->radius*cos(angN*PI/180)+7, 'N', 0xffff, bitmap, size, size);
#endif

Wyświetl plik

@ -1,5 +1,3 @@
#define ALT9225
#ifndef Display_h
#define Display_h
@ -7,11 +5,7 @@
#define FONT_SMALL 0
#include <SPI.h>
#ifdef ALT9225
#include <Arduino_GFX_Library.h>
#else
#include <TFT22_ILI9225.h>
#endif
#include <U8x8lib.h>
#include <SPIFFS.h>
@ -81,12 +75,12 @@ public:
class U8x8Display : public RawDisplay {
private:
U8X8 *u8x8 = NULL; // initialize later after reading config file
int _type;
uint8_t _type;
const uint8_t **fontlist;
int nfonts;
public:
U8x8Display(int type = 0) { _type = type; }
U8x8Display(uint8_t type = 0) { _type = type; }
void begin();
void clear();
void setFont(uint8_t fontindex);
@ -100,24 +94,17 @@ public:
void drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0);
};
#ifdef ALT9225
typedef Arduino_GFX MY_ILI9225;
#else
class MY_ILI9225 : public TFT22_ILI9225 {
using TFT22_ILI9225::TFT22_ILI9225;
public:
uint16_t drawGFXChar(int16_t x, int16_t y, unsigned char c, uint16_t color);
void drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr);
};
#endif
typedef Arduino_GFX MY_ILI9225;
class ILI9225Display : public RawDisplay {
private:
uint8_t yofs=0;
uint8_t findex=0;
uint8_t _type;
public:
MY_ILI9225 *tft = NULL; // initialize later after reading config file
ILI9225Display(int type = 1) { _type = type; }
void begin();
void clear();
void setFont(uint8_t fontindex);
@ -161,6 +148,7 @@ private:
return ret;
}
public:
static int getScreenIndex(int index);
void initFromFile(int index);
int layoutIdx;

Wyświetl plik

@ -276,6 +276,7 @@ int M10M20::decodeframeM10(uint8_t *data) {
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
Serial.printf(" repair: %d/%d\n", repl, repairstep);
if(!crcok) return 2;
if(data[1]==0x9F && data[2]==0x20) {
Serial.println("Decoding...");
@ -338,7 +339,7 @@ int M10M20::decodeframeM10(uint8_t *data) {
Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]);
return 0;
}
return crcok?1:2;
return 1;
}
static uint32_t rxdata;
@ -392,12 +393,14 @@ void M10M20::processM10data(uint8_t dt)
if(rxp==2 && dataptr[0]==0x45 && dataptr[1]==0x20) { isM20 = true; }
if(isM20) {
memcpy(sonde.si()->typestr, "M20 ", 5);
sonde.si()->subtype = 2;
if(rxp>=M20_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM20(dataptr);
}
} else {
memcpy(sonde.si()->typestr, "M10 ", 5);
sonde.si()->subtype = 1;
if(rxp>=M10_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM10(dataptr);

Wyświetl plik

@ -14,13 +14,38 @@
#include <Sonde.h>
#include <Display.h>
SX1278FSK::SX1278FSK()
#define SPI_MUTEX_LOCK() \
do \
{ \
} while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS)
#define SPI_MUTEX_UNLOCK() xSemaphoreGive(_lock)
SX1278FSK::SX1278FSK() {}
void SX1278FSK::setup(xSemaphoreHandle lock)
{
// Initialize class variables
_lock = lock;
Serial.println("Setup sx1278");
if(_lock) SPI_MUTEX_LOCK();
digitalWrite(sonde.config.sx1278_ss, HIGH);
pinMode(sonde.config.sx1278_ss, OUTPUT);
Serial.printf("Configuing SX1278FSK SPI with miso=%d, mosi=%d, sck=%d, ss=%d\n", sonde.config.sx1278_miso,
sonde.config.sx1278_mosi, sonde.config.sx1278_sck, sonde.config.sx1278_ss);
SPI.begin(sonde.config.sx1278_sck, sonde.config.sx1278_miso, sonde.config.sx1278_mosi, -1); // no hardware CS
// was: SPI.begin();
//Set most significant bit first
SPI.setBitOrder(MSBFIRST);
//Divide the clock frequency
SPI.setClockDivider(SPI_CLOCK_DIV2);
//Set data mode
SPI.setDataMode(SPI_MODE0);
if(_lock) SPI_MUTEX_UNLOCK();
};
static SPISettings spiset = SPISettings(40000000L, MSBFIRST, SPI_MODE0);
static SPISettings spiset = SPISettings(10000000L, MSBFIRST, SPI_MODE0);
/*
Function: Turns the module ON.
@ -34,19 +59,6 @@ uint8_t SX1278FSK::ON()
Serial.println(F("Starting 'ON'"));
#endif
// Powering the module
pinMode(SX1278_SS, OUTPUT);
digitalWrite(SX1278_SS, HIGH);
//Configure the MISO, MOSI, CS, SPCR.
SPI.begin();
//Set most significant bit first
SPI.setBitOrder(MSBFIRST);
//Divide the clock frequency
SPI.setClockDivider(SPI_CLOCK_DIV2);
//Set data mode
SPI.setDataMode(SPI_MODE0);
// Set Maximum Over Current Protection
state = setMaxCurrent(0x1B);
if( state == 0 )
@ -60,7 +72,6 @@ uint8_t SX1278FSK::ON()
{
return 1;
}
// set FSK mode
state = setFSK();
return state;
@ -77,10 +88,12 @@ void SX1278FSK::OFF()
Serial.println(F("Starting 'OFF'"));
#endif
SPI.end();
//SPI.end();
#if 0
// Powering the module
pinMode(SX1278_SS,OUTPUT);
digitalWrite(SX1278_SS,LOW);
#endif
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## Setting OFF ##"));
@ -98,15 +111,16 @@ byte SX1278FSK::readRegister(byte address)
{
byte value = 0x00;
if(_lock) SPI_MUTEX_LOCK();
digitalWrite(sonde.config.sx1278_ss,LOW);
SPI.beginTransaction(spiset);
digitalWrite(SX1278_SS,LOW);
//delay(1);
bitClear(address, 7); // Bit 7 cleared to write in registers
SPI.transfer(address);
value = SPI.transfer(0x00);
digitalWrite(SX1278_SS,HIGH);
SPI.endTransaction();
digitalWrite(sonde.config.sx1278_ss,HIGH);
#if (SX1278FSK_debug_mode > 1)
if(address!=0x3F) {
@ -118,7 +132,7 @@ byte SX1278FSK::readRegister(byte address)
Serial.println();
}
#endif
if(_lock) SPI_MUTEX_UNLOCK();
return value;
}
@ -131,15 +145,16 @@ Parameters:
*/
void SX1278FSK::writeRegister(byte address, byte data)
{
if(_lock) SPI_MUTEX_LOCK();
digitalWrite(sonde.config.sx1278_ss,LOW);
SPI.beginTransaction(spiset);
digitalWrite(SX1278_SS,LOW);
//delay(1);
bitSet(address, 7); // Bit 7 set to read from registers
SPI.transfer(address);
SPI.transfer(data);
digitalWrite(SX1278_SS,HIGH);
SPI.endTransaction();
digitalWrite(sonde.config.sx1278_ss,HIGH);
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Writing: ##\t"));
@ -150,7 +165,7 @@ void SX1278FSK::writeRegister(byte address, byte data)
Serial.print(data, HEX);
Serial.println();
#endif
if(_lock) SPI_MUTEX_UNLOCK();
}
/*
@ -867,4 +882,5 @@ void SX1278FSK::showRxRegisters()
}
#endif
xSemaphoreHandle globalLock =xSemaphoreCreateMutex();
SX1278FSK sx1278 = SX1278FSK();

Wyświetl plik

@ -35,8 +35,6 @@
#define SX1278FSK_debug_mode 0
#define SX1278_SS SS
//! MACROS //
#define bitRead(value, bit) (((value) >> (bit)) & 0x01) // read a bit
#define bitSet(value, bit) ((value) |= (1UL << (bit))) // set bit to '1'
@ -171,7 +169,9 @@ class SX1278FSK
{
public:
// class constructor
SX1278FSK();
SX1278FSK();
void setup(xSemaphoreHandle lock);
// Turn on SX1278 module (return 0 on sucess, 1 otherwise)
uint8_t ON();
@ -256,7 +256,7 @@ public:
// Receive a packet
uint8_t receivePacketTimeout(uint32_t wait, byte *data);
xSemaphoreHandle _lock = NULL;
#if 0
//! It gets the internal temperature of the module.

Wyświetl plik

@ -24,6 +24,7 @@ struct scancfg {
//struct scancfg scanLCD={ 121, 7, 120/6, 120/6/4, 6000.0/120.0/20.0, 20, 120*20, 1 };
struct scancfg scanLCD={ 121, 7, 120/6, 120/6/4, 6000.0/120.0/10.0, 10, 120*10, 2, 40 };
struct scancfg scanTFT={ 210, 16, 210/6, 210/6/5, 6000.0/210.0/10.0, 10, 210*10, 1, 0 };
struct scancfg scan9341={ 210, 16, 210/6, 210/6/5, 6000.0/210.0/10.0, 10, 210*10, 1, 0 };
struct scancfg &scanconfig = scanTFT;
@ -65,7 +66,7 @@ void Scanner::fillTiles(uint8_t *row, int value) {
///// unused???? uint8_t tiles[16] = { 0x0f,0x0f,0x0f,0x0f,0xf0,0xf0,0xf0,0xf0, 1, 3, 7, 15, 31, 63, 127, 255};
// type 0: lcd, 1: tft, 2: lcd(sh1106)
#define ISTFT (sonde.config.disptype==1)
#define ISTFT (sonde.config.disptype==1 || sonde.config.disptype==3)
void Scanner::plotResult()
{
int yofs = 0;

Wyświetl plik

@ -82,16 +82,21 @@ void Sonde::defaultConfig() {
config.power_pout = -1;
config.spectrum=10;
// Try autodetecting board type
config.type = TYPE_TTGO;
// Seems like on startup, GPIO4 is 1 on v1 boards, 0 on v2.1 boards?
config.gps_rxd = -1;
config.gps_txd = -1;
config.sx1278_ss = SS; // default SS pin, on all TTGOs
config.sx1278_miso = MISO;
config.sx1278_mosi = MOSI;
config.sx1278_sck = SCK;
config.oled_rst = 16;
config.disptype = 0;
config.tft_orient = 1;
config.button2_axp = 0;
config.norx_timeout = 20;
config.screenfile = 1;
config.tft_modeflip = 0;
config.tft_spifreq = SPI_DEFAULT_FREQ;
if(initlevels[16]==0) {
config.oled_sda = 4;
config.oled_scl = 15;
@ -103,32 +108,55 @@ void Sonde::defaultConfig() {
} else {
config.oled_sda = 21;
config.oled_scl = 22;
if(initlevels[17]==0) { // T-Beam
if(initlevels[17]==0) { // T-Beam or M5Stack Core2?
int tbeam=7;
if(initlevels[12]==0) {
tbeam = 10;
Serial.println("Autoconfig: looks like T-Beam 1.0 board");
Serial.println("Autoconfig: looks like T-Beam 1.0 or M5Stack Core2 board");
} else if ( initlevels[4]==1 && initlevels[12]==1 ) {
tbeam = 11;
Serial.println("Autoconfig: looks like T-Beam 1.1 board");
}
if(tbeam == 10 || tbeam == 11) { // T-Beam v1.0 or T-Beam v1.1
config.button_pin = 38;
config.button2_pin = 15 + 128; //T4 + 128; // T4 = GPIO13
// Maybe in future use as default only PWR as button2?
//config.button2_pin = 255;
config.button2_axp = 1;
config.gps_rxd = 34;
config.gps_txd = 12;
// Check for I2C-Display@21,22
#define SSD1306_ADDRESS 0x3c
Wire.begin(21, 22);
Wire.beginTransmission(SSD1306_ADDRESS);
byte err = Wire.endTransmission();
delay(100); // otherwise its too fast?!
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
if(err!=0 && fingerprint!=17) { // hmm. 17 after powerup with oled commected and no i2c answer!?!?
#define BM8563_ADDRESS 0x51
Wire.beginTransmission(BM8563_ADDRESS);
byte err = Wire.endTransmission();
if(err==0) {
Serial.println("M5stack Core2 board detected\n");
config.type = TYPE_M5_CORE2;
config.button_pin = 255;
config.button2_pin = 255;
config.button2_axp = 1;
config.disptype = 4; // ILI9342
config.oled_sda = 23;
config.oled_scl = 18;
config.oled_rst = -1;
config.tft_rs = 15;
config.tft_cs = 5;
config.screenfile = 4;
config.gps_rxd = 13;
config.gps_txd = -1; // 14
config.sx1278_ss = 33;
config.sx1278_miso = 38;
config.sx1278_mosi = 23; //MOSI;
config.sx1278_sck = 18; // SCK;
} else { // some t-beam...
config.button_pin = 38;
config.button2_pin = 15 + 128; //T4 + 128; // T4 = GPIO13
// Maybe in future use as default only PWR as button2?
//config.button2_pin = 255;
config.button2_axp = 1;
config.gps_rxd = 34;
config.gps_txd = 12;
// Check for I2C-Display@21,22
#define SSD1306_ADDRESS 0x3c
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
delay(100); // otherwise its too fast?!
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
if(err!=0 && fingerprint!=17) { // hmm. 17 after powerup with oled commected and no i2c answer!?!?
fingerprint |= 128;
Serial.println("no I2C display found, assuming large TFT display\n");
// CS=0, RST=14, RS=2, SDA=4, CLK=13
@ -141,10 +169,11 @@ void Sonde::defaultConfig() {
config.tft_cs = 0;
config.spectrum = -1; // no spectrum for now on large display
config.screenfile = 2;
} else {
} else {
// OLED display, pins 21,22 ok...
config.disptype = 0;
Serial.println("... with small OLED display\n");
}
}
} else {
Serial.println("Autoconfig: looks like T-Beam v0.7 board");
@ -185,7 +214,6 @@ void Sonde::defaultConfig() {
config.startfreq=400;
config.channelbw=10;
config.marker=0;
config.showafc=0;
config.freqofs=0;
config.rs41.agcbw=12500;
config.rs41.rxbw=6300;
@ -265,12 +293,20 @@ void Sonde::setConfig(const char *cfg) {
config.tft_cs = atoi(val);
} else if(strcmp(cfg,"tft_orient")==0) {
config.tft_orient = atoi(val);
} else if(strcmp(cfg,"tft_modeflip")==0) {
config.tft_modeflip = atoi(val);
} else if(strcmp(cfg,"tft_spifreq")==0) {
config.tft_spifreq = atoi(val);
} else if(strcmp(cfg,"gps_rxd")==0) {
config.gps_rxd = atoi(val);
} else if(strcmp(cfg,"gps_txd")==0) {
config.gps_txd = atoi(val);
} else if(strcmp(cfg,"sx1278_ss")==0) {
config.sx1278_ss = atoi(val);
} else if(strcmp(cfg,"sx1278_miso")==0) {
config.sx1278_miso = atoi(val);
} else if(strcmp(cfg,"sx1278_mosi")==0) {
config.sx1278_mosi = atoi(val);
} else if(strcmp(cfg,"sx1278_sck")==0) {
config.sx1278_sck = atoi(val);
} else if(strcmp(cfg,"maxsonde")==0) {
config.maxsonde = atoi(val);
if(config.maxsonde>MAXSONDE) config.maxsonde=MAXSONDE;
@ -305,8 +341,6 @@ void Sonde::setConfig(const char *cfg) {
config.spectrum = atoi(val);
} else if(strcmp(cfg,"marker")==0) {
config.marker = atoi(val);
} else if(strcmp(cfg,"showafc")==0) {
config.showafc = atoi(val);
} else if(strcmp(cfg,"freqofs")==0) {
config.freqofs = atoi(val);
} else if(strcmp(cfg,"rs41.agcbw")==0) {
@ -383,9 +417,11 @@ void Sonde::setConfig(const char *cfg) {
} else if(strcmp(cfg, "sondehub.callsign")==0) {
strncpy(config.sondehub.callsign, val, 63);
} else if(strcmp(cfg, "sondehub.lat")==0) {
strncpy(config.sondehub.lat, val, 19);
config.sondehub.lat = *val==0 ? NAN : atof(val);
Serial.printf("lat is %f\n", config.sondehub.lat);
} else if(strcmp(cfg, "sondehub.lon")==0) {
strncpy(config.sondehub.lon, val, 19);
config.sondehub.lon = *val==0 ? NAN : atof(val);
Serial.printf("lon is %f\n", config.sondehub.lon);
} else if(strcmp(cfg, "sondehub.alt")==0) {
strncpy(config.sondehub.alt, val, 19);
} else if(strcmp(cfg, "sondehub.antenna")==0) {

Wyświetl plik

@ -67,7 +67,7 @@ typedef struct st_sondeinfo {
// receiver configuration
bool active;
SondeType type;
int8_t subtype; /* 0 for none/unknown, hex type for dfm, maybe add 1/2 for M10/M20 as well?*/
int8_t subtype; /* 0 for none/unknown, hex type for dfm, 1/2 for M10/M20 */
float freq;
// decoded ID
char typestr[5]; // decoded type (use type if *typestr==0)
@ -187,14 +187,18 @@ struct st_sondehub {
int chase;
char host[64];
char callsign[64];
char lat[20];
char lon[20];
double lat;
double lon;
char alt[20];
char antenna[64];
char email[64];
};
// to be extended
enum { TYPE_TTGO, TYPE_M5_CORE2 };
typedef struct st_rdzconfig {
int type; // autodetected type, TTGO or M5_CORE2
// hardware configuration
int button_pin; // PIN port number menu button (+128 for touch mode)
int button2_pin; // PIN port number menu button (+128 for touch mode)
@ -209,9 +213,13 @@ typedef struct st_rdzconfig {
int tft_rs; // TFT RS pin
int tft_cs; // TFT CS pin
int tft_orient; // TFT orientation (default: 1)
int tft_modeflip; // Hack for Joerg's strange display
int tft_spifreq; // SPI transfer speed (default 40M is out of spec for some TFT)
int gps_rxd; // GPS module RXD pin. We expect 9600 baud NMEA data.
int gps_txd; // GPS module TXD pin
int sx1278_ss; // SPI slave select for sx1278
int sx1278_miso; // SPI MISO for sx1278
int sx1278_mosi; // SPI MOSI for sx1278
int sx1278_sck; // SPI SCK for sx1278
// software configuration
int debug; // show port and config options after reboot
int wifi; // connect to known WLAN 0=skip
@ -227,7 +235,6 @@ typedef struct st_rdzconfig {
int noisefloor; // for spectrum display
char mdnsname[15]; // mDNS-Name, defaults to rdzsonde
// receiver configuration
int showafc; // show afc value in rx screen
int freqofs; // frequency offset (tuner config = rx frequency + freqofs) in Hz
struct st_rs41config rs41; // configuration options specific for RS41 receiver
struct st_rs92config rs92;

Wyświetl plik

@ -34,6 +34,16 @@ TTGO T-Team 1.0 with IL9225 TFT => fingerprint 23
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
M5Stack Core2
0:1 1:0 2:0 3:1 4:1 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:1 14:1 15:1 16:1 17:0 18:1 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:1 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:1 14:1 15:1 16:1 17:0 18:1 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
Board fingerprint is 87 (nach power on)
0:1 1:0 2:0 3:1 4:0 5:0 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:0 5:0 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
Board fingerprint is 22 (nach reset)
Autoconfig: looks like T-Beam 1.0 board
vs 0010111
T-Beam 1.1 seems to be different: 1110111
GPIO4 = 1 (additional pullup) => +64