diff --git a/.vscode/settings.json b/.vscode/settings.json index dfe3b542f..ebed64343 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -50,7 +50,10 @@ "cassert": "cpp" }, "cSpell.words": [ + "Blox", "Meshtastic", + "NEMAGPS", + "Ublox", "descs", "protobufs" ] diff --git a/bin/version.sh b/bin/version.sh index 6b0158eb3..585b24030 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.1 \ No newline at end of file +export VERSION=0.6.2 \ No newline at end of file diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 5d9c75775..a60cf2af9 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -8,8 +8,7 @@ Minimum items needed to make sure hardware is good. - use "variants" to get all gpio bindings - plug in correct variants for the real board - Use the PMU driver on real hardware -- add a NEMA based GPS driver to test GPS -- Use new radio driver on real hardware - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ +- Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs - test the buttons @@ -24,6 +23,7 @@ Minimum items needed to make sure hardware is good. Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. +- stop polling for GPS characters, instead stay blocked on read in a thread - increase preamble length? - will break other clients? so all devices must update - enable BLE DFU somehow - set appversion/hwversion @@ -100,6 +100,7 @@ Nice ideas worth considering someday... - DONE neg 7 error code from receive - DONE remove unused sx1262 lib from github - at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. +- add a NEMA based GPS driver to test GPS ``` diff --git a/platformio.ini b/platformio.ini index 936a8e6c3..61b61d677 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,7 +31,7 @@ board_build.partitions = partition-table.csv ; note: we add src to our include search path so that lmic_project_config can override ; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc -build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${sysenv.COUNTRY} -DAPP_VERSION=${sysenv.APP_VERSION} @@ -74,7 +74,8 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git https://github.com/meshtastic/RadioLib.git - + https://github.com/meshtastic/TinyGPSPlus.git + ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] src_filter = diff --git a/proto b/proto index bd002e5a1..b35e7fb17 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bd002e5a144f209e42c97b64fea9a05a2e513b28 +Subproject commit b35e7fb17e80a9761145d69a288a9e87af862cab diff --git a/src/GPS.cpp b/src/GPS.cpp deleted file mode 100644 index a6a1e8eef..000000000 --- a/src/GPS.cpp +++ /dev/null @@ -1,218 +0,0 @@ - -#include "GPS.h" -#include "configuration.h" -#include "time.h" -#include -#include - -#ifdef GPS_RX_PIN -HardwareSerial _serial_gps(GPS_SERIAL_NUM); -#else -// Assume NRF52 -HardwareSerial &_serial_gps = Serial1; -#endif - -bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep - -GPS gps; - -// stuff that really should be in in the instance instead... -static uint32_t - timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time -static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock - -static bool wantNewLocation = true; - -GPS::GPS() : PeriodicTask() {} - -void GPS::setup() -{ - PeriodicTask::setup(); - - readFromRTC(); // read the main CPU RTC at first - -#ifdef GPS_RX_PIN - _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); -#else - _serial_gps.begin(GPS_BAUDRATE); -#endif - // _serial_gps.setRxBufferSize(1024); // the default is 256 - // ublox.enableDebugging(Serial); - - // note: the lib's implementation has the wrong docs for what the return val is - // it is not a bool, it returns zero for success - isConnected = ublox.begin(_serial_gps); - - // try a second time, the ublox lib serial parsing is buggy? - if (!isConnected) - isConnected = ublox.begin(_serial_gps); - - if (isConnected) { - DEBUG_MSG("Connected to GPS successfully\n"); - - bool factoryReset = false; - bool ok; - if (factoryReset) { - // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have - // GPS_TX connected) - ublox.factoryReset(); - delay(2000); - isConnected = ublox.begin(_serial_gps); - DEBUG_MSG("Factory reset success=%d\n", isConnected); - if (isConnected) { - ublox.assumeAutoPVT(true, true); // Just parse NEMA for now - } - } else { - ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API - assert(ok); - ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low - assert(ok); - // ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M - // assert(ok); - // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds - // assert(ok); - ok = ublox.powerSaveMode(); // use power save mode - assert(ok); - } - ok = ublox.saveConfiguration(3000); - assert(ok); - } else { - // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just - // assume NEMA at 9600 baud. - DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n"); - - // tell lib, we are expecting the module to send PVT messages by itself to our Rx pin - // you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call - // checkUblox cyclically) - ublox.assumeAutoPVT(true, true); - } -} - -void GPS::readFromRTC() -{ - struct timeval tv; /* btw settimeofday() is helpfull here too*/ - - if (!gettimeofday(&tv, NULL)) { - uint32_t now = millis(); - - DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS); - timeStartMsec = now; - zeroOffsetSecs = tv.tv_sec; - } -} - -/// If we haven't yet set our RTC this boot, set it from a GPS derived time -void GPS::perhapsSetRTC(const struct timeval *tv) -{ - if (!timeSetFromGPS) { - timeSetFromGPS = true; - DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec); -#ifndef NO_ESP32 - settimeofday(tv, NULL); -#else - DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n"); -#endif - readFromRTC(); - } -} - -#include - -uint32_t GPS::getTime() -{ - return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs; -} - -uint32_t GPS::getValidTime() -{ - return timeSetFromGPS ? getTime() : 0; -} - -/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock) -bool GPS::canSleep() -{ - return true; // we leave GPS on during sleep now, so sleep is okay !wantNewLocation; -} - -/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs -void GPS::prepareSleep() -{ - if (isConnected) - ublox.powerOff(); -} - -void GPS::doTask() -{ - uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix - - if (isConnected) { - // Consume all characters that have arrived - - // getPVT automatically calls checkUblox - ublox.checkUblox(); // See if new data is available. Process bytes as they come in. - - // If we don't have a fix (a quick check), don't try waiting for a solution) - // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions - // turn off for now - // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); - } - - // DEBUG_MSG("sec %d\n", ublox.getSecond()); - // DEBUG_MSG("lat %d\n", ublox.getLatitude()); - - // any fix that has time - if (!timeSetFromGPS && ublox.getT()) { - struct timeval tv; - - /* Convert to unix time -The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 -(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). -*/ - struct tm t; - t.tm_sec = ublox.getSecond(); - t.tm_min = ublox.getMinute(); - t.tm_hour = ublox.getHour(); - t.tm_mday = ublox.getDay(); - t.tm_mon = ublox.getMonth() - 1; - t.tm_year = ublox.getYear() - 1900; - t.tm_isdst = false; - time_t res = mktime(&t); - tv.tv_sec = res; - tv.tv_usec = 0; // time.centisecond() * (10 / 1000); - - DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); - if (t.tm_year < 0 || t.tm_year >= 300) - DEBUG_MSG("Ignoring invalid GPS time\n"); - else - perhapsSetRTC(&tv); - } - - if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only - { - // we only notify if position has changed - latitude = ublox.getLatitude() * 1e-7; - longitude = ublox.getLongitude() * 1e-7; - altitude = ublox.getAltitude() / 1000; // in mm convert to meters - DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude); - - hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 - if (hasValidLocation) { - wantNewLocation = false; - notifyObservers(NULL); - // ublox.powerOff(); - } - } else // we didn't get a location update, go back to sleep and hope the characters show up - wantNewLocation = true; - - // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over - // the serial - setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); -} - -void GPS::startLock() -{ - DEBUG_MSG("Looking for GPS lock\n"); - wantNewLocation = true; - setPeriod(1); -} diff --git a/src/GPS.h b/src/GPS.h deleted file mode 100644 index 912356c42..000000000 --- a/src/GPS.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "Observer.h" -#include "PeriodicTask.h" -#include "SparkFun_Ublox_Arduino_Library.h" -#include "sys/time.h" - -/** - * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) - * - * When new data is available it will notify observers. - */ -class GPS : public PeriodicTask, public Observable -{ - SFE_UBLOX_GPS ublox; - - public: - double latitude, longitude; - uint32_t altitude; - bool isConnected; // Do we have a GPS we are talking to - - GPS(); - - /// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero - uint32_t getTime(); - - /// Return time since 1970 in secs. If we don't have a GPS lock return zero - uint32_t getValidTime(); - - void setup(); - - - virtual void doTask(); - - /// If we haven't yet set our RTC this boot, set it from a GPS derived time - void perhapsSetRTC(const struct timeval *tv); - - /// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock) - bool canSleep(); - - /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs - void prepareSleep(); - - /// Restart our lock attempt - try to get and broadcast a GPS reading ASAP - void startLock(); - - /// Returns ture if we have acquired GPS lock. - bool hasLock() const { return hasValidLocation; } - - private: - void readFromRTC(); - - bool hasValidLocation = false; // default to false, until we complete our first read -}; - -extern GPS gps; diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 4c4944b0f..ed6eaf4ff 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -87,7 +87,7 @@ static void lsIdle() static void lsExit() { // setGPSPower(true); // restore GPS power - gps.startLock(); + gps->startLock(); } static void nbEnter() diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 9b3f14804..ecda8bf40 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -178,7 +178,7 @@ class MyNodeInfoCharacteristic : public ProtobufCharacteristic void onRead(BLECharacteristic *c) { // update gps connection state - myNodeInfo.has_gps = gps.isConnected; + myNodeInfo.has_gps = gps->isConnected; ProtobufCharacteristic::onRead(c); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp new file mode 100644 index 000000000..bb2d30b54 --- /dev/null +++ b/src/gps/GPS.cpp @@ -0,0 +1,81 @@ + +#include "GPS.h" +#include "configuration.h" +#include "time.h" +#include +#include + +#ifdef GPS_RX_PIN +HardwareSerial _serial_gps_real(GPS_SERIAL_NUM); +HardwareSerial &GPS::_serial_gps = _serial_gps_real; +#else +// Assume NRF52 +HardwareSerial &GPS::_serial_gps = Serial1; +#endif + +bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep + +GPS *gps; + +// stuff that really should be in in the instance instead... +static uint32_t + timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time +static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock + +void readFromRTC() +{ + struct timeval tv; /* btw settimeofday() is helpfull here too*/ + + if (!gettimeofday(&tv, NULL)) { + uint32_t now = millis(); + + DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS); + timeStartMsec = now; + zeroOffsetSecs = tv.tv_sec; + } +} + +/// If we haven't yet set our RTC this boot, set it from a GPS derived time +void perhapsSetRTC(const struct timeval *tv) +{ + if (!timeSetFromGPS) { + timeSetFromGPS = true; + DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec); +#ifndef NO_ESP32 + settimeofday(tv, NULL); +#else + DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n"); +#endif + readFromRTC(); + } +} + +void perhapsSetRTC(struct tm &t) +{ + /* Convert to unix time + The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 + (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). + */ + time_t res = mktime(&t); + struct timeval tv; + tv.tv_sec = res; + tv.tv_usec = 0; // time.centisecond() * (10 / 1000); + + // DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); + if (t.tm_year < 0 || t.tm_year >= 300) + DEBUG_MSG("Ignoring invalid GPS time\n"); + else + perhapsSetRTC(&tv); +} + +#include + +uint32_t getTime() +{ + return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs; +} + +uint32_t getValidTime() +{ + return timeSetFromGPS ? getTime() : 0; +} diff --git a/src/gps/GPS.h b/src/gps/GPS.h new file mode 100644 index 000000000..3eb972843 --- /dev/null +++ b/src/gps/GPS.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Observer.h" +#include "PeriodicTask.h" +#include "sys/time.h" + +/// If we haven't yet set our RTC this boot, set it from a GPS derived time +void perhapsSetRTC(const struct timeval *tv); +void perhapsSetRTC(struct tm &t); + +/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero +uint32_t getTime(); + +/// Return time since 1970 in secs. If we don't have a GPS lock return zero +uint32_t getValidTime(); + +void readFromRTC(); + +/** + * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class GPS : public Observable +{ + protected: + bool hasValidLocation = false; // default to false, until we complete our first read + + static HardwareSerial &_serial_gps; + + public: + int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double + int32_t altitude = 0; + bool isConnected = false; // Do we have a GPS we are talking to + + virtual ~GPS() {} + + /** + * Returns true if we succeeded + */ + virtual bool setup() { return true; } + + /// A loop callback for subclasses that need it. FIXME, instead just block on serial reads + virtual void loop() {} + + /// Returns ture if we have acquired GPS lock. + bool hasLock() const { return hasValidLocation; } + + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock() {} +}; + +extern GPS *gps; diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp new file mode 100644 index 000000000..7d19f8869 --- /dev/null +++ b/src/gps/NEMAGPS.cpp @@ -0,0 +1,65 @@ +#include "NEMAGPS.h" +#include "configuration.h" + +static int32_t toDegInt(RawDegrees d) +{ + int32_t degMult = 10000000; // 1e7 + int32_t r = d.deg * degMult + d.billionths / 100; + if (d.negative) + r *= -1; + return r; +} + +void NEMAGPS::loop() +{ + + while (_serial_gps.available() > 0) { + int c = _serial_gps.read(); + // Serial.write(c); + reader.encode(c); + } + + uint32_t now = millis(); + if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume + // serial chars at whatever rate) + lastUpdateMsec = now; + + auto ti = reader.time; + auto d = reader.date; + if (ti.isUpdated() && ti.isValid() && d.isValid()) { + /* Convert to unix time + The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 + (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). + */ + struct tm t; + t.tm_sec = ti.second(); + t.tm_min = ti.minute(); + t.tm_hour = ti.hour(); + t.tm_mday = d.day(); + t.tm_mon = d.month() - 1; + t.tm_year = d.year() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); + + isConnected = true; // we seem to have a real GPS (but not necessarily a lock) + } + + if (reader.location.isUpdated()) { + if (reader.altitude.isValid()) + altitude = reader.altitude.meters(); + + if (reader.location.isValid()) { + auto loc = reader.location.value(); + latitude = toDegInt(loc.lat); + longitude = toDegInt(loc.lng); + } + + // expect gps pos lat=37.520825, lon=-122.309162, alt=158 + DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) + notifyObservers(NULL); + } + } +} \ No newline at end of file diff --git a/src/gps/NEMAGPS.h b/src/gps/NEMAGPS.h new file mode 100644 index 000000000..5bea0d41f --- /dev/null +++ b/src/gps/NEMAGPS.h @@ -0,0 +1,21 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "PeriodicTask.h" +#include "TinyGPS++.h" + +/** + * A gps class thatreads from a NEMA GPS stream (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class NEMAGPS : public GPS +{ + TinyGPSPlus reader; + + uint32_t lastUpdateMsec = 0; + + public: + virtual void loop(); +}; diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp new file mode 100644 index 000000000..560c52fa8 --- /dev/null +++ b/src/gps/UBloxGPS.cpp @@ -0,0 +1,139 @@ +#include "UBloxGPS.h" +#include "sleep.h" +#include + +UBloxGPS::UBloxGPS() : PeriodicTask() +{ + notifySleepObserver.observe(¬ifySleep); +} + +bool UBloxGPS::setup() +{ +#ifdef GPS_RX_PIN + _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); +#else + _serial_gps.begin(GPS_BAUDRATE); +#endif + // _serial_gps.setRxBufferSize(1024); // the default is 256 + // ublox.enableDebugging(Serial); + + // note: the lib's implementation has the wrong docs for what the return val is + // it is not a bool, it returns zero for success + isConnected = ublox.begin(_serial_gps); + + // try a second time, the ublox lib serial parsing is buggy? + if (!isConnected) + isConnected = ublox.begin(_serial_gps); + + if (isConnected) { + DEBUG_MSG("Connected to UBLOX GPS successfully\n"); + + bool factoryReset = false; + bool ok; + if (factoryReset) { + // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have + // GPS_TX connected) + ublox.factoryReset(); + delay(3000); + isConnected = ublox.begin(_serial_gps); + DEBUG_MSG("Factory reset success=%d\n", isConnected); + ok = ublox.saveConfiguration(3000); + assert(ok); + return false; + } else { + ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API + assert(ok); + ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low + assert(ok); + // ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M + // assert(ok); + // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds + // assert(ok); + ok = ublox.powerSaveMode(); // use power save mode + assert(ok); + } + ok = ublox.saveConfiguration(3000); + assert(ok); + + PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device + + return true; + } else { + return false; + } +} + +/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs +int UBloxGPS::prepareSleep(void *unused) +{ + if (isConnected) + ublox.powerOff(); + + return 0; +} + +void UBloxGPS::doTask() +{ + uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix + + assert(isConnected); + + // Consume all characters that have arrived + + // getPVT automatically calls checkUblox + ublox.checkUblox(); // See if new data is available. Process bytes as they come in. + + // If we don't have a fix (a quick check), don't try waiting for a solution) + // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions + // turn off for now + // fixtype = ublox.getFixType(); + DEBUG_MSG("fix type %d\n", fixtype); + + // DEBUG_MSG("sec %d\n", ublox.getSecond()); + // DEBUG_MSG("lat %d\n", ublox.getLatitude()); + + // any fix that has time + if (ublox.getT()) { + /* Convert to unix time +The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 +(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). +*/ + struct tm t; + t.tm_sec = ublox.getSecond(); + t.tm_min = ublox.getMinute(); + t.tm_hour = ublox.getHour(); + t.tm_mday = ublox.getDay(); + t.tm_mon = ublox.getMonth() - 1; + t.tm_year = ublox.getYear() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); + } + + if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only + { + // we only notify if position has changed + latitude = ublox.getLatitude(); + longitude = ublox.getLongitude(); + altitude = ublox.getAltitude() / 1000; // in mm convert to meters + DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) { + wantNewLocation = false; + notifyObservers(NULL); + // ublox.powerOff(); + } + } else // we didn't get a location update, go back to sleep and hope the characters show up + wantNewLocation = true; + + // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over + // the serial + setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); +} + +void UBloxGPS::startLock() +{ + DEBUG_MSG("Looking for GPS lock\n"); + wantNewLocation = true; + setPeriod(1); +} diff --git a/src/gps/UBloxGPS.h b/src/gps/UBloxGPS.h new file mode 100644 index 000000000..39b125981 --- /dev/null +++ b/src/gps/UBloxGPS.h @@ -0,0 +1,41 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "PeriodicTask.h" +#include "SparkFun_Ublox_Arduino_Library.h" + +/** + * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class UBloxGPS : public GPS, public PeriodicTask +{ + SFE_UBLOX_GPS ublox; + + bool wantNewLocation = true; + + CallbackObserver notifySleepObserver = CallbackObserver(this, &UBloxGPS::prepareSleep); + + public: + UBloxGPS(); + + /** + * Returns true if we succeeded + */ + virtual bool setup(); + + virtual void doTask(); + + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock(); + + private: + + /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs + /// always returns 0 to indicate okay to sleep + int prepareSleep(void *unused); +}; diff --git a/src/main.cpp b/src/main.cpp index 7e1b39ab7..3971ae9bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,13 +21,14 @@ */ -#include "GPS.h" #include "MeshRadio.h" #include "MeshService.h" +#include "NEMAGPS.h" #include "NodeDB.h" #include "Periodic.h" #include "PowerFSM.h" #include "Router.h" +#include "UBloxGPS.h" #include "configuration.h" #include "error.h" #include "power.h" @@ -188,8 +189,18 @@ void setup() screen.print("Started...\n"); - // Init GPS - gps.setup(); + readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) + + // Init GPS - first try ublox + gps = new UBloxGPS(); + if (!gps->setup()) { + // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just + // assume NEMA at 9600 baud. + DEBUG_MSG("ERROR: No UBLOX GPS found, hoping that NEMA might work\n"); + delete gps; + gps = new NEMAGPS(); + gps->setup(); + } service.init(); @@ -258,6 +269,7 @@ void loop() { uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop? + gps->loop(); // FIXME, remove from main, instead block on read router.loop(); powerFSM.run_machine(); service.loop(); @@ -306,7 +318,7 @@ void loop() screen.debug()->setChannelNameStatus(channelSettings.name); screen.debug()->setPowerStatus(powerStatus); // TODO(#4): use something based on hdop to show GPS "signal" strength. - screen.debug()->setGPSStatus(gps.hasLock() ? "ok" : ":("); + screen.debug()->setGPSStatus(gps->hasLock() ? "good" : "bad"); // No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in) // i.e. don't just keep spinning in loop as fast as we can. diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 71eb5f37c..7e9fc4486 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -84,7 +84,7 @@ void MeshService::init() sendOwnerPeriod.setup(); nodeDB.init(); - gpsObserver.observe(&gps); + gpsObserver.observe(gps); packetReceivedObserver.observe(&router.notifyPacketReceived); } @@ -153,7 +153,7 @@ void MeshService::handleIncomingPosition(const MeshPacket *mp) tv.tv_sec = secs; tv.tv_usec = 0; - gps.perhapsSetRTC(&tv); + perhapsSetRTC(&tv); } } else { DEBUG_MSG("Ignoring incoming packet - not a position\n"); @@ -165,7 +165,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping // If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work) - if (!gps.isConnected) + if (!gps->isConnected) handleIncomingPosition(mp); else { DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); @@ -234,8 +234,8 @@ void MeshService::handleToRadio(MeshPacket &p) if (p.id == 0) p.id = generatePacketId(); // If the phone didn't supply one, then pick one - p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone - // (so we update our nodedb for the local node) + p.rx_time = getValidTime(); // Record the time the packet arrived from the phone + // (so we update our nodedb for the local node) // Send the packet into the mesh @@ -258,7 +258,7 @@ void MeshService::sendToMesh(MeshPacket *p) // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. if (p->has_payload && p->payload.has_position) { - if (!gps.isConnected) { + if (!gps->isConnected) { DEBUG_MSG("Stripping time %u from position send\n", p->payload.position.time); p->payload.position.time = 0; } else @@ -269,8 +269,7 @@ void MeshService::sendToMesh(MeshPacket *p) if (p->to == nodeDB.getNodeNum()) { DEBUG_MSG("Dropping locally processed message\n"); releaseToPool(p); - } - else { + } else { // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it if (router.send(p) != ERRNO_OK) { DEBUG_MSG("No radio was able to send packet, discarding...\n"); @@ -287,7 +286,7 @@ MeshPacket *MeshService::allocForSending() p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; p->id = generatePacketId(); - p->rx_time = gps.getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp return p; } @@ -316,7 +315,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) p->payload.has_position = true; p->payload.position = node->position; p->payload.want_response = wantReplies; - p->payload.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. + p->payload.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. sendToMesh(p); } @@ -330,12 +329,12 @@ int MeshService::onGPSChanged(void *unused) Position &pos = p->payload.position; // !zero or !zero lat/long means valid - if (gps.latitude != 0 || gps.longitude != 0) { - if (gps.altitude != 0) - pos.altitude = gps.altitude; - pos.latitude = gps.latitude; - pos.longitude = gps.longitude; - pos.time = gps.getValidTime(); + if (gps->latitude != 0 || gps->longitude != 0) { + if (gps->altitude != 0) + pos.altitude = gps->altitude; + pos.latitude_i = gps->latitude; + pos.longitude_i = gps->longitude; + pos.time = getValidTime(); } // We limit our GPS broadcasts to a max rate diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0559754a3..73b3391d6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -246,7 +246,7 @@ const NodeInfo *NodeDB::readNextInfo() /// Given a node, return how many seconds in the past (vs now) that we last heard from it uint32_t sinceLastSeen(const NodeInfo *n) { - uint32_t now = gps.getTime(); + uint32_t now = getTime(); uint32_t last_seen = n->position.time; int delta = (int)(now - last_seen); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b5a34a801..6e5734e59 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -68,7 +68,7 @@ void Router::handleReceived(MeshPacket *p) { // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function // Also, we should set the time from the ISR and it should have msec level resolution - p->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone + p->rx_time = getValidTime(); // store the arrival timestamp for the phone DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 096f28896..0b2c5b8ce 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -55,11 +55,3 @@ PB_BIND(ToRadio, ToRadio, 2) -#ifndef PB_CONVERT_DOUBLE_FLOAT -/* On some platforms (such as AVR), double is really float. - * To be able to encode/decode double on these platforms, you need. - * to define PB_CONVERT_DOUBLE_FLOAT in pb.h or compiler command line. - */ -PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES) -#endif - diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index bf79305b8..ba2293a0a 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -66,11 +66,11 @@ typedef struct _MyNodeInfo { } MyNodeInfo; typedef struct _Position { - double latitude; - double longitude; int32_t altitude; int32_t battery_level; uint32_t time; + int32_t latitude_i; + int32_t longitude_i; } Position; typedef struct _RadioConfig_UserPreferences { @@ -237,8 +237,8 @@ typedef struct _ToRadio { #define MyNodeInfo_error_code_tag 7 #define MyNodeInfo_error_address_tag 8 #define MyNodeInfo_error_count_tag 9 -#define Position_latitude_tag 1 -#define Position_longitude_tag 2 +#define Position_latitude_i_tag 7 +#define Position_longitude_i_tag 8 #define Position_altitude_tag 3 #define Position_battery_level_tag 4 #define Position_time_tag 6 @@ -297,11 +297,11 @@ typedef struct _ToRadio { /* Struct field encoding specification for nanopb */ #define Position_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, DOUBLE, latitude, 1) \ -X(a, STATIC, SINGULAR, DOUBLE, longitude, 2) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, INT32, battery_level, 4) \ -X(a, STATIC, SINGULAR, UINT32, time, 6) +X(a, STATIC, SINGULAR, UINT32, time, 6) \ +X(a, STATIC, SINGULAR, SINT32, latitude_i, 7) \ +X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) #define Position_CALLBACK NULL #define Position_DEFAULT NULL @@ -486,21 +486,21 @@ extern const pb_msgdesc_t ToRadio_msg; #define ToRadio_fields &ToRadio_msg /* Maximum encoded size of messages (where known) */ -#define Position_size 46 +#define Position_size 40 #define Data_size 256 #define User_size 72 /* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 383 -#define MeshPacket_size 425 +#define SubPacket_size 377 +#define MeshPacket_size 419 #define ChannelSettings_size 44 #define RadioConfig_size 120 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 138 +#define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 18925 +#define DeviceState_size 18535 #define DebugString_size 258 -#define FromRadio_size 434 -#define ToRadio_size 428 +#define FromRadio_size 428 +#define ToRadio_size 422 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/screen.cpp b/src/screen.cpp index a390520c4..0b3f22fb2 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -280,7 +280,7 @@ static float estimatedHeading(double lat, double lon) /// valid lat/lon static bool hasPosition(NodeInfo *n) { - return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0); + return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); } /// We will skip one node - the one for us, so we just blindly loop over all @@ -288,6 +288,9 @@ static bool hasPosition(NodeInfo *n) static size_t nodeIndex; static int8_t prevFrame = -1; +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // We only advance our nodeIndex if the frame # has changed - because @@ -334,7 +337,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum()); if (ourNode && hasPosition(ourNode) && hasPosition(node)) { Position &op = ourNode->position, &p = node->position; - float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude); + float d = 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 @@ -342,8 +345,8 @@ 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(p.latitude, p.longitude, op.latitude, op.longitude); - float myHeading = estimatedHeading(p.latitude, p.longitude); + float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i)); headingRadian = bearingToOther - myHeading; } else { // Debug info for gps lock errors diff --git a/src/sleep.cpp b/src/sleep.cpp index 9693a8fc6..4f5fa2fdc 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -29,6 +29,7 @@ extern AXP20X_Class axp; Observable preflightSleep; /// Called to tell observers we are now entering sleep and you should prepare. Must return 0 +/// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep Observable notifySleep, notifyDeepSleep; // deep sleep support @@ -125,12 +126,6 @@ static bool doPreflightSleep() /// Tell devices we are going to sleep and wait for them to handle things static void waitEnterSleep() { - /* - former hardwired code - now moved into notifySleep callbacks: - // Put radio in sleep mode (will still draw power but only 0.2uA) - service.radio.radioIf.sleep(); - */ - uint32_t now = millis(); while (!doPreflightSleep()) { delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) @@ -144,7 +139,6 @@ static void waitEnterSleep() // Code that still needs to be moved into notifyObservers Serial.flush(); // send all our characters before we stop cpu clock setBluetoothEnable(false); // has to be off before calling light sleep - gps.prepareSleep(); // abandon in-process parsing notifySleep.notifyObservers(NULL); } @@ -157,6 +151,7 @@ void doDeepSleep(uint64_t msecToWake) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(); + notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers notifyDeepSleep.notifyObservers(NULL); screen.setOn(false); // datasheet says this will draw only 10ua