From 91bc051e6d9d4bdc09747f8c45f7b9bf700946b5 Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sun, 3 Oct 2021 15:52:09 -0400 Subject: [PATCH 01/18] Create GeoCoord class --- src/gps/GeoCoord.cpp | 347 +++++++++++++++++++++++++++++++++++++++++++ src/gps/GeoCoord.h | 154 +++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 src/gps/GeoCoord.cpp create mode 100644 src/gps/GeoCoord.h diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp new file mode 100644 index 000000000..fbf0e90f4 --- /dev/null +++ b/src/gps/GeoCoord.cpp @@ -0,0 +1,347 @@ +#include "GeoCoord.h" + +GeoCoord::GeoCoord() { + _dirty = true; +} + +GeoCoord::GeoCoord (int32_t lat, int32_t lon, int32_t alt) : _lattitude(lat), _longitude(lon), _altidude(alt) { + GeoCoord::setCoords(); +} + +GeoCoord::GeoCoord (float lat, float lon, int32_t alt) : _altidude(alt) { + // Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000 + _lattitude = int32_t(lat * 1e+7); + _longitude = int32_t(lon * 1e+7); + GeoCoord::setCoords(); +} + +GeoCoord::GeoCoord(double lat, double lon, int32_t alt): _altidude(alt) { + // Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000 + _lattitude = int32_t(lat * 1e+7); + _longitude = int32_t(lon * 1e+7); + GeoCoord::setCoords(); +} + +// Initialize all the coordinate systems +void GeoCoord::setCoords() { + double lat = _lattitude * 1e-7; + double lon = _longitude * 1e-7; + GeoCoord::latLongToDMS(lat, lon, _dms); + GeoCoord::latLongToUTM(lat, lon, _utm); + GeoCoord::latLongToMGRS(lat, lon, _mgrs); + GeoCoord::latLongToOSGR(lat, lon, _osgr); + GeoCoord::latLongToOLC(lat, lon, _olc); + _dirty = false; +} + +void GeoCoord::updateCoords(int32_t lat, int32_t lon, int32_t alt) { + // If marked dirty or new coordiantes + if(_dirty || _lattitude != lat || _longitude != lon || _altidude != alt) { + _dirty = true; + _lattitude = lat; + _longitude = lon; + _altidude = alt; + setCoords(); + } +} + +void GeoCoord::updateCoords(const double lat, const double lon, const int32_t alt) { + int32_t iLat = lat * 1e+7; + int32_t iLon = lon * 1e+7; + // If marked dirty or new coordiantes + if(_dirty || _lattitude != iLat || _longitude != iLon || _altidude != alt) { + _dirty = true; + _lattitude = iLat; + _longitude = iLon; + _altidude = alt; + setCoords(); + } + +} + +void GeoCoord::updateCoords(const float lat, const float lon, const int32_t alt) { + int32_t iLat = lat * 1e+7; + int32_t iLon = lon * 1e+7; + // If marked dirty or new coordiantes + if(_dirty || _lattitude != iLat || _longitude != iLon || _altidude != alt) { + _dirty = true; + _lattitude = iLat; + _longitude = iLon; + _altidude = alt; + setCoords(); + } +} + +/** + * Converts lat long coordinates from decimal degrees to degrees minutes seconds format. + * DD°MM'SS"C DDD°MM'SS"C + */ +void GeoCoord::latLongToDMS(const double lat, const double lon, DMS &dms) { + if (lat < 0) dms.latCP = 'S'; + else dms.latCP = 'N'; + + double latDeg = lat; + + if (lat < 0) + latDeg = latDeg * -1; + + dms.latDeg = floor(latDeg); + double latMin = (latDeg - dms.latDeg) * 60; + dms.latMin = floor(latMin); + dms.latSec = (latMin - dms.latMin) * 60; + + if (lon < 0) dms.lonCP = 'W'; + else dms.lonCP = 'E'; + + double lonDeg = lon; + + if (lon < 0) + lonDeg = lonDeg * -1; + + dms.lonDeg = floor(lonDeg); + double lonMin = (lonDeg - dms.lonDeg) * 60; + dms.lonMin = floor(lonMin); + dms.lonSec = (lonMin - dms.lonMin) * 60; +} + +/** + * Converts lat long coordinates to UTM. + * based on this: https://github.com/walvok/LatLonToUTM/blob/master/latlon_utm.ino + */ +void GeoCoord::latLongToUTM(const double lat, const double lon, UTM &utm) { + + const std::string latBands = "CDEFGHJKLMNPQRSTUVWXX"; + utm.zone = int((lon + 180)/6 + 1); + utm.band = latBands[int(lat/8 + 10)]; + double a = 6378137; // WGS84 - equatorial radius + double k0 = 0.9996; // UTM point scale on the central meridian + double eccSquared = 0.00669438; // eccentricity squared + double lonTemp = (lon + 180) - int((lon + 180)/360) * 360 - 180; //Make sure the longitude is between -180.00 .. 179.9 + double latRad = toRadians(lat); + double lonRad = toRadians(lonTemp); + + // Special Zones for Norway and Svalbard + if( lat >= 56.0 && lat < 64.0 && lonTemp >= 3.0 && lonTemp < 12.0 ) // Norway + utm.zone = 32; + if( lat >= 72.0 && lat < 84.0 ) { // Svalbard + if ( lonTemp >= 0.0 && lonTemp < 9.0 ) utm.zone = 31; + else if( lonTemp >= 9.0 && lonTemp < 21.0 ) utm.zone = 33; + else if( lonTemp >= 21.0 && lonTemp < 33.0 ) utm.zone = 35; + else if( lonTemp >= 33.0 && lonTemp < 42.0 ) utm.zone = 37; + } + + double lonOrigin = (utm.zone - 1)*6 - 180 + 3; // puts origin in middle of zone + double lonOriginRad = toRadians(lonOrigin); + double eccPrimeSquared = (eccSquared)/(1 - eccSquared); + double N = a/sqrt(1 - eccSquared*sin(latRad)*sin(latRad)); + double T = tan(latRad)*tan(latRad); + double C = eccPrimeSquared*cos(latRad)*cos(latRad); + double A = cos(latRad)*(lonRad - lonOriginRad); + double M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256)*latRad + - (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*latRad) + + (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*latRad) + - (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*latRad)); + utm.easting = (double)(k0*N*(A+(1-T+C)*pow(A, 3)/6 + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120) + + 500000.0); + utm.northing = (double)(k0*(M+N*tan(latRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24 + + (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720))); + + if(lat < 0) + utm.northing += 10000000.0; //10000000 meter offset for southern hemisphere +} + +// Converts lat long coordinates to an MGRS. +void GeoCoord::latLongToMGRS(const double lat, const double lon, MGRS &mgrs) { + const std::string e100kLetters[3] = { "ABCDEFGH", "JKLMNPQR", "STUVWXYZ" }; + const std::string n100kLetters[2] = { "ABCDEFGHJKLMNPQRSTUV", "FGHJKLMNPQRSTUVABCDE" }; + UTM utm; + latLongToUTM(lat, lon, utm); + mgrs.zone = utm.zone; + mgrs.band = utm.band; + double col = floor(utm.easting / 100000); + mgrs.east100k = e100kLetters[(mgrs.zone - 1) % 3][col - 1]; + double row = (int32_t)floor(utm.northing / 100000.0) % 20; + mgrs.north100k = n100kLetters[(mgrs.zone-1)%2][row]; + mgrs.easting = (int32_t)utm.easting % 100000; + mgrs.northing = (int32_t)utm.northing % 100000; +} + +/** + * Converts lat long coordinates to Ordnance Survey Grid Reference (UK National Grid Ref). + * Based on: https://www.movable-type.co.uk/scripts/latlong-os-gridref.html + */ +void GeoCoord::latLongToOSGR(const double lat, const double lon, OSGR &osgr) { + char letter[] = "ABCDEFGHJKLMNOPQRSTUVWXYZ"; // No 'I' in OSGR + double a = 6377563.396; // Airy 1830 semi-major axis + double b = 6356256.909; // Airy 1830 semi-minor axis + double f0 = 0.9996012717; // National Grid point scale factor on the central meridian + double phi0 = toRadians(49); + double lambda0 = toRadians(-2); + double n0 = -100000; + double e0 = 400000; + double e2 = 1 - (b*b)/(a*a); // eccentricity squared + double n = (a - b)/(a + b); + + double osgb_Latitude; + double osgb_Longitude; + convertWGS84ToOSGB36(lat, lon, osgb_Latitude, osgb_Longitude); + double phi = osgb_Latitude; // already in radians + double lambda = osgb_Longitude; // already in radians + double v = a * f0 / sqrt(1 - e2 * sin(phi) * sin(phi)); + double rho = a * f0 * (1 - e2) / pow(1 - e2 * sin(phi) * sin(phi), 1.5); + double eta2 = v / rho - 1; + double mA = (1 + n + (5/4)*n*n + (5/4)*n*n*n) * (phi - phi0); + double mB = (3*n + 3*n*n + (21/8)*n*n*n) * sin(phi - phi0) * cos(phi + phi0); + // loss of precision in mC & mD due to floating point rounding can cause innaccuracy of northing by a few meters + double mC = (15/8*n*n + 15/8*n*n*n) * sin(2*(phi - phi0)) * cos(2*(phi + phi0)); + double mD = (35/24)*n*n*n * sin(3*(phi - phi0)) * cos(3*(phi + phi0)); + double m = b*f0*(mA - mB + mC - mD); + + double cos3Phi = cos(phi)*cos(phi)*cos(phi); + double cos5Phi = cos3Phi*cos(phi)*cos(phi); + double tan2Phi = tan(phi)*tan(phi); + double tan4Phi = tan2Phi*tan2Phi; + double I = m + n0; + double II = (v/2)*sin(phi)*cos(phi); + double III = (v/24)*sin(phi)*cos3Phi*(5 - tan2Phi + 9*eta2); + double IIIA = (v/720)*sin(phi)*cos5Phi*(61 - 58*tan2Phi + tan4Phi); + double IV = v*cos(phi); + double V = (v/6)*cos3Phi*(v/rho - tan2Phi); + double VI = (v/120)*cos5Phi*(5 - 18*tan2Phi + tan4Phi + 14*eta2 - 58*tan2Phi*eta2); + + double deltaLambda = lambda - lambda0; + double deltaLambda2 = deltaLambda*deltaLambda; + double northing = I + II*deltaLambda2 + III*deltaLambda2*deltaLambda2 + IIIA*deltaLambda2*deltaLambda2*deltaLambda2; + double easting = e0 + IV*deltaLambda + V*deltaLambda2*deltaLambda + VI*deltaLambda2*deltaLambda2*deltaLambda; + + if (easting < 0 || easting > 700000 || northing < 0 || northing > 1300000) // Check if out of boundaries + osgr = { 'I', 'I', 0, 0 }; + else { + uint32_t e100k = floor(easting / 100000); + uint32_t n100k = floor(northing / 100000); + int8_t l1 = (19 - n100k) - (19 - n100k) % 5 + floor((e100k + 10) / 5); + int8_t l2 = (19 - n100k) * 5 % 25 + e100k % 5; + osgr.e100k = letter[l1]; + osgr.n100k = letter[l2]; + osgr.easting = floor((int)easting % 100000); + osgr.northing = floor((int)northing % 100000); + } +} + +/** + * Converts lat long coordinates to Open Location Code. + * Based on: https://github.com/google/open-location-code/blob/main/c/src/olc.c + */ +void GeoCoord::latLongToOLC(double lat, double lon, OLC &olc) { + char tempCode[] = "1234567890abc"; + const char kAlphabet[] = "23456789CFGHJMPQRVWX"; + double latitude; + double longitude = lon; + double latitude_degrees = std::min(90.0, std::max(-90.0, lat)); + + if (latitude_degrees < 90) // Check latitude less than lat max + latitude = latitude_degrees; + else { + double precision; + if (OLC_CODE_LEN <= 10) + precision = pow_neg(20, floor((OLC_CODE_LEN / -2) + 2)); + else + precision = pow_neg(20, -3) / pow(5, OLC_CODE_LEN - 10); + latitude = latitude_degrees - precision / 2; + } + while (longitude < -180) // Normalize longitude + longitude += 360; + while (longitude >= 180) + longitude -= 360; + int64_t lat_val = 90 * 2.5e7; + int64_t lng_val = 180 * 8.192e6; + lat_val += latitude * 2.5e7; + lng_val += longitude * 8.192e6; + size_t pos = OLC_CODE_LEN; + + if (OLC_CODE_LEN > 10) { // Compute grid part of code if needed + for (size_t i = 0; i < 5; i++) { + int lat_digit = lat_val % 5; + int lng_digit = lng_val % 4; + int ndx = lat_digit * 4 + lng_digit; + tempCode[pos--] = kAlphabet[ndx]; + lat_val /= 5; + lng_val /= 4; + } + } else { + lat_val /= pow(5, 5); + lng_val /= pow(4, 5); + } + + pos = 10; + + for (size_t i = 0; i < 5; i++) { // Compute pair section of code + int lat_ndx = lat_val % 20; + int lng_ndx = lng_val % 20; + tempCode[pos--] = kAlphabet[lng_ndx]; + tempCode[pos--] = kAlphabet[lat_ndx]; + lat_val /= 20; + lng_val /= 20; + + if (i == 0) + tempCode[pos--] = '+'; + } + + if (OLC_CODE_LEN < 9) { // Add padding if needed + for (size_t i = OLC_CODE_LEN; i < 9; i++) + tempCode[i] = '0'; + tempCode[9] = '+'; + } + + size_t char_count = OLC_CODE_LEN; + if (10 > char_count) { + char_count = 10; + } + for (size_t i = 0; i < char_count; i++) { + olc.code[i] = tempCode[i]; + } + olc.code[char_count] = '\0'; +} + +// Converts the coordinate in WGS84 datum to the OSGB36 datum. +void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude) { + // Convert lat long to cartesian + double phi = toRadians(lat); + double lambda = toRadians(lon); + double h = 0.0; // No OSTN height data used, some loss of accuracy (up to 5m) + double wgsA = 6378137; // WGS84 datum semi major axis + double wgsF = 1 / 298.257223563; // WGS84 datum flattening + double ecc = 2*wgsF - wgsF*wgsF; + double vee = wgsA / sqrt(1 - ecc * pow(sin(phi), 2)); + double wgsX = (vee + h) * cos(phi) * cos(lambda); + double wgsY = (vee + h) * cos(phi) * sin(lambda); + double wgsZ = ((1 - ecc) * vee + h) * sin(phi); + + // 7-parameter Helmert transform + double tx = -446.448; // x shift in meters + double ty = 125.157; // y shift in meters + double tz = -542.060; // z shift in meters + double s = 20.4894/1e6 + 1; // scale normalized parts per million to (s + 1) + double rx = toRadians(-0.1502/3600); // x rotation normalize arcseconds to radians + double ry = toRadians(-0.2470/3600); // y rotation normalize arcseconds to radians + double rz = toRadians(-0.8421/3600); // z rotation normalize arcseconds to radians + double osgbX = tx + wgsX*s - wgsY*rz + wgsZ*ry; + double osgbY = ty + wgsX*rz + wgsY*s - wgsZ*rx; + double osgbZ = tz - wgsX*ry + wgsY*rx + wgsZ*s; + + // Convert cartesian to lat long + double airyA = 6377563.396; // Airy1830 datum semi major axis + double airyB = 6356256.909; // Airy1830 datum semi minor axis + double airyF = 1/ 299.3249646; // Airy1830 datum flattening + double airyEcc = 2*airyF - airyF*airyF; + double airyEcc2 = airyEcc / (1 - airyEcc); + double p = sqrt(osgbX*osgbX + osgbY*osgbY); + double R = sqrt(p*p + osgbZ*osgbZ); + double tanBeta = (airyB*osgbZ) / (airyA*p) * (1 + airyEcc2*airyB/R); + double sinBeta = tanBeta / sqrt(1 + tanBeta*tanBeta); + double cosBeta = sinBeta / tanBeta; + osgb_Latitude = atan2(osgbZ + airyEcc2*airyB*sinBeta*sinBeta*sinBeta, p - airyEcc*airyA*cosBeta*cosBeta*cosBeta); // leave in radians + osgb_Longitude = atan2(osgbY, osgbX); // leave in radians + //osgb height = p*cos(osgb.latitude) + osgbZ*sin(osgb.latitude) - + //(airyA*airyA/(airyA / sqrt(1 - airyEcc*sin(osgb.latitude)*sin(osgb.latitude)))); // Not used, no OSTN data +} \ No newline at end of file diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h new file mode 100644 index 000000000..d982ae24d --- /dev/null +++ b/src/gps/GeoCoord.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define PI 3.1415926535897932384626433832795 +#define OLC_CODE_LEN 11 + +// Helper functions +// Raises a number to an exponent, handling negative exponents. +static double pow_neg(double base, double exponent) { + if (exponent == 0) { + return 1; + } else if (exponent > 0) { + return pow(base, exponent); + } + return 1 / pow(base, -exponent); +} + +static inline double toRadians(double deg) +{ + return deg * PI / 180; +} + +static inline double toDegrees(double r) +{ + return r * 180 / PI; +} + +// GeoCoord structs/classes +// A struct to hold the data for a DMS coordinate. +struct DMS +{ + uint8_t latDeg; + uint8_t latMin; + uint32_t latSec; + char latCP; + uint8_t lonDeg; + uint8_t lonMin; + uint32_t lonSec; + char lonCP; +}; + +// A struct to hold the data for a UTM coordinate, this is also used when creating an MGRS coordinate. +struct UTM +{ + uint8_t zone; + char band; + uint32_t easting; + uint32_t northing; +}; + +// A struct to hold the data for a MGRS coordinate. +struct MGRS +{ + uint8_t zone; + char band; + char east100k; + char north100k; + uint32_t easting; + uint32_t northing; +}; + +// A struct to hold the data for a OSGR coordiante +struct OSGR { + char e100k; + char n100k; + uint32_t easting; + uint32_t northing; +}; + +// A struct to hold the data for a OLC coordinate +struct OLC { + char code[OLC_CODE_LEN + 1]; // +1 for null termination +}; + +class GeoCoord { + private: + int32_t _lattitude = 0; + int32_t _longitude = 0; + int32_t _altidude = 0; + + DMS _dms; + UTM _utm; + MGRS _mgrs; + OSGR _osgr; + OLC _olc; + + bool _dirty = true; + + void setCoords(); + + public: + GeoCoord(); + GeoCoord(int32_t lat, int32_t lon, int32_t alt); + GeoCoord(double lat, double lon, int32_t alt); + GeoCoord(float lat, float lon, int32_t alt); + + void updateCoords(const int32_t lat, const int32_t lon, const int32_t alt); + void updateCoords(const double lat, const double lon, const int32_t alt); + void updateCoords(const float lat, const float lon, const int32_t alt); + + // Conversions + static void latLongToDMS(const double lat, const double lon, DMS &dms); + static void latLongToUTM(const double lat, const double lon, UTM &utm); + static void latLongToMGRS(const double lat, const double lon, MGRS &mgrs); + static void latLongToOSGR(const double lat, const double lon, OSGR &osgr); + static void latLongToOLC(const double lat, const double lon, OLC &olc); + static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude); + + // Lat lon alt getters + int32_t getLatitude() const { return _lattitude; } + int32_t getLongitude() const { return _longitude; } + int32_t getAltitude() const { return _altidude; } + + // DMS getters + uint8_t getDMSLatDeg() const { return _dms.latDeg; } + uint8_t getDMSLatMin() const { return _dms.latMin; } + uint32_t getDMSLatSec() const { return _dms.latSec; } + char getDMSLatCP() const { return _dms.latCP; } + uint8_t getDMSLonDeg() const { return _dms.lonDeg; } + uint8_t getDMSLonMin() const { return _dms.lonMin; } + uint32_t getDMSLonSec() const { return _dms.lonSec; } + char getDMSLonCP() const { return _dms.lonCP; } + + // UTM getters + uint8_t getUTMZone() const { return _utm.zone; } + char getUTMBand() const { return _utm.band; } + uint32_t getUTMEasting() const { return _utm.easting; } + uint32_t getUTMNorthing() const { return _utm.northing; } + + // MGRS getters + uint8_t getMGRSZone() const { return _mgrs.zone; } + char getMGRSBand() const { return _mgrs.band; } + char getMGRSEast100k() const { return _mgrs.east100k; } + char getMGRSNorth100k() const { return _mgrs.north100k; } + uint32_t getMGRSEasting() const { return _mgrs.easting; } + uint32_t getMGRSNorthing() const { return _mgrs.northing; } + + // OSGR getters + char getOSGRE100k() const { return _osgr.e100k; } + char getOSGRN100k() const { return _osgr.n100k; } + uint32_t getOSGREasting() const { return _osgr.easting; } + uint32_t getOSGRNorthing() const { return _osgr.northing; } + + // OLC getter + void getOLCCode(char* code) { strncpy(code, _olc.code, OLC_CODE_LEN + 1); } // +1 for null termination +}; + From bf695a5f36e673c6ef7d15a81bb3cf922666118e Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sun, 3 Oct 2021 15:52:46 -0400 Subject: [PATCH 02/18] Use GeoCoord class in Screen.cpp --- src/graphics/Screen.cpp | 389 ++-------------------------------------- 1 file changed, 18 insertions(+), 371 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f889cf95b..8fd8c8956 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -35,6 +35,7 @@ along with this program. If not, see . #include "plugins/TextMessagePlugin.h" #include "target_specific.h" #include "utils.h" +#include "gps/GeoCoord.h" #ifndef NO_ESP32 #include "mesh/http/WiFiAPClient.h" @@ -72,6 +73,9 @@ std::vector pluginFrames; // Stores the last 4 of our hardware ID, to make finding the device for pairing easier static char ourId[5]; +// GeoCoord object for the screen +GeoCoord geoCoord; + #ifdef SHOW_REDRAWS static bool heartbeat = false; #endif @@ -385,363 +389,6 @@ static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GP } } -static inline double toRadians(double deg) -{ - return deg * PI / 180; -} - -static inline double toDegrees(double r) -{ - return r * 180 / PI; -} - -// A struct to hold the data for a DMS coordinate. -struct DMS -{ - byte latDeg; - byte latMin; - double latSec; - char latCP; - byte lonDeg; - byte lonMin; - double lonSec; - char lonCP; -}; - -// A struct to hold the data for a UTM coordinate, this is also used when creating an MGRS coordinate. -struct UTM -{ - byte zone; - char band; - double easting; - double northing; -}; - -// A struct to hold the data for a MGRS coordinate. -struct MGRS -{ - byte zone; - char band; - char east100k; - char north100k; - uint32_t easting; - uint32_t northing; -}; - -/** - * Converts lat long coordinates to UTM. - * based on this: https://github.com/walvok/LatLonToUTM/blob/master/latlon_utm.ino - */ -static struct UTM latLongToUTM(const double lat, const double lon) -{ - const String latBands = "CDEFGHJKLMNPQRSTUVWXX"; - UTM utm; - utm.zone = int((lon + 180)/6 + 1); - utm.band = latBands.charAt(int(lat/8 + 10)); - double a = 6378137; // WGS84 - equatorial radius - double k0 = 0.9996; // UTM point scale on the central meridian - double eccSquared = 0.00669438; // eccentricity squared - double lonTemp = (lon + 180) - int((lon + 180)/360) * 360 - 180; //Make sure the longitude is between -180.00 .. 179.9 - double latRad = toRadians(lat); - double lonRad = toRadians(lonTemp); - - // Special Zones for Norway and Svalbard - if( lat >= 56.0 && lat < 64.0 && lonTemp >= 3.0 && lonTemp < 12.0 ) // Norway - utm.zone = 32; - if( lat >= 72.0 && lat < 84.0 ) { // Svalbard - if ( lonTemp >= 0.0 && lonTemp < 9.0 ) utm.zone = 31; - else if( lonTemp >= 9.0 && lonTemp < 21.0 ) utm.zone = 33; - else if( lonTemp >= 21.0 && lonTemp < 33.0 ) utm.zone = 35; - else if( lonTemp >= 33.0 && lonTemp < 42.0 ) utm.zone = 37; - } - - double lonOrigin = (utm.zone - 1)*6 - 180 + 3; // puts origin in middle of zone - double lonOriginRad = toRadians(lonOrigin); - double eccPrimeSquared = (eccSquared)/(1 - eccSquared); - double N = a/sqrt(1 - eccSquared*sin(latRad)*sin(latRad)); - double T = tan(latRad)*tan(latRad); - double C = eccPrimeSquared*cos(latRad)*cos(latRad); - double A = cos(latRad)*(lonRad - lonOriginRad); - double M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256)*latRad - - (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*latRad) - + (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*latRad) - - (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*latRad)); - utm.easting = (double)(k0*N*(A+(1-T+C)*pow(A, 3)/6 + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120) - + 500000.0); - utm.northing = (double)(k0*(M+N*tan(latRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24 - + (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720))); - - if(lat < 0) - utm.northing += 10000000.0; //10000000 meter offset for southern hemisphere - - return utm; -} - -// Converts lat long coordinates to an MGRS. -static struct MGRS latLongToMGRS(double lat, double lon) -{ - const String e100kLetters[3] = { "ABCDEFGH", "JKLMNPQR", "STUVWXYZ" }; - const String n100kLetters[2] = { "ABCDEFGHJKLMNPQRSTUV", "FGHJKLMNPQRSTUVABCDE" }; - UTM utm = latLongToUTM(lat, lon); - MGRS mgrs; - mgrs.zone = utm.zone; - mgrs.band = utm.band; - double col = floor(utm.easting / 100000); - mgrs.east100k = e100kLetters[(mgrs.zone - 1) % 3].charAt(col - 1); - double row = (int)floor(utm.northing / 100000.0) % 20; - mgrs.north100k = n100kLetters[(mgrs.zone-1)%2].charAt(row); - mgrs.easting = (int)utm.easting % 100000; - mgrs.northing = (int)utm.northing % 100000; - return mgrs; -} - -/** - * Converts lat long coordinates from decimal degrees to degrees minutes seconds format. - * DD°MM'SS"C DDD°MM'SS"C - */ -static struct DMS latLongToDMS(double lat, double lon) -{ - DMS dms; - - if (lat < 0) dms.latCP = 'S'; - else dms.latCP = 'N'; - - double latDeg = lat; - - if (lat < 0) - latDeg = latDeg * -1; - - dms.latDeg = floor(latDeg); - double latMin = (latDeg - dms.latDeg) * 60; - dms.latMin = floor(latMin); - dms.latSec = (latMin - dms.latMin) * 60; - - if (lon < 0) dms.lonCP = 'W'; - else dms.lonCP = 'E'; - - double lonDeg = lon; - - if (lon < 0) - lonDeg = lonDeg * -1; - - dms.lonDeg = floor(lonDeg); - double lonMin = (lonDeg - dms.lonDeg) * 60; - dms.lonMin = floor(lonMin); - dms.lonSec = (lonMin - dms.lonMin) * 60; - - return dms; -} - -// Raises a number to an exponent, handling negative exponents. -static double pow_neg(double base, double exponent) { - if (exponent == 0) { - return 1; - } else if (exponent > 0) { - return pow(base, exponent); - } - return 1 / pow(base, -exponent); -} - -/** - * Converts lat long coordinates to Open Location Code. - * Based on: https://github.com/google/open-location-code/blob/main/c/src/olc.c - */ -static void latLongToOLC(double lat, double lon, char* code) { - char tempCode[] = "1234567890abc"; - const char kAlphabet[] = "23456789CFGHJMPQRVWX"; - const byte CODE_LEN = 12; - double latitude; - double longitude = lon; - double latitude_degrees = min(90.0, max(-90.0, lat)); - - if (latitude_degrees < 90) // Check latitude less than lat max - latitude = latitude_degrees; - else { - double precision; - if (CODE_LEN <= 10) - precision = pow_neg(20, floor((CODE_LEN / -2) + 2)); - else - precision = pow_neg(20, -3) / pow(5, CODE_LEN - 10); - latitude = latitude_degrees - precision / 2; - } - - while (lon < -180) // Normalize longitude - longitude += 360; - while (lon >= 180) - longitude -= 360; - - int64_t lat_val = 90 * 2.5e7; - int64_t lng_val = 180 * 8.192e6; - lat_val += latitude * 2.5e7; - lng_val += longitude * 8.192e6; - size_t pos = CODE_LEN; - - if (CODE_LEN > 10) { // Compute grid part of code if needed - for (size_t i = 0; i < 5; i++) { - int lat_digit = lat_val % 5; - int lng_digit = lng_val % 4; - int ndx = lat_digit * 4 + lng_digit; - tempCode[pos--] = kAlphabet[ndx]; - lat_val /= 5; - lng_val /= 4; - } - } else { - lat_val /= pow(5, 5); - lng_val /= pow(4, 5); - } - - pos = 10; - - for (size_t i = 0; i < 5; i++) { // Compute pair section of code - int lat_ndx = lat_val % 20; - int lng_ndx = lng_val % 20; - tempCode[pos--] = kAlphabet[lng_ndx]; - tempCode[pos--] = kAlphabet[lat_ndx]; - lat_val /= 20; - lng_val /= 20; - - if (i == 0) - tempCode[pos--] = '+'; - } - - if (CODE_LEN < 9) { // Add padding if needed - for (size_t i = CODE_LEN; i < 9; i++) - tempCode[i] = '0'; - tempCode[9] = '+'; - } - - size_t char_count = CODE_LEN + 1; - if (10 > char_count) { - char_count = 10; - } - for (size_t i = 0; i < char_count; i++) { - code[i] = tempCode[i]; - } - - code[char_count] = '\0'; -} - -struct GeoCoord { - double latitude; - double longitude; - double height; -}; - -struct OSGR { - char e100k; - char n100k; - uint32_t easting; - uint32_t northing; -}; - -// Converts the coordinate in WGS84 datum to the OSGB36 datum. -static struct GeoCoord convertWGS84ToOSGB36(double lat, double lon) { - // Convert lat long to cartesian - double phi = toRadians(lat); - double lambda = toRadians(lon); - double h = 0.0; // No OSTN height data used, some loss of accuracy (up to 5m) - double wgsA = 6378137; // WGS84 datum semi major axis - double wgsF = 1 / 298.257223563; // WGS84 datum flattening - double ecc = 2*wgsF - wgsF*wgsF; - double vee = wgsA / sqrt(1 - ecc * pow(sin(phi), 2)); - double wgsX = (vee + h) * cos(phi) * cos(lambda); - double wgsY = (vee + h) * cos(phi) * sin(lambda); - double wgsZ = ((1 - ecc) * vee + h) * sin(phi); - - // 7-parameter Helmert transform - double tx = -446.448; // x shift in meters - double ty = 125.157; // y shift in meters - double tz = -542.060; // z shift in meters - double s = 20.4894/1e6 + 1; // scale normalized parts per million to (s + 1) - double rx = toRadians(-0.1502/3600); // x rotation normalize arcseconds to radians - double ry = toRadians(-0.2470/3600); // y rotation normalize arcseconds to radians - double rz = toRadians(-0.8421/3600); // z rotation normalize arcseconds to radians - double osgbX = tx + wgsX*s - wgsY*rz + wgsZ*ry; - double osgbY = ty + wgsX*rz + wgsY*s - wgsZ*rx; - double osgbZ = tz - wgsX*ry + wgsY*rx + wgsZ*s; - - // Convert cartesian to lat long - double airyA = 6377563.396; // Airy1830 datum semi major axis - double airyB = 6356256.909; // Airy1830 datum semi minor axis - double airyF = 1/ 299.3249646; // Airy1830 datum flattening - GeoCoord osgb; - double airyEcc = 2*airyF - airyF*airyF; - double airyEcc2 = airyEcc / (1 - airyEcc); - double p = sqrt(osgbX*osgbX + osgbY*osgbY); - double R = sqrt(p*p + osgbZ*osgbZ); - double tanBeta = (airyB*osgbZ) / (airyA*p) * (1 + airyEcc2*airyB/R); - double sinBeta = tanBeta / sqrt(1 + tanBeta*tanBeta); - double cosBeta = sinBeta / tanBeta; - osgb.latitude = atan2(osgbZ + airyEcc2*airyB*sinBeta*sinBeta*sinBeta, p - airyEcc*airyA*cosBeta*cosBeta*cosBeta); // leave in radians - osgb.longitude = atan2(osgbY, osgbX); // leave in radians - osgb.height = 0; - //osgb height = p*cos(osgb.latitude) + osgbZ*sin(osgb.latitude) - - //(airyA*airyA/(airyA / sqrt(1 - airyEcc*sin(osgb.latitude)*sin(osgb.latitude)))); // Not used, no OSTN data - return osgb; -} - -/** - * Converts lat long coordinates to Ordnance Survey Grid Reference (UK National Grid Ref). - * Based on: https://www.movable-type.co.uk/scripts/latlong-os-gridref.html - */ -static struct OSGR latLongToOSGR(double lat, double lon) { - char letter[] = "ABCDEFGHJKLMNOPQRSTUVWXYZ"; // No 'I' in OSGR - double a = 6377563.396; // Airy 1830 semi-major axis - double b = 6356256.909; // Airy 1830 semi-minor axis - double f0 = 0.9996012717; // National Grid point scale factor on the central meridian - double phi0 = toRadians(49); - double lambda0 = toRadians(-2); - double n0 = -100000; - double e0 = 400000; - double e2 = 1 - (b*b)/(a*a); // eccentricity squared - double n = (a - b)/(a + b); - - GeoCoord osgb = convertWGS84ToOSGB36(lat, lon); - double phi = osgb.latitude; // already in radians - double lambda = osgb.longitude; // already in radians - double v = a * f0 / sqrt(1 - e2 * sin(phi) * sin(phi)); - double rho = a * f0 * (1 - e2) / pow(1 - e2 * sin(phi) * sin(phi), 1.5); - double eta2 = v / rho - 1; - double mA = (1 + n + (5/4)*n*n + (5/4)*n*n*n) * (phi - phi0); - double mB = (3*n + 3*n*n + (21/8)*n*n*n) * sin(phi - phi0) * cos(phi + phi0); - // loss of precision in mC & mD due to floating point rounding can cause innaccuracy of northing by a few meters - double mC = (15/8*n*n + 15/8*n*n*n) * sin(2*(phi - phi0)) * cos(2*(phi + phi0)); - double mD = (35/24)*n*n*n * sin(3*(phi - phi0)) * cos(3*(phi + phi0)); - double m = b*f0*(mA - mB + mC - mD); - - double cos3Phi = cos(phi)*cos(phi)*cos(phi); - double cos5Phi = cos3Phi*cos(phi)*cos(phi); - double tan2Phi = tan(phi)*tan(phi); - double tan4Phi = tan2Phi*tan2Phi; - double I = m + n0; - double II = (v/2)*sin(phi)*cos(phi); - double III = (v/24)*sin(phi)*cos3Phi*(5 - tan2Phi + 9*eta2); - double IIIA = (v/720)*sin(phi)*cos5Phi*(61 - 58*tan2Phi + tan4Phi); - double IV = v*cos(phi); - double V = (v/6)*cos3Phi*(v/rho - tan2Phi); - double VI = (v/120)*cos5Phi*(5 - 18*tan2Phi + tan4Phi + 14*eta2 - 58*tan2Phi*eta2); - - double deltaLambda = lambda - lambda0; - double deltaLambda2 = deltaLambda*deltaLambda; - double northing = I + II*deltaLambda2 + III*deltaLambda2*deltaLambda2 + IIIA*deltaLambda2*deltaLambda2*deltaLambda2; - double easting = e0 + IV*deltaLambda + V*deltaLambda2*deltaLambda + VI*deltaLambda2*deltaLambda2*deltaLambda; - - OSGR osgr; - if (easting < 0 || easting > 700000 || northing < 0 || northing > 1300000) // Check if out of boundaries - osgr = { 'I', 'I', 0, 0 }; - else { - uint32_t e100k = floor(easting / 100000); - uint32_t n100k = floor(northing / 100000); - byte l1 = (19 - n100k) - (19 - n100k) % 5 + floor((e100k + 10) / 5); - byte l2 = (19 - n100k) * 5 % 25 + e100k % 5; - osgr.e100k = letter[l1]; - osgr.n100k = letter[l2]; - osgr.easting = floor((int)easting % 100000); - osgr.northing = floor((int)northing % 100000); - } - return osgr; -} - // Draw GPS status coordinates static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) { @@ -757,33 +404,33 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } else { if (gpsFormat != GpsCoordinateFormat_GpsFormatDMS) { char coordinateLine[22]; - + geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); if (gpsFormat == GpsCoordinateFormat_GpsFormatDec) { // Decimal Degrees - sprintf(coordinateLine, "%f %f", gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7); + sprintf(coordinateLine, "%f %f", geoCoord.getLatitude() * 1e-7, geoCoord.getLongitude() * 1e-7); } else if (gpsFormat == GpsCoordinateFormat_GpsFormatUTM) { // Universal Transverse Mercator - UTM utm = latLongToUTM(gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7); - sprintf(coordinateLine, "%2i%1c %06.0f %07.0f", utm.zone, utm.band, utm.easting, utm.northing); + sprintf(coordinateLine, "%2i%1c %06i %07i", geoCoord.getUTMZone(), geoCoord.getUTMBand(), + geoCoord.getUTMEasting(), geoCoord.getUTMNorthing()); } else if (gpsFormat == GpsCoordinateFormat_GpsFormatMGRS) { // Military Grid Reference System - MGRS mgrs = latLongToMGRS(gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7); - sprintf(coordinateLine, "%2i%1c %1c%1c %05i %05i", mgrs.zone, mgrs.band, mgrs.east100k, mgrs.north100k, - mgrs.easting, mgrs.northing); + sprintf(coordinateLine, "%2i%1c %1c%1c %05i %05i", geoCoord.getMGRSZone(), geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), + geoCoord.getMGRSNorth100k(), geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing()); } else if (gpsFormat == GpsCoordinateFormat_GpsFormatOLC) { // Open Location Code - latLongToOLC(gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7, coordinateLine); + geoCoord.getOLCCode(coordinateLine); } else if (gpsFormat == GpsCoordinateFormat_GpsFormatOSGR) { // Ordnance Survey Grid Reference - OSGR osgr = latLongToOSGR(gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7); - if (osgr.e100k == 'I' || osgr.n100k == 'I') // OSGR is only valid around the UK region + if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region sprintf(coordinateLine, "%s", "Out of Boundary"); else - sprintf(coordinateLine, "%1c%1c %05i %05i", osgr.e100k, osgr.n100k, osgr.easting, osgr.northing); + sprintf(coordinateLine, "%1c%1c %05i %05i", geoCoord.getOSGRE100k(),geoCoord.getOSGRN100k(), + geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing()); } display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine); } else { char latLine[22]; char lonLine[22]; - DMS dms = latLongToDMS(gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7); - sprintf(latLine, "%2i° %2i' %2.4f\" %1c", dms.latDeg, dms.latMin, dms.latSec, dms.latCP); - sprintf(lonLine, "%3i° %2i' %2.4f\" %1c", dms.lonDeg, dms.lonMin, dms.lonSec, dms.lonCP); + sprintf(latLine, "%2i° %2i' %2.4f\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(), + geoCoord.getDMSLatCP()); + sprintf(lonLine, "%3i° %2i' %2.4f\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(), + geoCoord.getDMSLonCP()); display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine); display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine); } From aa936ade7e0e30eff466b88ce450addc1702472b Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sun, 3 Oct 2021 16:01:41 -0400 Subject: [PATCH 03/18] Use geoCoord object to draw altitude --- src/graphics/Screen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 8fd8c8956..bd08218fd 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -383,8 +383,8 @@ static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GP // displayLine = "No GPS Lock"; // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); } else { - - displayLine = "Altitude: " + String(gps->getAltitude()) + "m"; + geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); + displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m"; display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); } } From 8bbcdaa95170311b7c9defb9a69171c7fe5653ac Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sat, 9 Oct 2021 15:56:30 +1100 Subject: [PATCH 04/18] Bundle WebUI --- .github/workflows/main.yml | 15 +++++++++++++++ data/static/.gitkeep | 0 data/static/index.html | 1 - 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 data/static/.gitkeep delete mode 100644 data/static/index.html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c0f0eb1a..fb1ec7c07 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,6 +58,21 @@ jobs: run: | pio upgrade + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: "meshtastic/meshtastic-web" + file: "build.tar" + target: "build.tar" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + + + # We now run integration test before other build steps (to quickly see runtime failures) - name: Build for native run: platformio run -e native diff --git a/data/static/.gitkeep b/data/static/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/data/static/index.html b/data/static/index.html deleted file mode 100644 index 9297288c7..000000000 --- a/data/static/index.html +++ /dev/null @@ -1 +0,0 @@ -not yet supported - soon will be included in build From 99357e427bdf17b6798d86c353e06126c181e8e8 Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sat, 9 Oct 2021 16:44:00 +1100 Subject: [PATCH 05/18] Include release workflow --- .github/workflows/main.yml | 2 -- .github/workflows/release.yml | 13 +++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb1ec7c07..0425f13fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,8 +71,6 @@ jobs: tar -xf build.tar -C data/static rm build.tar - - # We now run integration test before other build steps (to quickly see runtime failures) - name: Build for native run: platformio run -e native diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3403a145d..7e4e259a6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,19 @@ jobs: run: | pio upgrade + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: "meshtastic/meshtastic-web" + file: "build.tar" + target: "build.tar" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + # Will be available in steps.version.outputs.version - name: Get version string run: echo "::set-output name=version::$(./bin/buildinfo.py long)" From dc436a3cc919d0ba8d421b9e3d5b02af15427dc6 Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sat, 9 Oct 2021 17:15:12 +1100 Subject: [PATCH 06/18] Bundle WebUI (#878) * Bundle WebUI * Include release workflow --- .github/workflows/main.yml | 13 +++++++++++++ .github/workflows/release.yml | 13 +++++++++++++ data/static/.gitkeep | 0 data/static/index.html | 1 - 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 data/static/.gitkeep delete mode 100644 data/static/index.html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c0f0eb1a..0425f13fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,6 +58,19 @@ jobs: run: | pio upgrade + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: "meshtastic/meshtastic-web" + file: "build.tar" + target: "build.tar" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + # We now run integration test before other build steps (to quickly see runtime failures) - name: Build for native run: platformio run -e native diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3403a145d..7e4e259a6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,19 @@ jobs: run: | pio upgrade + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@master + with: + repo: "meshtastic/meshtastic-web" + file: "build.tar" + target: "build.tar" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Unpack web ui + run: | + tar -xf build.tar -C data/static + rm build.tar + # Will be available in steps.version.outputs.version - name: Get version string run: echo "::set-output name=version::$(./bin/buildinfo.py long)" diff --git a/data/static/.gitkeep b/data/static/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/data/static/index.html b/data/static/index.html deleted file mode 100644 index 9297288c7..000000000 --- a/data/static/index.html +++ /dev/null @@ -1 +0,0 @@ -not yet supported - soon will be included in build From ee9c72b8c787ee1cc964265a6e1dcfcbd629fe13 Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:48:30 +0000 Subject: [PATCH 07/18] issue 879 - define source types --- src/mesh/MeshTypes.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 34d4f02c9..d45194d4f 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -15,6 +15,14 @@ typedef uint32_t PacketId; // A packet sequence number #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER #define ERRNO_DISABLED 34 // the itnerface is disabled +/* + * Source of a received message + */ +enum RxSource { + RX_SRC_LOCAL, // message was generated locally + RX_SRC_RADIO // message was received from radio mesh +}; + /** * the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket. * From 5eb2e6401f324f967922e6453be68eb6dfe88926 Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:54:42 +0000 Subject: [PATCH 08/18] issue 879 - changes to Router.cpp --- src/mesh/Router.cpp | 11 +++++++---- src/mesh/Router.h | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1c5d70101..3cf81be2f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -161,7 +161,7 @@ ErrorCode Router::sendLocal(MeshPacket *p) // If we are sending a broadcast, we also treat it as if we just received it ourself // this allows local apps (and PCs) to see broadcasts sourced locally if (p->to == NODENUM_BROADCAST) { - handleReceived(p); + handleReceived(p, RX_SRC_LOCAL); } return send(p); @@ -324,7 +324,7 @@ NodeNum Router::getNodeNum() * Handle any packet that is received by an interface on this node. * Note: some packets may merely being passed through this node and will be forwarded elsewhere. */ -void Router::handleReceived(MeshPacket *p) +void Router::handleReceived(MeshPacket *p, RxSource src) { // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone @@ -333,13 +333,16 @@ void Router::handleReceived(MeshPacket *p) bool decoded = perhapsDecode(p); if (decoded) { // parsing was successful, queue for our recipient - printPacket("handleReceived", p); + if (src == RX_SRC_LOCAL) + printPacket("handleReceived(local)", p); + else + printPacket("handleReceived(remote)", p); } else { printPacket("packet decoding failed (no PSK?)", p); } // call plugins here - MeshPlugin::callPlugins(*p); + MeshPlugin::callPlugins(*p, src); } void Router::perhapsHandleReceived(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 9d7358af5..519f158f7 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -122,7 +122,7 @@ class Router : protected concurrency::OSThread * Note: this packet will never be called for messages sent/generated by this node. * Note: this method will free the provided packet. */ - void handleReceived(MeshPacket *p); + void handleReceived(MeshPacket *p, RxSource src = RX_SRC_RADIO); /** Frees the provided packet, and generates a NAK indicating the speicifed error while sending */ void abortSendAndNak(Routing_Error err, MeshPacket *p); From c3ebe80f53f9d1f76ee3bd314fbcb48f79ffc2ab Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:57:56 +0000 Subject: [PATCH 09/18] issue 879 - add opt-in flag for plugins --- MeshPlugin.cpp | 234 +++++++++++++++++++++++++++++++++++++++++++++++++ MeshPlugin.h | 135 ++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+) create mode 100644 MeshPlugin.cpp create mode 100644 MeshPlugin.h diff --git a/MeshPlugin.cpp b/MeshPlugin.cpp new file mode 100644 index 000000000..4ebd2f4d7 --- /dev/null +++ b/MeshPlugin.cpp @@ -0,0 +1,234 @@ +#include "configuration.h" +#include "MeshPlugin.h" +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "plugins/RoutingPlugin.h" +#include + +std::vector *MeshPlugin::plugins; + +const MeshPacket *MeshPlugin::currentRequest; + +/** + * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow + * the RoutingPlugin to avoid sending redundant acks + */ +MeshPacket *MeshPlugin::currentReply; + +MeshPlugin::MeshPlugin(const char *_name) : name(_name) +{ + // Can't trust static initalizer order, so we check each time + if (!plugins) + plugins = new std::vector(); + + plugins->push_back(this); +} + +void MeshPlugin::setup() {} + +MeshPlugin::~MeshPlugin() +{ + assert(0); // FIXME - remove from list of plugins once someone needs this feature +} + +MeshPacket *MeshPlugin::allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +{ + Routing c = Routing_init_default; + + c.error_reason = err; + c.which_variant = Routing_error_reason_tag; + + // Now that we have moded sendAckNak up one level into the class heirarchy we can no longer assume we are a RoutingPlugin + // So we manually call pb_encode_to_bytes and specify routing port number + // auto p = allocDataProtobuf(c); + MeshPacket *p = router->allocForSending(); + p->decoded.portnum = PortNum_ROUTING_APP; + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Routing_fields, &c); + + p->priority = MeshPacket_Priority_ACK; + + p->hop_limit = 0; // Assume just immediate neighbors for now + p->to = to; + p->decoded.request_id = idFrom; + p->channel = chIndex; + DEBUG_MSG("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); + + return p; +} + +MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *p) +{ + auto r = allocAckNak(err, getFrom(p), p->id, p->channel); + + setReplyTo(r, *p); + + return r; +} + +void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src) +{ + // DEBUG_MSG("In call plugins\n"); + bool pluginFound = false; + + // We now allow **encrypted** packets to pass through the plugins + bool isDecoded = mp.which_payloadVariant == MeshPacket_decoded_tag; + + currentReply = NULL; // No reply yet + + // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets + auto ourNodeNum = nodeDB.getNodeNum(); + bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; + + for (auto i = plugins->begin(); i != plugins->end(); ++i) { + auto &pi = **i; + + pi.currentRequest = ∓ + + /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) + bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); + + if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) { + // new case, monitor separately for now, then FIXME merge above + wantsPacket = false; + } + + assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time + + if (wantsPacket) { + DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket); + + pluginFound = true; + + /// received channel (or NULL if not decoded) + Channel *ch = isDecoded ? &channels.getByIndex(mp.channel) : NULL; + + /// Is the channel this packet arrived on acceptable? (security check) + /// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins + + /// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and it needs to + /// to be able to fetch the initial admin packets without yet knowing any channels. + + bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && (strcmp(ch->settings.name, pi.boundChannel) == 0)); + + if (!rxChannelOk) { + // no one should have already replied! + assert(!currentReply); + + if (mp.decoded.want_response) { + printPacket("packet on wrong channel, returning error", &mp); + currentReply = pi.allocErrorResponse(Routing_Error_NOT_AUTHORIZED, &mp); + } else + printPacket("packet on wrong channel, but can't respond", &mp); + } else { + + bool handled = pi.handleReceived(mp); + + // Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious + // sniffing) also: we only let the one plugin send a reply, once that happens, remaining plugins are not + // considered + + // NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary + // because currently when the phone sends things, it sends things using the local node ID as the from address. A + // better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like + // any other node. + if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) { + pi.sendResponse(mp); + DEBUG_MSG("Plugin %s sent a response\n", pi.name); + } else { + DEBUG_MSG("Plugin %s considered\n", pi.name); + } + + // If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks + if (pi.myReply) { + DEBUG_MSG("Discarding an unneeded response\n"); + packetPool.release(pi.myReply); + pi.myReply = NULL; + } + + if (handled) { + DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name); + break; + } + } + } + + pi.currentRequest = NULL; + } + + if (mp.decoded.want_response && toUs) { + if (currentReply) { + printPacket("Sending response", currentReply); + service.sendToMesh(currentReply); + currentReply = NULL; + } else if(mp.from != ourNodeNum) { + // Note: if the message started with the local node we don't want to send a no response reply + + // No one wanted to reply to this requst, tell the requster that happened + DEBUG_MSG("No one responded, send a nak\n"); + + // SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded) + // but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs + // bad. + routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); + } + } + + if (!pluginFound) + DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n", + mp.decoded.portnum, + (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE"); +} + +MeshPacket *MeshPlugin::allocReply() +{ + auto r = myReply; + myReply = NULL; // Only use each reply once + return r; +} + +/** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. Implementing this method + * is optional + */ +void MeshPlugin::sendResponse(const MeshPacket &req) +{ + auto r = allocReply(); + if (r) { + setReplyTo(r, req); + currentReply = r; + } else { + // Ignore - this is now expected behavior for routing plugin (because it ignores some replies) + // DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); + } +} + +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet + * This ensures that if the request packet was sent reliably, the reply is sent that way as well. + */ +void setReplyTo(MeshPacket *p, const MeshPacket &to) +{ + assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now + p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0 + p->channel = to.channel; // Use the same channel that the request came in on + + // No need for an ack if we are just delivering locally (it just generates an ignored ack) + p->want_ack = (to.from != 0) ? to.want_ack : false; + if (p->priority == MeshPacket_Priority_UNSET) + p->priority = MeshPacket_Priority_RELIABLE; + p->decoded.request_id = to.id; +} + +std::vector MeshPlugin::GetMeshPluginsWithUIFrames() +{ + + std::vector pluginsWithUIFrames; + for (auto i = plugins->begin(); i != plugins->end(); ++i) { + auto &pi = **i; + if (pi.wantUIFrame()) { + DEBUG_MSG("Plugin wants a UI Frame\n"); + pluginsWithUIFrames.push_back(&pi); + } + } + return pluginsWithUIFrames; +} diff --git a/MeshPlugin.h b/MeshPlugin.h new file mode 100644 index 000000000..2350c951a --- /dev/null +++ b/MeshPlugin.h @@ -0,0 +1,135 @@ +#pragma once + +#include "mesh/Channels.h" +#include "mesh/MeshTypes.h" +#include + +#ifndef NO_SCREEN +#include +#include +#endif + +/** A baseclass for any mesh "plugin". + * + * A plugin allows you to add new features to meshtastic device code, without needing to know messaging details. + * + * A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive + * and handle. + * + * Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You + * can use these classes as examples for how to write your own custom plugin. See here: (FIXME) + */ +class MeshPlugin +{ + static std::vector *plugins; + + public: + /** Constructor + * name is for debugging output + */ + MeshPlugin(const char *_name); + + virtual ~MeshPlugin(); + + /** For use only by MeshService + */ + static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO); + + static std::vector GetMeshPluginsWithUIFrames(); +#ifndef NO_SCREEN + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } +#endif + protected: + const char *name; + + /** Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific + recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those + plugins can set this to true and their handleReceived() will be called for every packet. + */ + bool isPromiscuous = false; + + /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave + * this setting disabled - see issue #877 */ + bool loopbackOk = false; + + /** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this + * flag */ + bool encryptedOk = false; + + /** If a bound channel name is set, we will only accept received packets that come in on that channel. + * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface + * are allowed on any channel (this lets the local user do anything). + * + * We will send responses on the same channel that the request arrived on. + */ + const char *boundChannel = NULL; + + /** + * If this plugin is currently handling a request currentRequest will be preset + * to the packet with the request. This is mostly useful for reply handlers. + * + * Note: this can be static because we are guaranteed to be processing only one + * plugin at a time. + */ + static const MeshPacket *currentRequest; + + /** + * If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling. + */ + MeshPacket *myReply = NULL; + + /** + * Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have + * been initialized + */ + virtual void setup(); + + /** + * @return true if you want to receive the specified portnum + */ + virtual bool wantPacket(const MeshPacket *p) = 0; + + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceived(const MeshPacket &mp) { return false; } + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. + * + * Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set + * the protected reply field in this instance. + * */ + virtual MeshPacket *allocReply(); + + /*** + * @return true if you want to be alloced a UI screen frame + */ + virtual bool wantUIFrame() { return false; } + + MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + + /// Send an error response for the specified packet. + MeshPacket *allocErrorResponse(Routing_Error err, const MeshPacket *p); + + private: + /** + * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow + * the RoutingPlugin to avoid sending redundant acks + */ + static MeshPacket *currentReply; + + friend class ReliableRouter; + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply() + * to generate the reply message, and if !NULL that message will be delivered to whoever sent req + */ + void sendResponse(const MeshPacket &req); +}; + +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet + * This ensures that if the request packet was sent reliably, the reply is sent that way as well. + */ +void setReplyTo(MeshPacket *p, const MeshPacket &to); \ No newline at end of file From 26330120ce852202fa048c97743eab096d172afe Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:59:54 +0000 Subject: [PATCH 10/18] oops --- MeshPlugin.cpp | 234 ------------------------------------------------- 1 file changed, 234 deletions(-) delete mode 100644 MeshPlugin.cpp diff --git a/MeshPlugin.cpp b/MeshPlugin.cpp deleted file mode 100644 index 4ebd2f4d7..000000000 --- a/MeshPlugin.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#include "configuration.h" -#include "MeshPlugin.h" -#include "Channels.h" -#include "MeshService.h" -#include "NodeDB.h" -#include "plugins/RoutingPlugin.h" -#include - -std::vector *MeshPlugin::plugins; - -const MeshPacket *MeshPlugin::currentRequest; - -/** - * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow - * the RoutingPlugin to avoid sending redundant acks - */ -MeshPacket *MeshPlugin::currentReply; - -MeshPlugin::MeshPlugin(const char *_name) : name(_name) -{ - // Can't trust static initalizer order, so we check each time - if (!plugins) - plugins = new std::vector(); - - plugins->push_back(this); -} - -void MeshPlugin::setup() {} - -MeshPlugin::~MeshPlugin() -{ - assert(0); // FIXME - remove from list of plugins once someone needs this feature -} - -MeshPacket *MeshPlugin::allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) -{ - Routing c = Routing_init_default; - - c.error_reason = err; - c.which_variant = Routing_error_reason_tag; - - // Now that we have moded sendAckNak up one level into the class heirarchy we can no longer assume we are a RoutingPlugin - // So we manually call pb_encode_to_bytes and specify routing port number - // auto p = allocDataProtobuf(c); - MeshPacket *p = router->allocForSending(); - p->decoded.portnum = PortNum_ROUTING_APP; - p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Routing_fields, &c); - - p->priority = MeshPacket_Priority_ACK; - - p->hop_limit = 0; // Assume just immediate neighbors for now - p->to = to; - p->decoded.request_id = idFrom; - p->channel = chIndex; - DEBUG_MSG("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); - - return p; -} - -MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *p) -{ - auto r = allocAckNak(err, getFrom(p), p->id, p->channel); - - setReplyTo(r, *p); - - return r; -} - -void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src) -{ - // DEBUG_MSG("In call plugins\n"); - bool pluginFound = false; - - // We now allow **encrypted** packets to pass through the plugins - bool isDecoded = mp.which_payloadVariant == MeshPacket_decoded_tag; - - currentReply = NULL; // No reply yet - - // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets - auto ourNodeNum = nodeDB.getNodeNum(); - bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; - - for (auto i = plugins->begin(); i != plugins->end(); ++i) { - auto &pi = **i; - - pi.currentRequest = ∓ - - /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) - bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); - - if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) { - // new case, monitor separately for now, then FIXME merge above - wantsPacket = false; - } - - assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time - - if (wantsPacket) { - DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket); - - pluginFound = true; - - /// received channel (or NULL if not decoded) - Channel *ch = isDecoded ? &channels.getByIndex(mp.channel) : NULL; - - /// Is the channel this packet arrived on acceptable? (security check) - /// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins - - /// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and it needs to - /// to be able to fetch the initial admin packets without yet knowing any channels. - - bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && (strcmp(ch->settings.name, pi.boundChannel) == 0)); - - if (!rxChannelOk) { - // no one should have already replied! - assert(!currentReply); - - if (mp.decoded.want_response) { - printPacket("packet on wrong channel, returning error", &mp); - currentReply = pi.allocErrorResponse(Routing_Error_NOT_AUTHORIZED, &mp); - } else - printPacket("packet on wrong channel, but can't respond", &mp); - } else { - - bool handled = pi.handleReceived(mp); - - // Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious - // sniffing) also: we only let the one plugin send a reply, once that happens, remaining plugins are not - // considered - - // NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary - // because currently when the phone sends things, it sends things using the local node ID as the from address. A - // better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like - // any other node. - if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) { - pi.sendResponse(mp); - DEBUG_MSG("Plugin %s sent a response\n", pi.name); - } else { - DEBUG_MSG("Plugin %s considered\n", pi.name); - } - - // If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks - if (pi.myReply) { - DEBUG_MSG("Discarding an unneeded response\n"); - packetPool.release(pi.myReply); - pi.myReply = NULL; - } - - if (handled) { - DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name); - break; - } - } - } - - pi.currentRequest = NULL; - } - - if (mp.decoded.want_response && toUs) { - if (currentReply) { - printPacket("Sending response", currentReply); - service.sendToMesh(currentReply); - currentReply = NULL; - } else if(mp.from != ourNodeNum) { - // Note: if the message started with the local node we don't want to send a no response reply - - // No one wanted to reply to this requst, tell the requster that happened - DEBUG_MSG("No one responded, send a nak\n"); - - // SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded) - // but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs - // bad. - routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); - } - } - - if (!pluginFound) - DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n", - mp.decoded.portnum, - (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE"); -} - -MeshPacket *MeshPlugin::allocReply() -{ - auto r = myReply; - myReply = NULL; // Only use each reply once - return r; -} - -/** Messages can be received that have the want_response bit set. If set, this callback will be invoked - * so that subclasses can (optionally) send a response back to the original sender. Implementing this method - * is optional - */ -void MeshPlugin::sendResponse(const MeshPacket &req) -{ - auto r = allocReply(); - if (r) { - setReplyTo(r, req); - currentReply = r; - } else { - // Ignore - this is now expected behavior for routing plugin (because it ignores some replies) - // DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); - } -} - -/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet - * This ensures that if the request packet was sent reliably, the reply is sent that way as well. - */ -void setReplyTo(MeshPacket *p, const MeshPacket &to) -{ - assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now - p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0 - p->channel = to.channel; // Use the same channel that the request came in on - - // No need for an ack if we are just delivering locally (it just generates an ignored ack) - p->want_ack = (to.from != 0) ? to.want_ack : false; - if (p->priority == MeshPacket_Priority_UNSET) - p->priority = MeshPacket_Priority_RELIABLE; - p->decoded.request_id = to.id; -} - -std::vector MeshPlugin::GetMeshPluginsWithUIFrames() -{ - - std::vector pluginsWithUIFrames; - for (auto i = plugins->begin(); i != plugins->end(); ++i) { - auto &pi = **i; - if (pi.wantUIFrame()) { - DEBUG_MSG("Plugin wants a UI Frame\n"); - pluginsWithUIFrames.push_back(&pi); - } - } - return pluginsWithUIFrames; -} From 4669e4713ca7a65e1e690392da4bd10846094d32 Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 14:00:14 +0000 Subject: [PATCH 11/18] oops --- MeshPlugin.h | 135 --------------------------------------------------- 1 file changed, 135 deletions(-) delete mode 100644 MeshPlugin.h diff --git a/MeshPlugin.h b/MeshPlugin.h deleted file mode 100644 index 2350c951a..000000000 --- a/MeshPlugin.h +++ /dev/null @@ -1,135 +0,0 @@ -#pragma once - -#include "mesh/Channels.h" -#include "mesh/MeshTypes.h" -#include - -#ifndef NO_SCREEN -#include -#include -#endif - -/** A baseclass for any mesh "plugin". - * - * A plugin allows you to add new features to meshtastic device code, without needing to know messaging details. - * - * A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive - * and handle. - * - * Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You - * can use these classes as examples for how to write your own custom plugin. See here: (FIXME) - */ -class MeshPlugin -{ - static std::vector *plugins; - - public: - /** Constructor - * name is for debugging output - */ - MeshPlugin(const char *_name); - - virtual ~MeshPlugin(); - - /** For use only by MeshService - */ - static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO); - - static std::vector GetMeshPluginsWithUIFrames(); -#ifndef NO_SCREEN - virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } -#endif - protected: - const char *name; - - /** Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific - recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those - plugins can set this to true and their handleReceived() will be called for every packet. - */ - bool isPromiscuous = false; - - /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave - * this setting disabled - see issue #877 */ - bool loopbackOk = false; - - /** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this - * flag */ - bool encryptedOk = false; - - /** If a bound channel name is set, we will only accept received packets that come in on that channel. - * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface - * are allowed on any channel (this lets the local user do anything). - * - * We will send responses on the same channel that the request arrived on. - */ - const char *boundChannel = NULL; - - /** - * If this plugin is currently handling a request currentRequest will be preset - * to the packet with the request. This is mostly useful for reply handlers. - * - * Note: this can be static because we are guaranteed to be processing only one - * plugin at a time. - */ - static const MeshPacket *currentRequest; - - /** - * If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling. - */ - MeshPacket *myReply = NULL; - - /** - * Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have - * been initialized - */ - virtual void setup(); - - /** - * @return true if you want to receive the specified portnum - */ - virtual bool wantPacket(const MeshPacket *p) = 0; - - /** Called to handle a particular incoming message - - @return true if you've guaranteed you've handled this message and no other handlers should be considered for it - */ - virtual bool handleReceived(const MeshPacket &mp) { return false; } - - /** Messages can be received that have the want_response bit set. If set, this callback will be invoked - * so that subclasses can (optionally) send a response back to the original sender. - * - * Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set - * the protected reply field in this instance. - * */ - virtual MeshPacket *allocReply(); - - /*** - * @return true if you want to be alloced a UI screen frame - */ - virtual bool wantUIFrame() { return false; } - - MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); - - /// Send an error response for the specified packet. - MeshPacket *allocErrorResponse(Routing_Error err, const MeshPacket *p); - - private: - /** - * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow - * the RoutingPlugin to avoid sending redundant acks - */ - static MeshPacket *currentReply; - - friend class ReliableRouter; - - /** Messages can be received that have the want_response bit set. If set, this callback will be invoked - * so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply() - * to generate the reply message, and if !NULL that message will be delivered to whoever sent req - */ - void sendResponse(const MeshPacket &req); -}; - -/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet - * This ensures that if the request packet was sent reliably, the reply is sent that way as well. - */ -void setReplyTo(MeshPacket *p, const MeshPacket &to); \ No newline at end of file From 1e455ac4c39248b759c988d29a314b149e733cfd Mon Sep 17 00:00:00 2001 From: a-f-G-U-C <65810997+a-f-G-U-C@users.noreply.github.com> Date: Sat, 9 Oct 2021 14:02:21 +0000 Subject: [PATCH 12/18] issue 879 - add opt-in flag for plugins --- src/mesh/MeshPlugin.cpp | 12 ++++++++++-- src/mesh/MeshPlugin.h | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp index 65cb4ce36..4ebd2f4d7 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -66,7 +66,7 @@ MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket * return r; } -void MeshPlugin::callPlugins(const MeshPacket &mp) +void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src) { // DEBUG_MSG("In call plugins\n"); bool pluginFound = false; @@ -79,6 +79,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB.getNodeNum(); bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; + for (auto i = plugins->begin(); i != plugins->end(); ++i) { auto &pi = **i; @@ -87,6 +88,11 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); + if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) { + // new case, monitor separately for now, then FIXME merge above + wantsPacket = false; + } + assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time if (wantsPacket) { @@ -169,7 +175,9 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) } if (!pluginFound) - DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum); + DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n", + mp.decoded.portnum, + (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE"); } MeshPacket *MeshPlugin::allocReply() diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h index 937c37dfe..2350c951a 100644 --- a/src/mesh/MeshPlugin.h +++ b/src/mesh/MeshPlugin.h @@ -33,7 +33,7 @@ class MeshPlugin /** For use only by MeshService */ - static void callPlugins(const MeshPacket &mp); + static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshPluginsWithUIFrames(); #ifndef NO_SCREEN @@ -48,6 +48,10 @@ class MeshPlugin */ bool isPromiscuous = false; + /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave + * this setting disabled - see issue #877 */ + bool loopbackOk = false; + /** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this * flag */ bool encryptedOk = false; From 70818681439966c3f7b888291da36ab0d0327946 Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:17:23 -0400 Subject: [PATCH 13/18] Spelling fixes. Thanks a-f-G-U-C! --- src/gps/GeoCoord.cpp | 30 +++++++++++++++--------------- src/gps/GeoCoord.h | 8 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index fbf0e90f4..b96181069 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -4,27 +4,27 @@ GeoCoord::GeoCoord() { _dirty = true; } -GeoCoord::GeoCoord (int32_t lat, int32_t lon, int32_t alt) : _lattitude(lat), _longitude(lon), _altidude(alt) { +GeoCoord::GeoCoord (int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _longitude(lon), _altitude(alt) { GeoCoord::setCoords(); } -GeoCoord::GeoCoord (float lat, float lon, int32_t alt) : _altidude(alt) { +GeoCoord::GeoCoord (float lat, float lon, int32_t alt) : _altitude(alt) { // Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000 - _lattitude = int32_t(lat * 1e+7); + _latitude = int32_t(lat * 1e+7); _longitude = int32_t(lon * 1e+7); GeoCoord::setCoords(); } -GeoCoord::GeoCoord(double lat, double lon, int32_t alt): _altidude(alt) { +GeoCoord::GeoCoord(double lat, double lon, int32_t alt): _altitude(alt) { // Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000 - _lattitude = int32_t(lat * 1e+7); + _latitude = int32_t(lat * 1e+7); _longitude = int32_t(lon * 1e+7); GeoCoord::setCoords(); } // Initialize all the coordinate systems void GeoCoord::setCoords() { - double lat = _lattitude * 1e-7; + double lat = _latitude * 1e-7; double lon = _longitude * 1e-7; GeoCoord::latLongToDMS(lat, lon, _dms); GeoCoord::latLongToUTM(lat, lon, _utm); @@ -36,11 +36,11 @@ void GeoCoord::setCoords() { void GeoCoord::updateCoords(int32_t lat, int32_t lon, int32_t alt) { // If marked dirty or new coordiantes - if(_dirty || _lattitude != lat || _longitude != lon || _altidude != alt) { + if(_dirty || _latitude != lat || _longitude != lon || _altitude != alt) { _dirty = true; - _lattitude = lat; + _latitude = lat; _longitude = lon; - _altidude = alt; + _altitude = alt; setCoords(); } } @@ -49,11 +49,11 @@ void GeoCoord::updateCoords(const double lat, const double lon, const int32_t al int32_t iLat = lat * 1e+7; int32_t iLon = lon * 1e+7; // If marked dirty or new coordiantes - if(_dirty || _lattitude != iLat || _longitude != iLon || _altidude != alt) { + if(_dirty || _latitude != iLat || _longitude != iLon || _altitude != alt) { _dirty = true; - _lattitude = iLat; + _latitude = iLat; _longitude = iLon; - _altidude = alt; + _altitude = alt; setCoords(); } @@ -63,11 +63,11 @@ void GeoCoord::updateCoords(const float lat, const float lon, const int32_t alt) int32_t iLat = lat * 1e+7; int32_t iLon = lon * 1e+7; // If marked dirty or new coordiantes - if(_dirty || _lattitude != iLat || _longitude != iLon || _altidude != alt) { + if(_dirty || _latitude != iLat || _longitude != iLon || _altitude != alt) { _dirty = true; - _lattitude = iLat; + _latitude = iLat; _longitude = iLon; - _altidude = alt; + _altitude = alt; setCoords(); } } diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index d982ae24d..fcb7aa18b 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -81,9 +81,9 @@ struct OLC { class GeoCoord { private: - int32_t _lattitude = 0; + int32_t _latitude = 0; int32_t _longitude = 0; - int32_t _altidude = 0; + int32_t _altitude = 0; DMS _dms; UTM _utm; @@ -114,9 +114,9 @@ class GeoCoord { static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude); // Lat lon alt getters - int32_t getLatitude() const { return _lattitude; } + int32_t getLatitude() const { return _latitude; } int32_t getLongitude() const { return _longitude; } - int32_t getAltitude() const { return _altidude; } + int32_t getAltitude() const { return _altitude; } // DMS getters uint8_t getDMSLatDeg() const { return _dms.latDeg; } From 796e8c836a5b9f99c0a2eda251b244464918478a Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:28:51 -0400 Subject: [PATCH 14/18] Move latLongtoMeter under GeoCoord --- src/gps/GeoCoord.cpp | 21 +++++++++++++++++++++ src/gps/GeoCoord.h | 1 + src/graphics/Screen.cpp | 25 ++----------------------- src/plugins/esp32/RangeTestPlugin.cpp | 24 ++---------------------- 4 files changed, 26 insertions(+), 45 deletions(-) diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index b96181069..96c27bdf8 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -344,4 +344,25 @@ void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double & osgb_Longitude = atan2(osgbY, osgbX); // leave in radians //osgb height = p*cos(osgb.latitude) + osgbZ*sin(osgb.latitude) - //(airyA*airyA/(airyA / sqrt(1 - airyEcc*sin(osgb.latitude)*sin(osgb.latitude)))); // Not used, no OSTN data +} + +/// Ported from my old java code, returns distance in meters along the globe +/// surface (by magic?) +float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b) +{ + double pk = (180 / 3.14169); + double a1 = lat_a / pk; + double a2 = lng_a / pk; + double b1 = lat_b / pk; + double b2 = lng_b / pk; + double cos_b1 = cos(b1); + double cos_a1 = cos(a1); + double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2); + double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2); + double t3 = sin(a1) * sin(b1); + double tt = acos(t1 + t2 + t3); + if (std::isnan(tt)) + tt = 0.0; // Must have been the same point? + + return (float)(6366000 * tt); } \ No newline at end of file diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index fcb7aa18b..236d66a9f 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -112,6 +112,7 @@ class GeoCoord { static void latLongToOSGR(const double lat, const double lon, OSGR &osgr); static void latLongToOLC(const double lat, const double lon, OLC &olc); static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude); + static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b); // Lat lon alt getters int32_t getLatitude() const { return _latitude; } diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index bd08218fd..8a7997a09 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -437,27 +437,6 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } } -/// Ported from my old java code, returns distance in meters along the globe -/// surface (by magic?) -static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b) -{ - double pk = (180 / 3.14169); - double a1 = lat_a / pk; - double a2 = lng_a / pk; - double b1 = lat_b / pk; - double b2 = lng_b / pk; - double cos_b1 = cos(b1); - double cos_a1 = cos(a1); - double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2); - double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2); - double t3 = sin(a1) * sin(b1); - double tt = acos(t1 + t2 + t3); - if (isnan(tt)) - tt = 0.0; // Must have been the same point? - - return (float)(6366000 * tt); -} - /** * Computes the bearing in degrees between two points on Earth. Ported from my * old Gaggle android app. @@ -543,7 +522,7 @@ static float estimatedHeading(double lat, double lon) return b; } - float d = latLongToMeter(oldLat, oldLon, lat, lon); + float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon); if (d < 10) // haven't moved enough, just keep current bearing return b; @@ -667,7 +646,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // display direction toward node hasNodeHeading = true; Position &p = node->position; - float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float d = GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); if (d < 2000) snprintf(distStr, sizeof(distStr), "%.0f m", d); else diff --git a/src/plugins/esp32/RangeTestPlugin.cpp b/src/plugins/esp32/RangeTestPlugin.cpp index b28bd8c9c..47970b88c 100644 --- a/src/plugins/esp32/RangeTestPlugin.cpp +++ b/src/plugins/esp32/RangeTestPlugin.cpp @@ -5,6 +5,7 @@ #include "RTC.h" #include "Router.h" #include "configuration.h" +#include "gps/GeoCoord.h" #include #include //#include @@ -184,27 +185,6 @@ bool RangeTestPluginRadio::handleReceived(const MeshPacket &mp) return true; // Let others look at this message also if they want } -/// Ported from my old java code, returns distance in meters along the globe -/// surface (by magic?) -float RangeTestPluginRadio::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b) -{ - double pk = (180 / 3.14169); - double a1 = lat_a / pk; - double a2 = lng_a / pk; - double b1 = lat_b / pk; - double b2 = lng_b / pk; - double cos_b1 = cos(b1); - double cos_a1 = cos(a1); - double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2); - double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2); - double t3 = sin(a1) * sin(b1); - double tt = acos(t1 + t2 + t3); - if (isnan(tt)) - tt = 0.0; // Must have been the same point? - - return (float)(6366000 * tt); -} - bool RangeTestPluginRadio::appendFile(const MeshPacket &mp) { auto &p = mp.decoded; @@ -303,7 +283,7 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp) fileToAppend.printf("%f,", mp.rx_snr); // RX SNR if (n->position.latitude_i && n->position.longitude_i && gpsStatus->getLatitude() && gpsStatus->getLongitude()) { - float distance = latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7, + float distance = GeoCoord::latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7, gpsStatus->getLatitude() * 1e-7, gpsStatus->getLongitude() * 1e-7); fileToAppend.printf("%f,", distance); // Distance in meters } else { From 4a98bdd9d65590952f7ec82d61c27ce3694034f1 Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sat, 9 Oct 2021 13:31:27 -0400 Subject: [PATCH 15/18] Move bearing under GeoCoord --- src/gps/GeoCoord.cpp | 25 +++++++++++++++++++++++++ src/gps/GeoCoord.h | 1 + src/graphics/Screen.cpp | 29 ++--------------------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index 96c27bdf8..c3bac700a 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -365,4 +365,29 @@ float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double tt = 0.0; // Must have been the same point? return (float)(6366000 * tt); +} + +/** + * Computes the bearing in degrees between two points on Earth. Ported from my + * old Gaggle android app. + * + * @param lat1 + * Latitude of the first point + * @param lon1 + * Longitude of the first point + * @param lat2 + * Latitude of the second point + * @param lon2 + * Longitude of the second point + * @return Bearing between the two points in radians. A value of 0 means due + * north. + */ +float GeoCoord::bearing(double lat1, double lon1, double lat2, double lon2) +{ + double lat1Rad = toRadians(lat1); + double lat2Rad = toRadians(lat2); + double deltaLonRad = toRadians(lon2 - lon1); + double y = sin(deltaLonRad) * cos(lat2Rad); + double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad)); + return atan2(y, x); } \ No newline at end of file diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index 236d66a9f..7a2c1aa9d 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -113,6 +113,7 @@ class GeoCoord { static void latLongToOLC(const double lat, const double lon, OLC &olc); static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude); static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b); + static float bearing(double lat1, double lon1, double lat2, double lon2); // Lat lon alt getters int32_t getLatitude() const { return _latitude; } diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 8a7997a09..d197f7765 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -437,31 +437,6 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const } } -/** - * Computes the bearing in degrees between two points on Earth. Ported from my - * old Gaggle android app. - * - * @param lat1 - * Latitude of the first point - * @param lon1 - * Longitude of the first point - * @param lat2 - * Latitude of the second point - * @param lon2 - * Longitude of the second point - * @return Bearing between the two points in radians. A value of 0 means due - * north. - */ -static float bearing(double lat1, double lon1, double lat2, double lon2) -{ - double lat1Rad = toRadians(lat1); - double lat2Rad = toRadians(lat2); - double deltaLonRad = toRadians(lon2 - lon1); - double y = sin(deltaLonRad) * cos(lat2Rad); - double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad)); - return atan2(y, x); -} - namespace { @@ -526,7 +501,7 @@ static float estimatedHeading(double lat, double lon) if (d < 10) // haven't moved enough, just keep current bearing return b; - b = bearing(oldLat, oldLon, lat, lon); + b = GeoCoord::bearing(oldLat, oldLon, lat, lon); oldLat = lat; oldLon = lon; @@ -654,7 +629,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // FIXME, also keep the guess at the operators heading and add/substract // it. currently we don't do this and instead draw north up only. - float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float bearingToOther = GeoCoord::bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); headingRadian = bearingToOther - myHeading; drawNodeHeading(display, compassX, compassY, headingRadian); } From 96f4998d114ed489bbdec241e69854741f37f540 Mon Sep 17 00:00:00 2001 From: Sam <35611307+syund@users.noreply.github.com> Date: Sat, 9 Oct 2021 14:54:28 -0400 Subject: [PATCH 16/18] Add functionality for converting lat lon to bearing range and back --- src/gps/GeoCoord.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++ src/gps/GeoCoord.h | 8 +++++++ 2 files changed, 65 insertions(+) diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp index c3bac700a..d09b1addb 100644 --- a/src/gps/GeoCoord.cpp +++ b/src/gps/GeoCoord.cpp @@ -390,4 +390,61 @@ float GeoCoord::bearing(double lat1, double lon1, double lat2, double lon2) double y = sin(deltaLonRad) * cos(lat2Rad); double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad)); return atan2(y, x); +} + +/** + * Ported from http://www.edwilliams.org/avform147.htm#Intro + * @brief Convert from meters to range in radians on a great circle + * @param range_meters + * The range in meters + * @return range in radians on a great circle + */ +float GeoCoord::rangeMetersToRadians(double range_meters) { + // 1 nm is 1852 meters + double distance_nm = range_meters * 1852; + return (PI / (180 * 60)) *distance_nm; +} + +/** + * Ported from http://www.edwilliams.org/avform147.htm#Intro + * @brief Convert from radians to range in meters on a great circle + * @param range_radians + * The range in radians + * @return Range in meters on a great circle + */ +float GeoCoord::rangeRadiansToMeters(double range_radians) { + double distance_nm = ((180 * 60) / PI) * range_radians; + // 1 meter is 0.000539957 nm + return distance_nm * 0.000539957; +} + +// Find distance from point to passed in point +int32_t GeoCoord::distanceTo(GeoCoord pointB) { + return latLongToMeter(this->getLatitude() * 1e-7, this->getLongitude() * 1e-7, pointB.getLatitude() * 1e-7, pointB.getLongitude() * 1e-7); +} + +// Find bearing from point to passed in point +int32_t GeoCoord::bearingTo(GeoCoord pointB) { + return bearing(this->getLatitude() * 1e-7, this->getLongitude() * 1e-7, pointB.getLatitude() * 1e-7, pointB.getLongitude() * 1e-7); +} + +/** + * Create a new point bassed on the passed in poin + * Ported from http://www.edwilliams.org/avform147.htm#LL + * @param bearing + * The bearing in raidans + * @param range_meters + * range in meters + * @return GeoCoord object of point at bearing and range from initial point +*/ +std::shared_ptr GeoCoord::pointAtDistance(double bearing, double range_meters) { + double range_radians = rangeMetersToRadians(range_meters); + double lat1 = this->getLatitude() * 1e-7; + double lon1 = this->getLongitude() * 1e-7; + double lat = asin(sin(lat1) * cos(range_radians) + cos(lat1) * sin(range_radians) * cos(bearing)); + double dlon = atan2(sin(bearing) * sin(range_radians) * cos(lat1), cos(range_radians) - sin(lat1) * sin(lat)); + double lon = fmod(lon1 - dlon + PI, 2 * PI) - PI; + + return std::make_shared(double(lat), double(lon), this->getAltitude()); + } \ No newline at end of file diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h index 7a2c1aa9d..f3795c847 100644 --- a/src/gps/GeoCoord.h +++ b/src/gps/GeoCoord.h @@ -7,6 +7,7 @@ #include #include #include +#include #define PI 3.1415926535897932384626433832795 #define OLC_CODE_LEN 11 @@ -114,6 +115,13 @@ class GeoCoord { static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude); static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b); static float bearing(double lat1, double lon1, double lat2, double lon2); + static float rangeRadiansToMeters(double range_radians); + static float rangeMetersToRadians(double range_meters); + + // Point to point conversions + int32_t distanceTo(GeoCoord pointB); + int32_t bearingTo(GeoCoord pointB); + std::shared_ptr pointAtDistance(double bearing, double range); // Lat lon alt getters int32_t getLatitude() const { return _latitude; } From 14e36f0a2b7d11464eda663f778c75cbd2e3ce6a Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Mon, 11 Oct 2021 00:11:04 +1100 Subject: [PATCH 17/18] Web server changes to support frontend --- src/mesh/http/ContentHandler.cpp | 373 +++---------------------------- src/mesh/http/ContentHandler.h | 8 +- src/mesh/http/ContentStatic.h | 119 ---------- 3 files changed, 28 insertions(+), 472 deletions(-) delete mode 100644 src/mesh/http/ContentStatic.h diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 851f06abe..335eb8d7c 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -4,7 +4,6 @@ #include "airtime.h" #include "main.h" #include "mesh/http/ContentHelper.h" -#include "mesh/http/ContentStatic.h" #include "mesh/http/WiFiAPClient.h" #include "power.h" #include "sleep.h" @@ -78,20 +77,18 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio); ResourceNode *nodeHotspotApple = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot); - ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleHotspot); - ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon); - ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot); - ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse); - ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost); - ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic); + ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleGenerate204); + ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart); - ResourceNode *node404 = new ResourceNode("", "GET", &handle404); ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); + ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks); ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED); ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport); ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static", "GET", &handleSpiffsBrowseStatic); ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic); + + ResourceNode *nodeRoot = new ResourceNode("/*", "GET", &handleStatic); // Secure nodes secureServer->registerNode(nodeAPIv1ToRadioOptions); @@ -99,11 +96,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) secureServer->registerNode(nodeAPIv1FromRadio); secureServer->registerNode(nodeHotspotApple); secureServer->registerNode(nodeHotspotAndroid); - secureServer->registerNode(nodeFavicon); - secureServer->registerNode(nodeRoot); - secureServer->registerNode(nodeStaticBrowse); - secureServer->registerNode(nodeStaticPOST); - secureServer->registerNode(nodeStatic); secureServer->registerNode(nodeRestart); secureServer->registerNode(nodeFormUpload); secureServer->registerNode(nodeJsonScanNetworks); @@ -111,7 +103,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) secureServer->registerNode(nodeJsonSpiffsBrowseStatic); secureServer->registerNode(nodeJsonDelete); secureServer->registerNode(nodeJsonReport); - secureServer->setDefaultNode(node404); + secureServer->registerNode(nodeRoot); secureServer->addMiddleware(&middlewareSpeedUp240); @@ -121,11 +113,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) insecureServer->registerNode(nodeAPIv1FromRadio); insecureServer->registerNode(nodeHotspotApple); insecureServer->registerNode(nodeHotspotAndroid); - insecureServer->registerNode(nodeFavicon); - insecureServer->registerNode(nodeRoot); - insecureServer->registerNode(nodeStaticBrowse); - insecureServer->registerNode(nodeStaticPOST); - insecureServer->registerNode(nodeStatic); insecureServer->registerNode(nodeRestart); insecureServer->registerNode(nodeFormUpload); insecureServer->registerNode(nodeJsonScanNetworks); @@ -133,7 +120,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) insecureServer->registerNode(nodeJsonSpiffsBrowseStatic); insecureServer->registerNode(nodeJsonDelete); insecureServer->registerNode(nodeJsonReport); - insecureServer->setDefaultNode(node404); + insecureServer->registerNode(nodeRoot); insecureServer->addMiddleware(&middlewareSpeedUp160); } @@ -180,11 +167,8 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) /* For documentation, see: - https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion - https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md - - Example: - http://10.10.30.198/api/v1/fromradio + https://meshtastic.org/docs/developers/device/http-api + https://meshtastic.org/docs/developers/device/device-api */ // Get access to the parameters @@ -233,24 +217,20 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) /* For documentation, see: - https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion - https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md - - Example: - http://10.10.30.198/api/v1/toradio + https://meshtastic.org/docs/developers/device/http-api + https://meshtastic.org/docs/developers/device/device-api */ - // Status code is 200 OK by default. - res->setHeader("Content-Type", "application/x-protobuf"); res->setHeader("Access-Control-Allow-Headers", "Content-Type"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS"); res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto"); + if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - res->print(""); + // res->print(""); @todo remove return; } @@ -264,86 +244,8 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n"); } -void handleFavicon(HTTPRequest *req, HTTPResponse *res) -{ - // Set Content-Type - res->setHeader("Content-Type", "image/vnd.microsoft.icon"); - // Write data from header file - res->write(FAVICON_DATA, FAVICON_LENGTH); -} - -void handleStaticPost(HTTPRequest *req, HTTPResponse *res) -{ - // Assume POST request. Contains submitted data. - res->println("File Edited

