diff --git a/data_embed/index.html b/data_embed/index.html index 27404be..91df49d 100644 --- a/data_embed/index.html +++ b/data_embed/index.html @@ -296,11 +296,11 @@ You like to enable, if you use your tracker portable and it should automatically
- +
- +
diff --git a/src/TTGO_T-Beam_LoRa_APRS.ino b/src/TTGO_T-Beam_LoRa_APRS.ino index 8aad801..2bcf4c0 100644 --- a/src/TTGO_T-Beam_LoRa_APRS.ino +++ b/src/TTGO_T-Beam_LoRa_APRS.ino @@ -176,6 +176,7 @@ String aprsLonPresetNiceNotation; String aprsLatLonAsMaidenheadGridLocator; String aprsLatLonDAO = ""; String aprsLatLonPresetCOMP = ""; +boolean aprsLatLonInvalidPosition = true; RawDegrees bestRawLat; RawDegrees bestRawLng; double bestDoubleLat; @@ -669,16 +670,21 @@ out_relay_path: outString += String(buf); } - // position_ambiguity is not defined for compressed positions. - // But we test it, in order to see what happens. - // Else we could send in non-compressed mode, but will loose altitude or course/speed info. - // This is ok, because if you like to hide your position for privacy reasons, - // course/speed and altituded are also values you like to protect. - //if (!(sp_flags & SP_POS_FIXED) && gps_state && gps_isValid & - //(position_ambiguity < -4 || position_ambiguity >= 0) && - //aprsLatLonPresetCOMP.length()) { - //if (position_ambiguity >= 0) { - if (position_ambiguity == 0 && aprsLatLonPresetCOMP.length()) { + if (aprsLatLonInvalidPosition) { + // aprs spec: default null position. + // The null position should be include the \. symbol (unknown/indeterminate position). + outString += "0000.00N\\00000.00W."; + may_add_dao_extension = false; + } else if (position_ambiguity == 0 && aprsLatLonPresetCOMP.length()) { + // position_ambiguity is not defined for compressed positions. + // But we test it, in order to see what happens. + // Else we could send in non-compressed mode, but will loose altitude or course/speed info. + // This is ok, because if you like to hide your position for privacy reasons, + // course/speed and altituded are also values you like to protect. + //if (!(sp_flags & SP_POS_FIXED) && gps_state && gps_isValid & + //(position_ambiguity < -4 || position_ambiguity >= 0) && + //aprsLatLonPresetCOMP.length()) { + //if (position_ambiguity >= 0) { char helper_base91[] = {"0000\0"}; outString += aprsSymbolTable; @@ -749,13 +755,8 @@ out_relay_path: } else { // not compressed, i.e. fixed position - if ((aprsLatPreset == "0000.00N" || aprsLatPreset == "0000.00S") && (aprsLonPreset == "00000.00W" || aprsLonPreset == "00000.00E")) { - // aprs spec: default null position. - // The null position should be include the \. symbol (unknown/indeterminate position). - outString += "0000.00N\00000.00W."; - may_add_dao_extension = false; - } else if (position_ambiguity > 0) { - char buf[10]; // room for 00000.00W + \0 == 10 + if (position_ambiguity > 0) { + char buf[10]; // room for 00000.00E + \0 == 10 int n; int pos; // Only change mm.hh in dd[d]mm.hh, due to spec. Not degrees. @@ -839,6 +840,14 @@ out_relay_path: outString += " Batt="; outString += String(BattVolts, 2); outString += ("V"); + #ifdef T_BEAM_V1_2 + outString = outString + "/" + String(axp.getBatteryPercent()) + "%"; + if (axp.isCharging()) { + outString += "+"; // is charging -> indicate with "+" + } else if (!axp.isVbusIn()) { + outString += "-"; // not charging and no vbus -> is discharging -> indicate with "-" + } + #endif } if (InpVolts > 1.0) { outString += " P="; @@ -1341,11 +1350,19 @@ String getSatAndBatInfo() { } line5 = line5 + "/" + charge + "mA"; #elif T_BEAM_V1_2 - if (InpVolts > 1.0) { + if (axp.isVbusIn()) { line5 = line5 + " P:" + String(InpVolts, 2) + "V"; } else { line5 = line5 + " B:" + String(BattVolts, 2) + "V"; } + if (axp.isBatteryConnect()) { + line5 = line5 + "/" + String(axp.getBatteryPercent()) + "%"; + if (axp.isCharging()) { + line5 += "+"; // is charging -> indicate with "+" + } else if (!axp.isVbusIn()) { + line5 += "-"; // not charging and no vbus -> is discharging -> indicate with "-" + } + } #else line5 = line5 + " P:" + String(InpVolts, 2) + "V"; #endif @@ -2253,8 +2270,8 @@ boolean readFile(fs::FS &fs, const char *filename) { if ( (JSONBuffer.containsKey("SelfAP_PW") && (p = JSONBuffer["SelfAP_PW"])) || (JSONBuffer.containsKey("ap_password") && (p = JSONBuffer["ap_password"])) ) { String ap_password = String(p); - if (ap_password.length() && ap_password.length() > 7) { - wifi_ModeAP_PASS = ap_password; + if (ap_password.length() > 7) { + wifi_ModeAP_PASS = String(ap_password); Serial.printf("readFile: wifi.cfg, valid Self-AP PW to be used %s\r\n", wifi_ModeAP_PASS.c_str()); } } @@ -2435,8 +2452,8 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { char helper_base91[] = {"0\0"}; char nswe; float f; - double fLon = 0; - double fLat = 0; + double fLon = 0.0; + double fLat = 0.0; p = sLat.c_str(); // some assurance @@ -2451,18 +2468,23 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { for (int i = 0; q[i]; i++) { if (i == 2) { if (p[2] != '-' || q[2] != '-') { - return storeLatLonPreset("0000.00N", "00000.00W", 0); + return storeLatLonPreset("00-00.00N", "000-00.0000W", 0); } } else if (i == 5) { if ((p[5] != '.' || q[5] != '.')) { - return storeLatLonPreset("0000.00N", "00000.00W", 0); + return storeLatLonPreset("00-00.00N", "000-00.0000W", 0); } } else if (p[i+1] && q[i+1] && (!isdigit(p[i]) || !isdigit(q[i]))) { - return storeLatLonPreset("0000.00N", "00000.00W", 0); + return storeLatLonPreset("00-00.00N", "000-00.0000W", 0); } } } else { - return storeLatLonPreset("0000.00N", "00000.00W", 0); + return storeLatLonPreset("00-00.00N", "000-00.0000W", 0); + } + + if (aprsLatLonInvalidPosition) { + // APRS special notation 00000.00W means, no valid position; 00000.00E would be a correct position + aprsLatLonInvalidPosition = (sLon == "000-00.0000W" && sLat == "00-00.0000N"); } // We have stored the manual position string in a heigher precision (in case resolution more precise than 18.52m is required; i.e. for base-91 location encoding, or DAO extenstion). @@ -2477,24 +2499,25 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { sprintf(buf, "%.2s%05.2f%c", p, (f > 59.99 ? 59.99 : f), nswe); tmp_aprsLatPreset = String(buf); - if (sLat.length() > 9 && (precision == 1 || precision == 2)) { + if (!aprsLatLonInvalidPosition && sLat.length() > 9 && (precision == 1 || precision == 2)) { + float fp = f; if (precision == 1) { - if (f > 59.999) f=59.999; + if (fp > 59.999) fp=59.999; } else { - if (f > 59.9999) f=59.9999; + if (fp > 59.9999) fp=59.9999; } if (precision == 1) { - sprintf(buf, "%.2s%06.3f", p, f); + sprintf(buf, "%.2s%06.3f", p, fp); tmp_aprsLatLonDAO = String("!W") + String(buf[7]); } else { - sprintf(buf, "%.2s%07.4f", p, f); + sprintf(buf, "%.2s%07.4f", p, fp); ax25_base91enc(helper_base91, 1, atoi(buf+7)*0.91); tmp_aprsLatLonDAO = String("!w") + helper_base91[0]; } buf[7] = nswe; buf[8] = 0;; tmp_aprsLatPresetDAO = String(buf); // "NiceNotation" may be used for presenting at oled. Four decimals would be too hard to read. We use 3 decimals for both, precision 1 and precision 2. - sprintf(buf, "%.2s-%06.3f%c", p, f, nswe); + sprintf(buf, "%.2s-%06.3f%c", p, (f > 59.999 ? 59.999 : f), nswe); tmp_aprsLatPresetNiceNotation = String(buf); } else { tmp_aprsLatPresetDAO = tmp_aprsLatPreset; @@ -2502,7 +2525,7 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { sprintf(buf, "%.2s-%05.2f%c", p, (f > 59.99 ? 59.99 : f), nswe); tmp_aprsLatPresetNiceNotation = String(buf); } - if (aprsLatLonPresetCOMP.isEmpty()) { + if (!aprsLatLonInvalidPosition && aprsLatLonPresetCOMP.isEmpty()) { sprintf(buf, "%.2s", p); fLat = atof(buf) + f/60.0; if (nswe == 'S') @@ -2520,17 +2543,18 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { sprintf(buf, "%.3s%05.2f%c", p, (f > 59.99 ? 59.99 : f), nswe); tmp_aprsLonPreset = String(buf); - if (sLon.length() > 10 && (precision == 1 || precision == 2)) { + if (!aprsLatLonInvalidPosition && sLon.length() > 10 && (precision == 1 || precision == 2)) { + float fp = f; if (precision == 1) { - if (f > 59.999) f=59.999; + if (fp > 59.999) fp=59.999; } else { - if (f > 59.9999) f=59.9999; + if (fp > 59.9999) fp=59.9999; } if (precision == 1) { - sprintf(buf, "%.3s%06.3f", p, f); + sprintf(buf, "%.3s%06.3f", p, fp); tmp_aprsLatLonDAO = tmp_aprsLatLonDAO + String(buf[8]) + "!"; } else { - sprintf(buf, "%.3s%07.4f", p, f); + sprintf(buf, "%.3s%07.4f", p, fp); ax25_base91enc(helper_base91, 1, atoi(buf+7)*0.91); tmp_aprsLatLonDAO = tmp_aprsLatLonDAO + helper_base91[0] + "!"; } @@ -2538,7 +2562,7 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { tmp_aprsLonPresetDAO = String(buf); // "NiceNotation" may be used for presenting at oled. Four decimals would be too hard to read. We use 3 decimals (see precision 1 above). // Furthermore, we don't have enough space on oled anyway for one additional character. - sprintf(buf, "%.3s-%06.3f%c", p, f, nswe); + sprintf(buf, "%.3s-%06.3f%c", p, (f > 59.999 ? 59.999 : f), nswe); tmp_aprsLonPresetNiceNotation = String(buf); tmp_aprsLatLonAsMaidenheadGridLocator = compute_maidenhead_grid_locator(tmp_aprsLatPresetNiceNotation, tmp_aprsLonPresetNiceNotation, 0); } else { @@ -2548,7 +2572,7 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { tmp_aprsLonPresetNiceNotation = String(buf); tmp_aprsLatLonAsMaidenheadGridLocator = compute_maidenhead_grid_locator(tmp_aprsLatPresetNiceNotation, tmp_aprsLonPresetNiceNotation, 1); } - if (aprsLatLonPresetCOMP.isEmpty()) { + if (!aprsLatLonInvalidPosition && aprsLatLonPresetCOMP.isEmpty()) { sprintf(buf, "%.3s", p); fLon = atof(buf) + f/60.0; if (nswe == 'W') @@ -2572,7 +2596,7 @@ int storeLatLonPreset(String _sLat, String _sLon, int precision) { aprsLatLonAsMaidenheadGridLocator = String(tmp_aprsLatLonAsMaidenheadGridLocator); // Only when called from a function called from setup_phase2_soft_reconfiguration (after boot oder config save) with preset position (indicator: aprsLatLonPresetCOMP is empty). // Else: store_compressed_position() is explicitely called right after this function - if (aprsLatLonPresetCOMP.isEmpty()) + if (!aprsLatLonInvalidPosition && aprsLatLonPresetCOMP.isEmpty()) store_compressed_position(fLat, fLon); return 0; @@ -2591,6 +2615,7 @@ void init_and_validate_aprs_position_and_icon() { latlon_precision = 0; } + aprsLatLonInvalidPosition = true; aprsLatPresetFromPreferences.toUpperCase(); aprsLatPresetFromPreferences.replace(",", "."); aprsLatPresetFromPreferences.trim(); aprsLonPresetFromPreferences.toUpperCase(); aprsLonPresetFromPreferences.replace(",", "."); aprsLonPresetFromPreferences.trim(); if ( aprsLatPresetFromPreferences.length() == 11 && @@ -2613,7 +2638,9 @@ void init_and_validate_aprs_position_and_icon() { aprsLonPreset.length() != 9 || !(aprsLonPreset.endsWith("E") || aprsLonPreset.endsWith("W")) || aprsLonPreset.c_str()[5] != '.' || aprsLatPresetDAO.length() != 8 || !(aprsLatPresetDAO.endsWith("N") || aprsLatPresetDAO.endsWith("S")) || aprsLatPresetDAO.c_str()[4] != '.' || aprsLonPresetDAO.length() != 9 || !(aprsLonPresetDAO.endsWith("E") || aprsLonPresetDAO.endsWith("W")) || aprsLonPresetDAO.c_str()[5] != '.') { - storeLatLonPreset("0000.00N", "00000.00W", 0); + aprsLatLonPresetCOMP = ""; + aprsLatLonInvalidPosition = true; + storeLatLonPreset("00-00.00N", "000-00.0000W", 0); } if (aprsSymbolTable.length() != 1) @@ -3345,11 +3372,11 @@ void setup_phase2_soft_reconfiguration(boolean runtime_reconfiguration) { // LoRa Chip config // if we are fill-in or wide2 digi, we listen only on configured main frequency - lora_speed_rx_curr = (rx_on_frequencies != 2 || lora_digipeating_mode > 1) ? lora_speed : lora_speed_cross_digi; + lora_speed_rx_curr = (rx_on_frequencies != 2 || lora_digipeating_mode > 1) ? lora_speed : lora_speed_cross_digi; lora_set_speed(lora_speed_rx_curr); Serial.printf("LoRa Speed:\t%lu\r\n", lora_speed_rx_curr); - lora_freq_rx_curr = (rx_on_frequencies != 2 || lora_digipeating_mode > 1) ? lora_freq : lora_freq_cross_digi; + lora_freq_rx_curr = (rx_on_frequencies != 2 || lora_digipeating_mode > 1) ? lora_freq : lora_freq_cross_digi; rf95.setFrequency(lora_freq_rx_curr); Serial.printf("LoRa FREQ:\t%f\r\n", lora_freq_rx_curr); @@ -3527,6 +3554,7 @@ void setup() if (!axp.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL)) { ; } + axp.disableTSPinMeasure(); axp.setDC1Voltage(3300); axp.enableDC1(); // oled do not turn off axp.setDC2Voltage(3300); @@ -3538,10 +3566,16 @@ void setup() axp.enableSystemVoltageMeasure(); axp.enableVbusVoltageMeasure(); axp.enableBattVoltageMeasure(); - axp.enableTSPinMeasure(); axp.setChargingLedMode(XPOWERS_CHG_LED_OFF); + // set the charging voltage axp.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); + // set the charging current axp.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); + // Battery-friendly settings: + // Set the precharge current + axp.setPrechargeCurr(XPOWERS_AXP2101_PRECHARGE_200MA); + // Set the charging termination current + axp.setChargerTerminationCurr(XPOWERS_AXP2101_CHG_ITERM_25MA); #endif // can reduce cpu power consumtion up to 20 % @@ -5201,7 +5235,7 @@ debug_bestHdop = bestHdop; #if defined(T_BEAM_V1_0) || defined(T_BEAM_V1_2) if(shutdown_active){ - if(InpVolts> 4){ + if(InpVolts > 4){ shutdown_usb_status_bef = true; shutdown_countdown_timer_enable = false; } diff --git a/src/taskWebServer.cpp b/src/taskWebServer.cpp index c3a8a89..069cb50 100644 --- a/src/taskWebServer.cpp +++ b/src/taskWebServer.cpp @@ -169,18 +169,28 @@ WiFiClient aprsis_client; void sendCacheHeader() { server.sendHeader("Cache-Control", "max-age=3600"); } void sendGzipHeader() { server.sendHeader("Content-Encoding", "gzip"); } -String jsonEscape(String s){ - const char *p = s.c_str(); + +String htmlFreetextEscape(const char *s) { + const char *p = s; String s_out; for (; *p; p++) { char buf[7] = ""; // room for "<0x01>" + \0 == 7 switch (*p) { - case '\\': - strcpy(buf, "\\\\"); - break; case '\"': - strcpy(buf, "\\\""); + strcpy(buf, """); + break; + case '<': + strcpy(buf, "<"); + break; + case '>': + strcpy(buf, ">"); + break; + case '&': + strcpy(buf, "&"); + break; + case ' ': + strcpy(buf, " "); break; default: if (*p < 0x20 || *p == '\x7f') { @@ -194,8 +204,29 @@ String jsonEscape(String s){ return s_out; } +String jsonEscape(const char *s){ + const char *p = s; + String s_out; + + for (; *p; p++) { + char buf[3] = ""; // room for 2x'\' + \0 == 7 + switch (*p) { + case '\\': + strcpy(buf, "\\\\"); + break; + case '\"': + strcpy(buf, "\\\""); + break; + default: + sprintf(buf, "%c", *p); + } + s_out += String(buf); + } + return s_out; +} + String jsonLineFromPreferenceString(const char *preferenceName, bool last=false){ - return String("\"") + preferenceName + "\":\"" + jsonEscape(preferences.getString(preferenceName, "")) + (last ? + R"(")" : + R"(",)"); + return String("\"") + preferenceName + "\":\"" + jsonEscape(preferences.getString(preferenceName, "").c_str()) + (last ? + R"(")" : + R"(",)"); } String jsonLineFromPreferenceBool(const char *preferenceName, bool last=false){ return String("\"") + preferenceName + "\":" + (preferences.getBool(preferenceName) ? "true" : "false") + (last ? + R"()" : + R"(,)"); @@ -411,8 +442,8 @@ void refill_preferences_as_jsonData() { String s_tmp; String s = "{"; - s = s + "\n " + String("\"") + PREF_WIFI_PASSWORD + "\": \"" + jsonEscape((preferences.getString(PREF_WIFI_PASSWORD, "").isEmpty() ? String("") : "*")) + R"(",)"; - s = s + "\n " + String("\"") + PREF_AP_PASSWORD + "\": \"" + jsonEscape((preferences.getString(PREF_AP_PASSWORD, "").isEmpty() ? String("") : "*")) + R"(",)"; + s = s + "\n " + String("\"") + PREF_WIFI_PASSWORD + "\": \"" + jsonEscape((preferences.getString(PREF_WIFI_PASSWORD, "").isEmpty() ? "" : "*")) + R"(",)"; + s = s + "\n " + String("\"") + PREF_AP_PASSWORD + "\": \"" + jsonEscape((preferences.getString(PREF_AP_PASSWORD, "").isEmpty() ? "" : "*")) + R"(",)"; s = s + "\n " + jsonLineFromPreferenceInt(PREF_WIFI_ENABLE); s = s + "\n " + jsonLineFromPreferenceString(PREF_WIFI_SSID); s = s + "\n " + jsonLineFromPreferenceBool(PREF_WIFI_STA_ALLOW_FAILBACK_TO_MODE_AP_AFTER_ONCE_CONNECTED); @@ -547,9 +578,9 @@ void fill_wifi_config_as_jsonData() { if (apcnt) { for (pos = 0 ; pos < apcnt; pos++) { s += " {\n "; - s = s + "\"SSID\": \"" + jsonEscape(String(APs[pos].ssid)) + "\""; + s = s + "\"SSID\": \"" + jsonEscape(APs[pos].ssid) + "\""; s += ",\n "; - s = s + "\"password\": \"" + jsonEscape(String(APs[pos].pw)) + "\""; + s = s + "\"password\": \"" + jsonEscape(APs[pos].pw) + "\""; if (!pos) { s += ",\n "; s = s + "\"prio\": 1"; @@ -561,13 +592,13 @@ void fill_wifi_config_as_jsonData() { } } else { s += " {\n "; - s = s + "\"SSID\": \"" + jsonEscape(preferences.getString(PREF_WIFI_PASSWORD, "")) + "\""; + s = s + "\"SSID\": \"" + jsonEscape(preferences.getString(PREF_WIFI_PASSWORD, "").c_str()) + "\""; s += ",\n "; - s = s + "\"password\": \"" + jsonEscape(preferences.getString(PREF_WIFI_PASSWORD, "")) + "\""; + s = s + "\"password\": \"" + jsonEscape(preferences.getString(PREF_WIFI_PASSWORD, "").c_str()) + "\""; s += "\n }\n"; } s += " ],\n\n"; - s = s + " \"SelfAP_PW\": \"" + jsonEscape(preferences.getString(PREF_AP_PASSWORD)) + "\""; + s = s + " \"SelfAP_PW\": \"" + jsonEscape(preferences.getString(PREF_AP_PASSWORD, "").c_str()) + "\""; s += "\n}\n"; @@ -610,7 +641,7 @@ void handle_ReceivedList() { strftime(buf, 64, "%Y-%m-%d %H:%M:%S", &element->rxTime); auto packet_data = received.createNestedObject(); packet_data["time"] = String(buf); - packet_data["packet"] = element->packet->c_str(); + packet_data["packet"] = htmlFreetextEscape(element->packet->c_str()); packet_data["rssi"] = element->RSSI; packet_data["snr"] = element->SNR; } @@ -631,7 +662,7 @@ void handle_ReceivedList() { jsonData += ",{"; } jsonData += jsonLineFromString("time", buf); - jsonData += jsonLineFromString("packet", element->packet->c_str()); + jsonData += jsonLineFromString("packet", htmlFreetextEscape(element->packet->c_str()).c_str()); jsonData += jsonLineFromInt("rssi", element->RSSI); jsonData += jsonLineFromInt("snr", element->SNR, true); jsonData += "}";