Merge pull request from geeksville/nema-124

support ublox 8m gpses (I think)
This commit is contained in:
Kevin Hester 2020-05-04 20:18:33 -07:00 committed by GitHub
commit f10ad07f97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 471 additions and 337 deletions

View File

@ -50,7 +50,10 @@
"cassert": "cpp"
},
"cSpell.words": [
"Blox",
"Meshtastic",
"NEMAGPS",
"Ublox",
"descs",
"protobufs"
]

View File

@ -1,3 +1,3 @@
export VERSION=0.6.1
export VERSION=0.6.2

View File

@ -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
```

View File

@ -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 =

2
proto

@ -1 +1 @@
Subproject commit bd002e5a144f209e42c97b64fea9a05a2e513b28
Subproject commit b35e7fb17e80a9761145d69a288a9e87af862cab

View File

@ -1,218 +0,0 @@
#include "GPS.h"
#include "configuration.h"
#include "time.h"
#include <assert.h>
#include <sys/time.h>
#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 <time.h>
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);
}

View File

@ -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<void *>
{
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;

View File

@ -87,7 +87,7 @@ static void lsIdle()
static void lsExit()
{
// setGPSPower(true); // restore GPS power
gps.startLock();
gps->startLock();
}
static void nbEnter()

View File

@ -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);

81
src/gps/GPS.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "GPS.h"
#include "configuration.h"
#include "time.h"
#include <assert.h>
#include <sys/time.h>
#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 <time.h>
uint32_t getTime()
{
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t getValidTime()
{
return timeSetFromGPS ? getTime() : 0;
}

55
src/gps/GPS.h Normal file
View File

@ -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<void *>
{
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;

65
src/gps/NEMAGPS.cpp Normal file
View File

@ -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);
}
}
}

21
src/gps/NEMAGPS.h Normal file
View File

@ -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();
};

139
src/gps/UBloxGPS.cpp Normal file
View File

@ -0,0 +1,139 @@
#include "UBloxGPS.h"
#include "sleep.h"
#include <assert.h>
UBloxGPS::UBloxGPS() : PeriodicTask()
{
notifySleepObserver.observe(&notifySleep);
}
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);
}

41
src/gps/UBloxGPS.h Normal file
View File

@ -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<UBloxGPS, void *> notifySleepObserver = CallbackObserver<UBloxGPS, void *>(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);
};

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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" */

View File

@ -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

View File

@ -29,6 +29,7 @@ extern AXP20X_Class axp;
Observable<void *> 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<void *> 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