GPS implementation (#216)

Implemented basic GPS support:
- GPS HAL
- GPS Service
- GPS Settings app
This commit is contained in:
Ken Van Hoeylandt
2025-02-11 23:46:52 +01:00
committed by GitHub
parent 2345ba6d13
commit 14e459e50f
36 changed files with 2596 additions and 30 deletions
@@ -1,5 +1,6 @@
#pragma once
#include "Tactility/hal/gps/GpsDevice.h"
#include "Tactility/hal/sdcard/SdCardDevice.h"
#include "Tactility/hal/spi/Spi.h"
#include "Tactility/hal/uart/Uart.h"
@@ -44,14 +45,17 @@ struct Configuration {
/** An optional power interface for battery or other power delivery. */
const CreatePower _Nullable power = nullptr;
/** A list of I2C interfaces */
/** A list of I2C interface configurations */
const std::vector<i2c::Configuration> i2c = {};
/** A list of SPI interfaces */
/** A list of SPI interface configurations */
const std::vector<spi::Configuration> spi = {};
/** A list of UART interfaces */
/** A list of UART interface configurations */
const std::vector<uart::Configuration> uart = {};
/** A list of GPS device configurations */
const std::vector<gps::GpsDevice::Configuration> gps = {};
};
} // namespace
@@ -22,7 +22,8 @@ public:
Touch,
SdCard,
Keyboard,
Power
Power,
Gps
};
typedef uint32_t Id;
@@ -36,7 +37,7 @@ public:
Device();
virtual ~Device() = default;
Id getId() const { return id; }
inline Id getId() const { return id; }
/** The type of device. */
virtual Type getType() const = 0;
@@ -0,0 +1,99 @@
#pragma once
#include "../Device.h"
#include "../uart/Uart.h"
#include "GpsDeviceInitDefault.h"
#include "Satellites.h"
#include <Tactility/Mutex.h>
#include <Tactility/Thread.h>
#include <minmea.h>
#include <utility>
namespace tt::hal::gps {
class GpsDevice : public Device {
public:
typedef int SatelliteSubscriptionId;
typedef int LocationSubscriptionId;
struct Configuration {
std::string name;
uart_port_t uartPort;
uint32_t baudRate;
std::function<bool(uart_port_t)> initFunction = initGpsDefault;
};
private:
struct SatelliteSubscription {
SatelliteSubscriptionId id;
std::shared_ptr<std::function<void(Device::Id id, const minmea_sat_info&)>> onData;
};
struct LocationSubscription {
LocationSubscriptionId id;
std::shared_ptr<std::function<void(Device::Id id, const minmea_sentence_rmc&)>> onData;
};
const Configuration configuration;
Mutex mutex;
std::unique_ptr<Thread> thread;
bool threadInterrupted = false;
std::vector<SatelliteSubscription> satelliteSubscriptions;
std::vector<LocationSubscription> locationSubscriptions;
SatelliteSubscriptionId lastSatelliteSubscriptionId = 0;
LocationSubscriptionId lastLocationSubscriptionId = 0;
static int32_t threadMainStatic(void* parameter);
int32_t threadMain();
bool isThreadInterrupted() const;
public:
explicit GpsDevice(Configuration configuration) : configuration(std::move(configuration)) {
assert(this->configuration.initFunction != nullptr);
}
~GpsDevice() override = default;
Type getType() const override { return Type::Gps; }
std::string getName() const override { return configuration.name; }
std::string getDescription() const override { return ""; }
bool start();
bool stop();
bool isStarted() const;
SatelliteSubscriptionId subscribeSatellites(const std::function<void(Device::Id deviceId, const minmea_sat_info&)>& onData) {
satelliteSubscriptions.push_back({
.id = ++lastSatelliteSubscriptionId,
.onData = std::make_shared<std::function<void(Device::Id, const minmea_sat_info&)>>(onData)
});
return lastSatelliteSubscriptionId;
}
void unsubscribeSatellites(SatelliteSubscriptionId subscriptionId) {
std::erase_if(satelliteSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
}
LocationSubscriptionId subscribeLocations(const std::function<void(Device::Id deviceId, const minmea_sentence_rmc&)>& onData) {
locationSubscriptions.push_back({
.id = ++lastLocationSubscriptionId,
.onData = std::make_shared<std::function<void(Device::Id, const minmea_sentence_rmc&)>>(onData)
});
return lastLocationSubscriptionId;
}
void unsubscribeLocations(SatelliteSubscriptionId subscriptionId) {
std::erase_if(locationSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
}
};
}
@@ -0,0 +1,9 @@
#pragma once
#include <Tactility/hal/uart/Uart.h>
namespace tt::hal::gps {
bool initGpsDefault(uart_port_t port);
}
@@ -0,0 +1,59 @@
#pragma once
#include <Tactility/RtosCompat.h>
#include <Tactility/Mutex.h>
#include <minmea.h>
#include <ranges>
#include <memory>
namespace tt::hal::gps {
/** Thread-safe storage of recent satellites */
class SatelliteStorage {
public:
static constexpr size_t recordCount = 32;
private:
struct SatelliteRecord {
minmea_sat_info data {
.nr = 0,
.elevation = 0,
.azimuth = 0,
.snr = 0
};
TickType_t lastUpdated = 0;
bool inUse = false;
};
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::array<SatelliteRecord, recordCount> records;
uint16_t recycleTimeSeconds;
uint16_t recentTimeSeconds;
SatelliteRecord* findRecord(int number);
SatelliteRecord* findUnusedRecord();
SatelliteRecord* findRecordToRecycle();
/** Tries to find an existing record, otherwise return a free one, otherwise return the oldest active one */
SatelliteRecord* findWithFallback(int number);
public:
explicit SatelliteStorage(
uint16_t recycleTimeSeconds = 120,
uint16_t recentTimeSeconds = 60
) : recycleTimeSeconds(recycleTimeSeconds), recentTimeSeconds(recentTimeSeconds) {}
void notify(const minmea_sat_info& info);
void getRecords(const std::function<void(const minmea_sat_info&)>& onRecord) const;
};
} // namespace tt::hal::gps
@@ -56,6 +56,19 @@ bool lock(uart_port_t port, TickType_t timeout = 10 / portTICK_PERIOD_MS);
bool unlock(uart_port_t port);
size_t read(uart_port_t port, uint8_t* buffer, size_t bufferSize, TickType_t timeout = 10 / portTICK_PERIOD_MS);
bool readByte(uart_port_t port, uint8_t* output, TickType_t timeout = 10 / portTICK_PERIOD_MS);
size_t write(uart_port_t port, const uint8_t* buffer, size_t bufferSize, TickType_t timeout = 10 / portTICK_PERIOD_MS);
bool writeString(uart_port_t port, const char* buffer, TickType_t timeout = 10 / portTICK_PERIOD_MS);
size_t available(uart_port_t port, TickType_t timeout = 10 / portTICK_PERIOD_MS);
bool setBaudRate(uart_port_t port, uint32_t baudRate, TickType_t timeout = 10 / portTICK_PERIOD_MS);
uint32_t getBaudRate(uart_port_t port);
void flush(uart_port_t port, TickType_t timeout = 10 / portTICK_PERIOD_MS);
void flushInput(uart_port_t port, TickType_t timeout = 10 / portTICK_PERIOD_MS);
std::string readStringUntil(uart_port_t port, char untilChar, TickType_t timeout = 10 / portTICK_PERIOD_MS);
bool readUntil(uart_port_t port, uint8_t* buffer, size_t bufferSize, uint8_t untilByte, TickType_t timeout = 10 / portTICK_PERIOD_MS);
} // namespace tt::hal::uart
@@ -0,0 +1,22 @@
#pragma once
#include "Tactility/hal/gps/GpsDevice.h"
namespace tt::service::gps {
/** Register a hardware device to the GPS service. */
void addGpsDevice(const std::shared_ptr<hal::gps::GpsDevice>& device);
/** Deregister a hardware device to the GPS service. */
void removeGpsDevice(const std::shared_ptr<hal::gps::GpsDevice>& device);
/** @return true when GPS is set to receive updates from at least 1 device */
bool startReceiving();
/** Turn GPS receiving off */
void stopReceiving();
/** @return true when GPS receiver is on and 1 or more devices are active */
bool isReceiving();
}
@@ -0,0 +1,10 @@
#pragma once
#include <minmea.h>
namespace tt::hal::gps {
/** @return true when the input float is valid (contains non-zero values) */
inline bool isValid(const minmea_float& inFloat) { return inFloat.value != 0 && inFloat.scale != 0; }
}