File Edited

"); - - // The form is submitted with the x-www-form-urlencoded content type, so we need the - // HTTPURLEncodedBodyParser to read the fields. - // Note that the content of the file's content comes from a
"); - res->println(""); - res->println(""); - res->println(""); - - return; - } - - res->println("

Upload new file

"); - res->println("

This form allows you to upload files. Keep your filenames small and files under 200k.

"); - res->println("
"); - res->println("file:
"); - res->println(""); - res->println("
"); - - res->println("

All Files

"); - - File root = SPIFFS.open("/"); - if (root.isDirectory()) { - res->println(""); - - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - - File file = root.openNextFile(); - while (file) { - String filePath = String(file.name()); - if (filePath.indexOf("/static") == 0) { - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - res->println(""); - } - - file = root.openNextFile(); - } - res->println("
File"); - res->println("Size"); - res->println("Actions"); - res->println("
"); - - if (String(file.name()).substring(1).endsWith(".gz")) { - String modifiedFile = String(file.name()).substring(1); - modifiedFile.remove((modifiedFile.length() - 3), 3); - res->print("" + String(file.name()).substring(1) + ""); - } else { - res->print("" + String(file.name()).substring(1) + - ""); - } - res->println(""); - res->print(String(file.size())); - res->println(""); - res->print("Delete "); - res->println(""); - if (!String(file.name()).substring(1).endsWith(".gz")) { - res->print("Edit"); - } - res->println("
"); - - res->print("
"); - // res->print("Total : " + String(SPIFFS.totalBytes()) + " Bytes
"); - res->print("Used : " + String(SPIFFS.usedBytes()) + " Bytes
"); - res->print("Free : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()) + " Bytes
"); - } -} - void handleStatic(HTTPRequest *req, HTTPResponse *res) { // Get access to the parameters @@ -634,35 +336,33 @@ void handleStatic(HTTPRequest *req, HTTPResponse *res) std::string filename = "/static/" + parameter1; std::string filenameGzip = "/static/" + parameter1 + ".gz"; - if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) { - // Send "404 Not Found" as response, as the file doesn't seem to exist - res->setStatusCode(404); - res->setStatusText("Not found"); - res->println("404 Not Found"); - res->printf("

