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/13] 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/13] 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/13] 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 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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 08/13] 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 09/13] 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 10/13] 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 11/13] 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 12/13] 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 13/13] 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; }