File not found: %s

\n", filename.c_str()); - return; - } // Try to open the file from SPIFFS File file; + bool has_set_content_type = false; + if (SPIFFS.exists(filename.c_str())) { file = SPIFFS.open(filename.c_str()); if (!file.available()) { DEBUG_MSG("File not available - %s\n", filename.c_str()); } - } else if (SPIFFS.exists(filenameGzip.c_str())) { file = SPIFFS.open(filenameGzip.c_str()); res->setHeader("Content-Encoding", "gzip"); if (!file.available()) { DEBUG_MSG("File not available\n"); } + } else { + has_set_content_type = true; + filenameGzip = "/static/index.html.gz"; + file = SPIFFS.open(filenameGzip.c_str()); + res->setHeader("Content-Encoding", "gzip"); + res->setHeader("Content-Type", "text/html"); } res->setHeader("Content-Length", httpsserver::intToString(file.size())); - bool has_set_content_type = false; // Content-Type is guessed using the definition of the contentTypes-table defined above int cTypeIdx = 0; do { @@ -949,36 +649,12 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) res->println("}"); } -// -------- - -void handle404(HTTPRequest *req, HTTPResponse *res) -{ - - // Discard request body, if we received any - // We do this, as this is the default node and may also server POST/PUT requests - req->discardRequestBody(); - - // Set the response status - res->setStatusCode(404); - res->setStatusText("Not Found"); - - // Set content type of the response - res->setHeader("Content-Type", "text/html"); - - // Write a tiny HTTP page - res->println(""); - res->println(""); - res->println("Not Found"); - res->println("

404 Not Found

The requested resource was not found on this server.

"); - res->println(""); -} - /* This supports the Apple Captive Network Assistant (CNA) Portal */ void handleHotspot(HTTPRequest *req, HTTPResponse *res) { - DEBUG_MSG("Hotspot Request\n"); + DEBUG_MSG("Apple Hotspot Request\n"); /* If we don't do a redirect, be sure to return a "Success" message @@ -995,6 +671,11 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) res->println("\n"); } +void handleGenerate204(HTTPRequest *req, HTTPResponse *res) +{ + DEBUG_MSG("Android Hotspot Request\n"); + res->setStatusCode(204); +} void handleRestart(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "text/html"); diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index fc091eff9..a31dca701 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -5,25 +5,19 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer); // Declare some handler functions for the various URLs on the server void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res); void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res); -void handleStyleCSS(HTTPRequest *req, HTTPResponse *res); void handleHotspot(HTTPRequest *req, HTTPResponse *res); -void handleRoot(HTTPRequest *req, HTTPResponse *res); -void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res); -void handleStaticPost(HTTPRequest *req, HTTPResponse *res); +void handleGenerate204(HTTPRequest *req, HTTPResponse *res); void handleStatic(HTTPRequest *req, HTTPResponse *res); void handleRestart(HTTPRequest *req, HTTPResponse *res); -void handle404(HTTPRequest *req, HTTPResponse *res); void handleFormUpload(HTTPRequest *req, HTTPResponse *res); void handleScanNetworks(HTTPRequest *req, HTTPResponse *res); void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res); void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res); void handleBlinkLED(HTTPRequest *req, HTTPResponse *res); void handleReport(HTTPRequest *req, HTTPResponse *res); -void handleFavicon(HTTPRequest *req, HTTPResponse *res); void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next); void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next); -void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function next); uint32_t getTimeSpeedUp(); void setTimeSpeedUp(); diff --git a/src/mesh/http/ContentStatic.h b/src/mesh/http/ContentStatic.h deleted file mode 100644 index 324276476..000000000 --- a/src/mesh/http/ContentStatic.h +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include - -/* - This file contains static content. -*/ - -// Length of the binary data -const int FAVICON_LENGTH = 2238; - -// Binary data for the favicon -const byte FAVICON_DATA[] = { - 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x20, 0x20, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0xA8, 0x08, 0x00, 0x00, 0x16, 0x00, 0x00, - 0x00, 0x28, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x84, - 0xDC, 0x3D, 0x00, 0x84, 0xDC, 0x3C, 0x00, 0x85, 0xDC, 0x3F, 0x00, 0x86, 0xDD, 0x40, 0x00, 0x83, 0xDC, 0x3C, 0x00, 0x85, 0xDC, - 0x3E, 0x00, 0x82, 0xDC, 0x3A, 0x00, 0x8B, 0xDE, 0x49, 0x00, 0x84, 0xDB, 0x3E, 0x00, 0x82, 0xD9, 0x3C, 0x00, 0x89, 0xDD, 0x45, - 0x00, 0x83, 0xDB, 0x3C, 0x00, 0x83, 0xD8, 0x3D, 0x00, 0x81, 0xD8, 0x3A, 0x00, 0x8D, 0xE0, 0x49, 0x00, 0x88, 0xE4, 0x3F, 0x00, - 0x89, 0xE9, 0x3E, 0x00, 0x84, 0xD8, 0x40, 0x00, 0x85, 0xDF, 0x3C, 0x00, 0x8E, 0xF2, 0x40, 0x00, 0x8D, 0xF6, 0x3D, 0x00, 0x90, - 0xEA, 0x49, 0x00, 0x82, 0xD5, 0x3E, 0x00, 0x78, 0xC1, 0x3A, 0x00, 0x90, 0xE9, 0x4A, 0x00, 0x8E, 0xF5, 0x3D, 0x00, 0x84, 0xDD, - 0x3D, 0x00, 0x91, 0xF7, 0x43, 0x00, 0x87, 0xE5, 0x3D, 0x00, 0x6C, 0xA2, 0x38, 0x00, 0x53, 0x65, 0x31, 0x00, 0x41, 0x39, 0x2E, - 0x00, 0x3A, 0x27, 0x2B, 0x00, 0x34, 0x1A, 0x2A, 0x00, 0x41, 0x38, 0x2E, 0x00, 0x82, 0xD8, 0x3D, 0x00, 0x88, 0xE7, 0x3D, 0x00, - 0x8E, 0xF3, 0x41, 0x00, 0x69, 0x95, 0x39, 0x00, 0x3E, 0x33, 0x2C, 0x00, 0x31, 0x11, 0x29, 0x00, 0x2E, 0x0A, 0x29, 0x00, 0x2D, - 0x0B, 0x27, 0x00, 0x30, 0x10, 0x29, 0x00, 0x34, 0x18, 0x2A, 0x00, 0x3E, 0x31, 0x2C, 0x00, 0x68, 0x95, 0x39, 0x00, 0x88, 0xE7, - 0x3E, 0x00, 0x82, 0xD7, 0x3C, 0x00, 0x84, 0xDD, 0x3C, 0x00, 0x8B, 0xEE, 0x3E, 0x00, 0x85, 0xDF, 0x3D, 0x00, 0x47, 0x48, 0x2E, - 0x00, 0x30, 0x0F, 0x29, 0x00, 0x31, 0x13, 0x29, 0x00, 0x48, 0x4D, 0x2E, 0x00, 0x61, 0x7F, 0x39, 0x00, 0x6A, 0x9C, 0x38, 0x00, - 0x75, 0xB8, 0x39, 0x00, 0x85, 0xDE, 0x3D, 0x00, 0x8C, 0xEF, 0x3E, 0x00, 0x89, 0xDE, 0x44, 0x00, 0x80, 0xD1, 0x3C, 0x00, 0x3A, - 0x28, 0x2C, 0x00, 0x32, 0x16, 0x2A, 0x00, 0x33, 0x17, 0x2A, 0x00, 0x4B, 0x50, 0x30, 0x00, 0x76, 0xBA, 0x3A, 0x00, 0x8A, 0xEF, - 0x3C, 0x00, 0x9A, 0xFE, 0x4D, 0x00, 0x95, 0xFF, 0x43, 0x00, 0x93, 0xFF, 0x40, 0x00, 0x4B, 0x52, 0x30, 0x00, 0x7E, 0xCE, 0x3C, - 0x00, 0x87, 0xD9, 0x44, 0x00, 0x34, 0x1B, 0x2A, 0x00, 0x65, 0x90, 0x36, 0x00, 0x8E, 0xF6, 0x3D, 0x00, 0x8F, 0xF7, 0x40, 0x00, - 0x8B, 0xDD, 0x48, 0x00, 0x73, 0xB1, 0x3A, 0x00, 0x66, 0x95, 0x35, 0x00, 0x66, 0x93, 0x35, 0x00, 0x35, 0x1B, 0x2A, 0x00, 0x8D, - 0xE8, 0x45, 0x00, 0x82, 0xD9, 0x3B, 0x00, 0x72, 0xAA, 0x3C, 0x00, 0x95, 0xFD, 0x46, 0x00, 0x8D, 0xF0, 0x40, 0x00, 0x57, 0x70, - 0x32, 0x00, 0x3C, 0x2D, 0x2C, 0x00, 0x2F, 0x0D, 0x29, 0x00, 0x81, 0xD4, 0x3D, 0x00, 0x8D, 0xF1, 0x40, 0x00, 0x94, 0xFC, 0x46, - 0x00, 0x73, 0xAE, 0x3D, 0x00, 0x45, 0x44, 0x2D, 0x00, 0x94, 0xF5, 0x49, 0x00, 0x90, 0xF0, 0x45, 0x00, 0x73, 0xAF, 0x3B, 0x00, - 0x38, 0x21, 0x2C, 0x00, 0x30, 0x11, 0x29, 0x00, 0x2F, 0x0F, 0x28, 0x00, 0x72, 0xAC, 0x3B, 0x00, 0x6A, 0x93, 0x3D, 0x00, 0x2E, - 0x0D, 0x27, 0x00, 0x35, 0x1C, 0x2B, 0x00, 0x36, 0x20, 0x2A, 0x00, 0x5E, 0x77, 0x39, 0x00, 0x78, 0xBE, 0x3B, 0x00, 0x36, 0x21, - 0x2A, 0x00, 0x71, 0xAB, 0x3B, 0x00, 0x4C, 0x54, 0x30, 0x00, 0x3D, 0x31, 0x2B, 0x00, 0x82, 0xD6, 0x3D, 0x00, 0x79, 0xC5, 0x39, - 0x00, 0x9A, 0xFF, 0x4D, 0x00, 0x8A, 0xE8, 0x40, 0x00, 0x8A, 0xE7, 0x40, 0x00, 0x7A, 0xC6, 0x39, 0x00, 0x3D, 0x2E, 0x2C, 0x00, - 0x81, 0xD5, 0x3D, 0x00, 0x77, 0xBC, 0x3A, 0x00, 0x31, 0x12, 0x2A, 0x00, 0x69, 0x9B, 0x37, 0x00, 0x8E, 0xF3, 0x40, 0x00, 0x83, - 0xDC, 0x3B, 0x00, 0x8C, 0xF6, 0x3B, 0x00, 0x88, 0xD9, 0x45, 0x00, 0x86, 0xE1, 0x3D, 0x00, 0x85, 0xE0, 0x3D, 0x00, 0x7B, 0xC8, - 0x39, 0x00, 0x36, 0x1F, 0x29, 0x00, 0x55, 0x6B, 0x32, 0x00, 0x8A, 0xEE, 0x3C, 0x00, 0x48, 0x4B, 0x2E, 0x00, 0x51, 0x61, 0x31, - 0x00, 0x8C, 0xE0, 0x48, 0x00, 0x8B, 0xDE, 0x47, 0x00, 0x98, 0xEE, 0x55, 0x00, 0x5D, 0x79, 0x36, 0x00, 0x3A, 0x2A, 0x2B, 0x00, - 0x3A, 0x29, 0x2B, 0x00, 0x5C, 0x78, 0x36, 0x00, 0x60, 0x7C, 0x3A, 0x00, 0x3D, 0x30, 0x2C, 0x00, 0x99, 0xFD, 0x4C, 0x00, 0x66, - 0x8A, 0x3C, 0x00, 0x2D, 0x0C, 0x27, 0x00, 0x42, 0x3C, 0x2E, 0x00, 0x84, 0xDA, 0x3E, 0x00, 0x88, 0xE5, 0x3F, 0x00, 0x37, 0x22, - 0x2B, 0x00, 0x2E, 0x0B, 0x28, 0x00, 0x6A, 0x9B, 0x37, 0x00, 0x72, 0xAF, 0x3A, 0x00, 0x32, 0x15, 0x29, 0x00, 0x2A, 0x00, 0x28, - 0x00, 0x5B, 0x75, 0x35, 0x00, 0x89, 0xE8, 0x3D, 0x00, 0x78, 0xBF, 0x3A, 0x00, 0x73, 0xB4, 0x38, 0x00, 0x83, 0xDA, 0x3C, 0x00, - 0x84, 0xDE, 0x3C, 0x00, 0x85, 0xDD, 0x3E, 0x00, 0x86, 0xDE, 0x40, 0x00, 0x84, 0xDE, 0x3B, 0x00, 0x86, 0xE2, 0x3C, 0x00, 0x85, - 0xDD, 0x3F, 0x00, 0x87, 0xE2, 0x3F, 0x00, 0x87, 0xE1, 0x3E, 0x00, 0x85, 0xDE, 0x3E, 0x00, 0x89, 0xE2, 0x41, 0x00, 0x89, 0xE2, - 0x43, 0x00, 0x84, 0xDC, 0x3E, 0x00, 0x83, 0xD8, 0x3E, 0x00, 0x90, 0xF6, 0x41, 0x00, 0x2B, 0x04, 0x28, 0x00, 0x8C, 0xE3, 0x47, - 0x00, 0x8B, 0xDE, 0x48, 0x00, 0x8A, 0xDC, 0x47, 0x00, 0x8A, 0xDD, 0x47, 0x00, 0x8D, 0xDD, 0x4A, 0x00, 0x8A, 0xDE, 0x47, 0x00, - 0x8B, 0xDD, 0x49, 0x00, 0x8B, 0xE0, 0x46, 0x00, 0x9A, 0xF2, 0x55, 0x00, 0x59, 0x70, 0x35, 0x00, 0x8F, 0xDE, 0x4F, 0x00, 0x82, - 0xDC, 0x3B, 0x00, 0x82, 0xDB, 0x39, 0x00, 0x7F, 0xD7, 0x38, 0x00, 0x92, 0xF0, 0x48, 0x00, 0x33, 0x19, 0x29, 0x00, 0x87, 0xDD, - 0x42, 0x00, 0x87, 0xDD, 0x41, 0x00, 0x92, 0xEC, 0x4B, 0x00, 0x78, 0xBD, 0x3C, 0x00, 0x86, 0xDD, 0x3F, 0x00, 0x81, 0xD9, 0x39, - 0x00, 0x7B, 0xC4, 0x3C, 0x00, 0x34, 0x1A, 0x29, 0x00, 0x89, 0xDD, 0x44, 0x00, 0x86, 0xDC, 0x40, 0x00, 0x88, 0xDD, 0x44, 0x00, - 0x87, 0xDE, 0x41, 0x00, 0x99, 0xFA, 0x4F, 0x00, 0x7B, 0xC3, 0x3E, 0x00, 0x83, 0xD7, 0x3F, 0x00, 0x8B, 0xED, 0x3E, 0x00, 0x40, - 0x33, 0x2F, 0x00, 0x39, 0x27, 0x2B, 0x00, 0x81, 0xD7, 0x3B, 0x00, 0x3B, 0x2C, 0x2A, 0x00, 0x33, 0x18, 0x29, 0x00, 0x38, 0x22, - 0x2B, 0x00, 0x85, 0xDA, 0x40, 0x00, 0x89, 0xEA, 0x3D, 0x00, 0x6F, 0xA9, 0x38, 0x00, 0x70, 0xAB, 0x38, 0x00, 0x85, 0xDD, 0x3D, - 0x00, 0x88, 0xE1, 0x40, 0x00, 0x36, 0x1F, 0x2B, 0x00, 0x30, 0x13, 0x28, 0x00, 0x68, 0x9A, 0x36, 0x00, 0x90, 0xFB, 0x3F, 0x00, - 0x8A, 0xDD, 0x46, 0x00, 0x8D, 0xE9, 0x45, 0x00, 0x5A, 0x71, 0x36, 0x00, 0x27, 0x00, 0x24, 0x00, 0x73, 0x9F, 0x45, 0x00, 0x97, - 0xFE, 0x4A, 0x00, 0x86, 0xD8, 0x43, 0x00, 0x73, 0xA1, 0x45, 0x00, 0x8E, 0xDF, 0x4C, 0x00, 0x85, 0xDB, 0x40, 0x00, 0x72, 0xB5, - 0x37, 0x00, 0x95, 0xF4, 0x4B, 0x00, 0x73, 0xB6, 0x37, 0x00, 0x88, 0xE9, 0x3C, 0x00, 0x8A, 0xDB, 0x48, 0x00, 0x8C, 0xDE, 0x49, - 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0xAE, 0x06, 0xF1, 0x02, 0x04, 0x04, 0x02, 0xF1, 0x06, 0xAE, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, - 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0xAE, 0xC7, 0xF1, 0x02, 0x04, - 0x04, 0x02, 0xF1, 0xC7, 0xAE, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x02, 0x03, 0x0B, 0xAE, 0xEF, 0xF0, 0x02, 0x01, 0x01, 0x02, 0xB4, 0xEF, 0xAE, 0x0B, 0x03, 0x02, 0x01, 0x00, - 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, 0xEB, 0xA7, 0xD1, 0xEC, 0xED, 0x96, - 0x04, 0x04, 0x96, 0xED, 0xEE, 0xD1, 0xA7, 0xEB, 0x05, 0x04, 0x01, 0x04, 0xCA, 0x04, 0x01, 0x01, 0x01, 0xCA, 0xCA, 0xCA, 0xCA, - 0xCA, 0xCA, 0xCA, 0xCC, 0xE2, 0x8A, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE8, 0xE7, 0xE9, 0xE5, 0xBB, 0xE3, 0x8A, 0xE2, 0xCC, - 0xCA, 0xCC, 0xEA, 0xCC, 0xCA, 0xCA, 0xCA, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x08, 0xDD, 0x55, 0xDE, 0x2C, 0xDF, - 0xE0, 0xE1, 0xE1, 0xE0, 0xDF, 0x2C, 0xDE, 0x55, 0xDD, 0x08, 0x04, 0x01, 0x04, 0xCA, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xC6, 0xD8, 0xD9, 0xAB, 0x78, 0x6A, 0x28, 0xDA, 0xDB, 0x28, 0x6A, 0x5A, 0xDC, 0xD9, 0xD8, 0xC6, - 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, 0x03, 0xD4, 0xD1, 0x31, - 0xD5, 0xD6, 0x98, 0xD7, 0xD6, 0xD5, 0x0B, 0x32, 0xD4, 0x03, 0x05, 0x04, 0x01, 0x04, 0xCA, 0x04, 0x01, 0x01, 0x01, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x05, 0xC3, 0xC2, 0xA4, 0xD0, 0xD1, 0xB3, 0xD2, 0xD3, 0xD3, 0xD2, 0x4F, 0x32, 0xD0, 0xA4, 0xC2, - 0xC3, 0x05, 0x02, 0x05, 0xB7, 0x05, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xCB, 0xC2, 0xCC, 0x02, 0xCD, - 0x79, 0xCE, 0xCF, 0xC1, 0xC1, 0xCF, 0xCE, 0x79, 0xCD, 0x02, 0xCC, 0xC2, 0xCB, 0x03, 0xCB, 0xB3, 0xCB, 0x03, 0x03, 0x03, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x7E, 0x05, 0xC6, 0x7E, 0x00, 0xC7, 0x15, 0xC8, 0xC9, 0xC9, 0xC8, 0x15, 0xC7, 0x00, 0x7E, - 0xC6, 0x05, 0x7E, 0x04, 0x7E, 0xCA, 0x7E, 0x04, 0x04, 0x04, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x00, 0x03, 0xC3, 0x00, - 0x05, 0x55, 0xC4, 0xC5, 0xC1, 0xC1, 0xC5, 0xC4, 0x55, 0x05, 0x00, 0xC3, 0x03, 0x00, 0xAE, 0x00, 0x3D, 0x00, 0xAE, 0xAE, 0xAE, - 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0x7E, 0xBE, 0x00, 0x05, 0xBE, 0x7E, 0xBF, 0xC0, 0x5C, 0xC1, 0xC1, 0x5C, 0xC0, 0xBF, 0x7E, - 0xBE, 0x05, 0x00, 0xBE, 0x7E, 0xBE, 0xC2, 0x06, 0xBD, 0xBD, 0xBD, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB3, 0xB3, 0xB5, 0xB6, 0xB6, - 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0x6B, 0x6B, 0xBB, 0xBA, 0xB9, 0xB8, 0xB7, 0xB6, 0xB6, 0xB5, 0xB3, 0xB5, 0xBC, 0xB4, 0xB3, 0xB3, - 0xB3, 0x02, 0x02, 0xA8, 0xA9, 0xAA, 0xA8, 0x02, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0x8F, 0xB1, 0x71, 0x2D, 0xB1, 0x9E, 0xB0, - 0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0x02, 0xA4, 0xB2, 0xAA, 0xA8, 0x02, 0x02, 0x04, 0x31, 0xA3, 0x04, 0x31, 0x82, 0x3B, 0xA3, 0xA4, - 0xA5, 0xA6, 0x82, 0xA7, 0x8E, 0x20, 0x78, 0x78, 0x20, 0x8E, 0xA7, 0x82, 0xA6, 0xA5, 0xA4, 0xA3, 0x3B, 0x82, 0x3D, 0x04, 0xA3, - 0x31, 0x04, 0x09, 0x9F, 0xA0, 0x21, 0x2C, 0xA1, 0x47, 0x52, 0x5B, 0x5A, 0xA2, 0x1C, 0x81, 0x8D, 0x8E, 0x91, 0x91, 0x8E, 0x8D, - 0x81, 0x1C, 0xA2, 0x5A, 0x5B, 0x52, 0x47, 0xA1, 0x2C, 0x21, 0xA0, 0x9F, 0x09, 0x96, 0x97, 0x16, 0x98, 0x99, 0x9A, 0x46, 0x9B, - 0x28, 0x9C, 0x6D, 0x76, 0x7D, 0x8C, 0x9D, 0x8E, 0x8E, 0x9D, 0x9E, 0x7D, 0x75, 0x6D, 0x9C, 0x65, 0x9B, 0x46, 0x39, 0x99, 0x98, - 0x16, 0x97, 0x96, 0x4F, 0x89, 0x15, 0x1F, 0x69, 0x38, 0x45, 0x8A, 0x78, 0x66, 0x6C, 0x74, 0x4A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x8B, 0x80, 0x45, 0x90, 0x66, 0x91, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x15, 0x89, 0x4F, 0x7E, 0x0D, 0x14, 0x1E, 0x29, 0x37, 0x44, - 0x14, 0x59, 0x65, 0x6B, 0x73, 0x7F, 0x80, 0x25, 0x81, 0x82, 0x25, 0x80, 0x7F, 0x83, 0x84, 0x65, 0x85, 0x4D, 0x86, 0x87, 0x29, - 0x88, 0x14, 0x0D, 0x7E, 0x05, 0x0C, 0x13, 0x1D, 0x28, 0x2C, 0x43, 0x4E, 0x72, 0x64, 0x53, 0x5A, 0x73, 0x74, 0x75, 0x1C, 0x1C, - 0x76, 0x74, 0x77, 0x78, 0x6A, 0x64, 0x79, 0x4E, 0x7A, 0x2C, 0x7B, 0x7C, 0x7D, 0x0C, 0x05, 0x04, 0x0B, 0x12, 0x1C, 0x27, 0x36, - 0x42, 0x4D, 0x5D, 0x63, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x1A, 0x1A, 0x6D, 0x6C, 0x6E, 0x53, 0x69, 0x6F, 0x5D, 0x19, 0x70, 0x36, - 0x71, 0x24, 0x12, 0x0B, 0x04, 0x03, 0x03, 0x11, 0x1B, 0x2E, 0x35, 0x41, 0x4C, 0x5E, 0x62, 0x63, 0x64, 0x65, 0x66, 0x2C, 0x5A, - 0x5A, 0x2C, 0x66, 0x65, 0x64, 0x67, 0x62, 0x5E, 0x52, 0x40, 0x65, 0x68, 0x1B, 0x11, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x25, - 0x34, 0x40, 0x4B, 0x56, 0x57, 0x58, 0x16, 0x59, 0x5A, 0x41, 0x5B, 0x5B, 0x41, 0x5A, 0x59, 0x5C, 0x5D, 0x5E, 0x5F, 0x21, 0x41, - 0x60, 0x61, 0x05, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x09, 0x24, 0x33, 0x3F, 0x2C, 0x4B, 0x4C, 0x4D, 0x4E, 0x14, 0x4F, 0x50, - 0x51, 0x51, 0x50, 0x4F, 0x14, 0x4E, 0x4D, 0x52, 0x53, 0x2C, 0x3F, 0x01, 0x54, 0x55, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x1A, - 0x23, 0x32, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x46, 0x45, 0x44, 0x43, 0x48, 0x41, 0x40, 0x3F, - 0x49, 0x3C, 0x4A, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x30, 0x32, 0x33, 0x34, 0x35, 0x36, 0x2C, 0x37, 0x38, - 0x39, 0x3A, 0x3A, 0x39, 0x38, 0x37, 0x2C, 0x36, 0x35, 0x34, 0x3B, 0x3C, 0x30, 0x3D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1A, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x2D, 0x2E, 0x13, - 0x2F, 0x30, 0x31, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x09, 0x02, 0x1B, 0x1C, 0x1D, 0x1E, - 0x1F, 0x20, 0x21, 0x21, 0x20, 0x22, 0x1E, 0x1D, 0x1C, 0x1B, 0x02, 0x09, 0x1A, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x17, 0x16, 0x18, 0x19, 0x13, 0x12, 0x11, - 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x0B, 0x0C, - 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x03, 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, - 0x03, 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; From 5c0a76ae46e30d19ef0c052545c1685ca5b79c3d Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Mon, 11 Oct 2021 12:27:10 +1100 Subject: [PATCH 18/18] Reverte seperate Generate204 function --- src/mesh/http/ContentHandler.cpp | 9 ++------- src/mesh/http/ContentHandler.h | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 335eb8d7c..1004e7555 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -77,7 +77,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio); ResourceNode *nodeHotspotApple = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot); - ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleGenerate204); + ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleHotspot); ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart); ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); @@ -654,7 +654,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) */ void handleHotspot(HTTPRequest *req, HTTPResponse *res) { - DEBUG_MSG("Apple Hotspot Request\n"); + DEBUG_MSG("Hotspot Request\n"); /* If we don't do a redirect, be sure to return a "Success" message @@ -671,11 +671,6 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) res->println("\n"); } -void handleGenerate204(HTTPRequest *req, HTTPResponse *res) -{ - DEBUG_MSG("Android Hotspot Request\n"); - res->setStatusCode(204); -} void handleRestart(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "text/html"); diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index a31dca701..779058d6b 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -6,7 +6,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer); void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res); void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res); void handleHotspot(HTTPRequest *req, HTTPResponse *res); -void handleGenerate204(HTTPRequest *req, HTTPResponse *res); void handleStatic(HTTPRequest *req, HTTPResponse *res); void handleRestart(HTTPRequest *req, HTTPResponse *res); void handleFormUpload(HTTPRequest *req, HTTPResponse *res);