Merge TactilityHeadless into Tactility (#263)
There currently is no practical use to have TactilityHeadless as a subproject. I'm merging it with the Tactility project.
This commit is contained in:
committed by
GitHub
parent
d0ca3b16f8
commit
d72852a6e2
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#define TT_ASSET_FOLDER "A:/system/"
|
||||
#define TT_ASSET(file) TT_ASSET_FOLDER file
|
||||
|
||||
// UI
|
||||
#define TT_ASSETS_UI_SPINNER TT_ASSET("spinner.png")
|
||||
|
||||
// App icons
|
||||
#define TT_ASSETS_APP_ICON_FALLBACK TT_ASSET("app_icon_fallback.png")
|
||||
#define TT_ASSETS_APP_ICON_FILES TT_ASSET("app_icon_files.png")
|
||||
#define TT_ASSETS_APP_ICON_DISPLAY_SETTINGS TT_ASSET("app_icon_display_settings.png")
|
||||
#define TT_ASSETS_APP_ICON_POWER_SETTINGS TT_ASSET("app_icon_power_settings.png")
|
||||
#define TT_ASSETS_APP_ICON_I2C_SETTINGS TT_ASSET("app_icon_i2c.png")
|
||||
#define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings.png")
|
||||
#define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info.png")
|
||||
#define TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS TT_ASSET("app_icon_time_date_settings.png")
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt {
|
||||
|
||||
#define SYSTEM_PARTITION_NAME "system"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define MOUNT_POINT_SYSTEM "/system"
|
||||
#else
|
||||
#define MOUNT_POINT_SYSTEM "system"
|
||||
#endif
|
||||
|
||||
#define DATA_PARTITION_NAME "data"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define MOUNT_POINT_DATA "/data"
|
||||
#else
|
||||
#define MOUNT_POINT_DATA "data"
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace tt {
|
||||
|
||||
/**
|
||||
* Settings that persist on NVS flash for ESP32.
|
||||
* On simulator, the settings are only in-memory.
|
||||
*/
|
||||
class Preferences {
|
||||
private:
|
||||
const char* namespace_;
|
||||
|
||||
public:
|
||||
explicit Preferences(const char* namespace_) {
|
||||
this->namespace_ = namespace_;
|
||||
}
|
||||
|
||||
bool hasBool(const std::string& key) const;
|
||||
bool hasInt32(const std::string& key) const;
|
||||
bool hasString(const std::string& key) const;
|
||||
|
||||
bool optBool(const std::string& key, bool& out) const;
|
||||
bool optInt32(const std::string& key, int32_t& out) const;
|
||||
bool optString(const std::string& key, std::string& out) const;
|
||||
|
||||
void putBool(const std::string& key, bool value);
|
||||
void putInt32(const std::string& key, int32_t value);
|
||||
void putString(const std::string& key, const std::string& value);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/hal/Configuration.h"
|
||||
|
||||
#include <Tactility/TactilityCore.h>
|
||||
#include <Tactility/Dispatcher.h>
|
||||
|
||||
namespace tt {
|
||||
|
||||
/** Initialize the hardware and started the internal services. */
|
||||
void initHeadless(const hal::Configuration& config);
|
||||
|
||||
/** Provides access to the dispatcher that runs on the main task.
|
||||
* @warning This dispatcher is used for WiFi and might block for some time during WiFi connection.
|
||||
* @return the dispatcher
|
||||
*/
|
||||
Dispatcher& getMainDispatcher();
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace tt::hal {
|
||||
|
||||
/** While technically this configuration is nullable, it's never null after initHeadless() is called. */
|
||||
const Configuration* _Nullable getConfiguration();
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,58 @@
|
||||
#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"
|
||||
#include "i2c/I2c.h"
|
||||
|
||||
namespace tt::hal {
|
||||
|
||||
typedef bool (*InitBoot)();
|
||||
|
||||
namespace display { class DisplayDevice; }
|
||||
namespace keyboard { class KeyboardDevice; }
|
||||
namespace power { class PowerDevice; }
|
||||
|
||||
typedef std::shared_ptr<display::DisplayDevice> (*CreateDisplay)();
|
||||
typedef std::shared_ptr<keyboard::KeyboardDevice> (*CreateKeyboard)();
|
||||
typedef std::shared_ptr<power::PowerDevice> (*CreatePower)();
|
||||
|
||||
enum class LvglInit {
|
||||
Default,
|
||||
None
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
/**
|
||||
* Called before I2C/SPI/etc is initialized.
|
||||
* Used for powering on the peripherals manually.
|
||||
*/
|
||||
const InitBoot _Nullable initBoot = nullptr;
|
||||
|
||||
/** Init behaviour: default (esp_lvgl_port for ESP32, nothing for PC) or None (nothing on any platform). Only used in Tactility, not in TactilityHeadless. */
|
||||
const LvglInit lvglInit = LvglInit::Default;
|
||||
|
||||
/** Display HAL functionality. */
|
||||
const CreateDisplay _Nullable createDisplay = nullptr;
|
||||
|
||||
/** Keyboard HAL functionality. */
|
||||
const CreateKeyboard _Nullable createKeyboard = nullptr;
|
||||
|
||||
/** An optional SD card interface. */
|
||||
const std::shared_ptr<sdcard::SdCardDevice> _Nullable sdcard = nullptr;
|
||||
|
||||
/** An optional power interface for battery or other power delivery. */
|
||||
const CreatePower _Nullable power = nullptr;
|
||||
|
||||
/** A list of I2C interface configurations */
|
||||
const std::vector<i2c::Configuration> i2c = {};
|
||||
|
||||
/** A list of SPI interface configurations */
|
||||
const std::vector<spi::Configuration> spi = {};
|
||||
|
||||
/** A list of UART interface configurations */
|
||||
const std::vector<uart::Configuration> uart = {};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace tt::hal {
|
||||
|
||||
/** Base class for HAL-related devices. */
|
||||
class Device {
|
||||
|
||||
public:
|
||||
|
||||
enum class Type {
|
||||
I2c,
|
||||
Display,
|
||||
Touch,
|
||||
SdCard,
|
||||
Keyboard,
|
||||
Power,
|
||||
Gps
|
||||
};
|
||||
|
||||
typedef uint32_t Id;
|
||||
|
||||
private:
|
||||
|
||||
Id id;
|
||||
|
||||
public:
|
||||
|
||||
Device();
|
||||
virtual ~Device() = default;
|
||||
|
||||
/** Unique identifier */
|
||||
inline Id getId() const { return id; }
|
||||
|
||||
/** The type of device */
|
||||
virtual Type getType() const = 0;
|
||||
|
||||
/** The part number or hardware name e.g. TdeckTouch, TdeckDisplay, BQ24295, etc. */
|
||||
virtual std::string getName() const = 0;
|
||||
|
||||
/** A short description of what this device does.
|
||||
* e.g. "USB charging controller with I2C interface."
|
||||
*/
|
||||
virtual std::string getDescription() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a device to the registry.
|
||||
* @warning This will leak memory if you want to destroy a device and don't call deregisterDevice()!
|
||||
*/
|
||||
void registerDevice(const std::shared_ptr<Device>& device);
|
||||
|
||||
/** Remove a device from the registry. */
|
||||
void deregisterDevice(const std::shared_ptr<Device>& device);
|
||||
|
||||
/** Find a single device with a custom filter */
|
||||
std::shared_ptr<Device> _Nullable findDevice(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction);
|
||||
|
||||
/** Find devices with a custom filter */
|
||||
std::vector<std::shared_ptr<Device>> findDevices(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction);
|
||||
|
||||
/** Find a device in the registry by its name. */
|
||||
std::shared_ptr<Device> _Nullable findDevice(std::string name);
|
||||
|
||||
/** Find a device in the registry by its identifier. */
|
||||
std::shared_ptr<Device> _Nullable findDevice(Device::Id id);
|
||||
|
||||
/** Find 0, 1 or more devices in the registry by type. */
|
||||
std::vector<std::shared_ptr<Device>> findDevices(Device::Type type);
|
||||
|
||||
/** Get a copy of the entire device registry in its current state. */
|
||||
std::vector<std::shared_ptr<Device>> getDevices();
|
||||
|
||||
/** Find devices of a certain type and cast them to the specified class */
|
||||
template<class DeviceType>
|
||||
std::vector<std::shared_ptr<DeviceType>> findDevices(Device::Type type) {
|
||||
auto devices = findDevices(type);
|
||||
if (devices.empty()) {
|
||||
return {};
|
||||
} else {
|
||||
std::vector<std::shared_ptr<DeviceType>> result;
|
||||
result.reserve(devices.size());
|
||||
for (auto& device : devices) {
|
||||
auto target_device = std::static_pointer_cast<DeviceType>(device);
|
||||
assert(target_device != nullptr);
|
||||
result.push_back(target_device);
|
||||
}
|
||||
return std::move(result);
|
||||
}
|
||||
}
|
||||
|
||||
/** Find the first device of the specified type and cast it to the specified class */
|
||||
template<class DeviceType>
|
||||
std::shared_ptr<DeviceType> findFirstDevice(Device::Type type) {
|
||||
auto devices = findDevices(type);
|
||||
if (devices.empty()) {
|
||||
return {};
|
||||
} else {
|
||||
auto& first = devices[0];
|
||||
return std::static_pointer_cast<DeviceType>(first);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <driver/gpio.h>
|
||||
#else
|
||||
typedef unsigned int gpio_num_t;
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::hal::touch {
|
||||
class TouchDevice;
|
||||
}
|
||||
|
||||
namespace tt::hal::display {
|
||||
|
||||
class DisplayDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
Type getType() const override { return Type::Display; }
|
||||
|
||||
virtual bool start() = 0;
|
||||
virtual bool stop() = 0;
|
||||
|
||||
virtual void setPowerOn(bool turnOn) {}
|
||||
virtual bool isPoweredOn() const { return true; }
|
||||
virtual bool supportsPowerControl() const { return false; }
|
||||
|
||||
virtual std::shared_ptr<touch::TouchDevice> _Nullable createTouch() = 0;
|
||||
|
||||
/** Set a value in the range [0, 255] */
|
||||
virtual void setBacklightDuty(uint8_t backlightDuty) { /* NO-OP */ }
|
||||
virtual bool supportsBacklightDuty() const { return false; }
|
||||
|
||||
/** Set a value in the range [0, 255] */
|
||||
virtual void setGammaCurve(uint8_t index) { /* NO-OP */ }
|
||||
virtual uint8_t getGammaCurveCount() const { return 0; };
|
||||
|
||||
/** After start() returns true, this should return a valid pointer until stop() is called and returns true */
|
||||
virtual lv_display_t* _Nullable getLvglDisplay() const = 0;
|
||||
};
|
||||
|
||||
} // namespace tt::hal::display
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace tt::hal::gps {
|
||||
|
||||
enum class GpsModel {
|
||||
Unknown = 0,
|
||||
AG3335,
|
||||
AG3352,
|
||||
ATGM336H, // Casic (might work with AT6558, Neoway N58 LTE Cat.1, Neoway G2, Neoway G7A)
|
||||
LS20031,
|
||||
MTK,
|
||||
MTK_L76B,
|
||||
MTK_PA1616S,
|
||||
UBLOX6,
|
||||
UBLOX7,
|
||||
UBLOX8,
|
||||
UBLOX9,
|
||||
UBLOX10,
|
||||
UC6580,
|
||||
};
|
||||
|
||||
const char* toString(GpsModel model);
|
||||
|
||||
std::vector<std::string> getModels();
|
||||
|
||||
struct GpsConfiguration {
|
||||
char uartName[32]; // e.g. "Internal" or "/dev/ttyUSB0"
|
||||
uint32_t baudRate;
|
||||
GpsModel model; // Choosing "Unknown" will result in a probe
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
#include "GpsConfiguration.h"
|
||||
#include "Satellites.h"
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Thread.h>
|
||||
|
||||
#include <minmea.h>
|
||||
#include <utility>
|
||||
|
||||
namespace tt::hal::gps {
|
||||
|
||||
enum class GpsResponse {
|
||||
None,
|
||||
NotAck,
|
||||
FrameErrors,
|
||||
Ok,
|
||||
};
|
||||
|
||||
class GpsDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
typedef int GgaSubscriptionId;
|
||||
typedef int RmcSubscriptionId;
|
||||
|
||||
enum class State {
|
||||
PendingOn,
|
||||
On,
|
||||
Error,
|
||||
PendingOff,
|
||||
Off
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
struct GgaSubscription {
|
||||
GgaSubscriptionId id;
|
||||
std::shared_ptr<std::function<void(Device::Id id, const minmea_sentence_gga&)>> onData;
|
||||
};
|
||||
|
||||
struct RmcSubscription {
|
||||
RmcSubscriptionId id;
|
||||
std::shared_ptr<std::function<void(Device::Id id, const minmea_sentence_rmc&)>> onData;
|
||||
};
|
||||
|
||||
const GpsConfiguration configuration;
|
||||
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
||||
std::unique_ptr<Thread> _Nullable thread;
|
||||
bool threadInterrupted = false;
|
||||
std::vector<GgaSubscription> ggaSubscriptions;
|
||||
std::vector<RmcSubscription> rmcSubscriptions;
|
||||
GgaSubscriptionId lastSatelliteSubscriptionId = 0;
|
||||
RmcSubscriptionId lastRmcSubscriptionId = 0;
|
||||
GpsModel model = GpsModel::Unknown;
|
||||
State state = State::Off;
|
||||
|
||||
static int32_t threadMainStatic(void* parameter);
|
||||
int32_t threadMain();
|
||||
|
||||
bool isThreadInterrupted() const;
|
||||
|
||||
void setState(State newState);
|
||||
|
||||
public:
|
||||
|
||||
explicit GpsDevice(GpsConfiguration configuration) : configuration(std::move(configuration)) {}
|
||||
|
||||
~GpsDevice() override = default;
|
||||
|
||||
Type getType() const override { return Type::Gps; }
|
||||
|
||||
std::string getName() const override {
|
||||
if (model != GpsModel::Unknown) {
|
||||
return toString(model);
|
||||
} else {
|
||||
return "Unknown GPS";
|
||||
}
|
||||
}
|
||||
|
||||
std::string getDescription() const override { return ""; }
|
||||
|
||||
bool start();
|
||||
bool stop();
|
||||
|
||||
GgaSubscriptionId subscribeGga(const std::function<void(Device::Id deviceId, const minmea_sentence_gga&)>& onData) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
ggaSubscriptions.push_back({
|
||||
.id = ++lastSatelliteSubscriptionId,
|
||||
.onData = std::make_shared<std::function<void(Device::Id, const minmea_sentence_gga&)>>(onData)
|
||||
});
|
||||
return lastSatelliteSubscriptionId;
|
||||
}
|
||||
|
||||
void unsubscribeGga(GgaSubscriptionId subscriptionId) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
std::erase_if(ggaSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
|
||||
}
|
||||
|
||||
RmcSubscriptionId subscribeRmc(const std::function<void(Device::Id deviceId, const minmea_sentence_rmc&)>& onData) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
rmcSubscriptions.push_back({
|
||||
.id = ++lastRmcSubscriptionId,
|
||||
.onData = std::make_shared<std::function<void(Device::Id, const minmea_sentence_rmc&)>>(onData)
|
||||
});
|
||||
return lastRmcSubscriptionId;
|
||||
}
|
||||
|
||||
void unsubscribeRmc(GgaSubscriptionId subscriptionId) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
std::erase_if(rmcSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
|
||||
}
|
||||
|
||||
GpsModel getModel() const;
|
||||
|
||||
State getState() const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "./I2cCompat.h"
|
||||
#include "Tactility/Lock.h"
|
||||
|
||||
#include <Tactility/RtosCompat.h>
|
||||
|
||||
#include <climits>
|
||||
#include <string>
|
||||
|
||||
namespace tt::hal::i2c {
|
||||
|
||||
constexpr TickType_t defaultTimeout = 10 / portTICK_PERIOD_MS;
|
||||
|
||||
enum class InitMode {
|
||||
ByTactility, // Tactility will initialize it in the correct bootup phase
|
||||
ByExternal, // The device is already initialized and Tactility should assume it works
|
||||
Disabled // Not initialized by default
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
std::string name;
|
||||
/** The port to operate on */
|
||||
i2c_port_t port;
|
||||
/** Whether this bus should be initialized when device starts up */
|
||||
InitMode initMode;
|
||||
/**
|
||||
* Whether this bus can be changed after booting.
|
||||
* If the bus is internal and/or used for core features like touch screen, then it can be declared static.
|
||||
*/
|
||||
bool isMutable;
|
||||
/** Configuration that must be valid when initAtBoot is set to true. */
|
||||
i2c_config_t config;
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
Started,
|
||||
Stopped,
|
||||
Unknown
|
||||
};
|
||||
|
||||
/**
|
||||
* Reconfigure a port with the provided settings.
|
||||
* @warning This fails when the HAL Configuration is not mutable.
|
||||
* @param[in] port the port to reconfigure
|
||||
* @param[in] configuration the new configuration
|
||||
* @return true on success
|
||||
*/
|
||||
bool configure(i2c_port_t port, const i2c_config_t& configuration);
|
||||
|
||||
/**
|
||||
* Start the bus for the specified port.
|
||||
* Devices might be started automatically at boot if their HAL configuration requires it.
|
||||
*/
|
||||
bool start(i2c_port_t port);
|
||||
|
||||
/** Stop the bus for the specified port. */
|
||||
bool stop(i2c_port_t port);
|
||||
|
||||
/** @return true if the bus is started */
|
||||
bool isStarted(i2c_port_t port);
|
||||
|
||||
/** Read bytes in master mode. */
|
||||
bool masterRead(i2c_port_t port, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/** Read bytes from the specified register in master mode. */
|
||||
bool masterReadRegister(i2c_port_t port, uint8_t address, uint8_t reg, uint8_t* data, size_t dataSize, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/** Write bytes in master mode. */
|
||||
bool masterWrite(i2c_port_t port, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/** Write bytes to a register in master mode */
|
||||
bool masterWriteRegister(i2c_port_t port, uint8_t address, uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/**
|
||||
* Write multiple values to multiple registers in master mode.
|
||||
* The input is as follows: { register1, value1, register2, value2, ... }
|
||||
* @return false if any of the write operations failed
|
||||
*/
|
||||
bool masterWriteRegisterArray(i2c_port_t port, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/** Write bytes and then read the response bytes in master mode*/
|
||||
bool masterWriteRead(i2c_port_t port, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/** @return true when a device is detected at the specified address */
|
||||
bool masterHasDeviceAtAddress(i2c_port_t port, uint8_t address, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/**
|
||||
* The lock for the specified bus.
|
||||
* This can be used when calling native I2C functionality outside of Tactility.
|
||||
*/
|
||||
Lock& getLock(i2c_port_t port);
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <hal/i2c_types.h>
|
||||
#include <driver/i2c.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum i2c_port_t {
|
||||
I2C_NUM_0 = 0,
|
||||
I2C_NUM_1,
|
||||
LP_I2C_NUM_0,
|
||||
I2C_NUM_MAX,
|
||||
};
|
||||
|
||||
enum i2c_mode_t {
|
||||
I2C_MODE_MASTER,
|
||||
I2C_MODE_MAX,
|
||||
};
|
||||
|
||||
struct i2c_config_t {
|
||||
i2c_mode_t mode;
|
||||
int sda_io_num;
|
||||
int scl_io_num;
|
||||
bool sda_pullup_en;
|
||||
bool scl_pullup_en;
|
||||
union {
|
||||
struct {
|
||||
uint32_t clk_speed;
|
||||
} master;
|
||||
};
|
||||
uint32_t clk_flags;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "I2c.h"
|
||||
#include "../Device.h"
|
||||
|
||||
namespace tt::hal::i2c {
|
||||
|
||||
/**
|
||||
* Represents an I2C peripheral at a specific port and address.
|
||||
* It helps to read and write registers.
|
||||
*
|
||||
* All read and write calls are thread-safe.
|
||||
*/
|
||||
class I2cDevice : public Device {
|
||||
|
||||
protected:
|
||||
|
||||
i2c_port_t port;
|
||||
uint8_t address;
|
||||
|
||||
static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS;
|
||||
|
||||
bool readRegister8(uint8_t reg, uint8_t& result) const;
|
||||
bool writeRegister8(uint8_t reg, uint8_t value) const;
|
||||
bool readRegister12(uint8_t reg, float& out) const;
|
||||
bool readRegister14(uint8_t reg, float& out) const;
|
||||
bool readRegister16(uint8_t reg, uint16_t& out) const;
|
||||
bool bitOn(uint8_t reg, uint8_t bitmask) const;
|
||||
bool bitOff(uint8_t reg, uint8_t bitmask) const;
|
||||
bool bitOnByIndex(uint8_t reg, uint8_t index) const { return bitOn(reg, 1 << index); }
|
||||
bool bitOffByIndex(uint8_t reg, uint8_t index) const { return bitOff(reg, 1 << index); }
|
||||
|
||||
public:
|
||||
|
||||
explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {}
|
||||
|
||||
Type getType() const override { return Type::I2c; }
|
||||
|
||||
i2c_port_t getPort() const { return port; }
|
||||
|
||||
uint8_t getAddress() const { return address; }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::hal::keyboard {
|
||||
|
||||
class Display;
|
||||
|
||||
class KeyboardDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
Type getType() const override { return Type::Keyboard; }
|
||||
|
||||
virtual bool start(lv_display_t* display) = 0;
|
||||
virtual bool stop() = 0;
|
||||
virtual bool isAttached() const = 0;
|
||||
|
||||
virtual lv_indev_t* _Nullable getLvglIndev() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace tt::hal::power {
|
||||
|
||||
class PowerDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
PowerDevice() = default;
|
||||
~PowerDevice() override = default;
|
||||
|
||||
Type getType() const override { return Type::Power; }
|
||||
|
||||
enum class MetricType {
|
||||
IsCharging, // bool
|
||||
Current, // int32_t, mAh - battery current: either during charging (positive value) or discharging (negative value)
|
||||
BatteryVoltage, // uint32_t, mV
|
||||
ChargeLevel, // uint8_t [0, 100]
|
||||
};
|
||||
|
||||
union MetricData {
|
||||
int32_t valueAsInt32 = 0;
|
||||
uint32_t valueAsUint32;
|
||||
uint8_t valueAsUint8;
|
||||
float valueAsFloat;
|
||||
bool valueAsBool;
|
||||
};
|
||||
|
||||
virtual bool supportsMetric(MetricType type) const = 0;
|
||||
|
||||
/**
|
||||
* @return false when metric is not supported or (temporarily) not available.
|
||||
*/
|
||||
virtual bool getMetric(MetricType type, MetricData& data) = 0;
|
||||
|
||||
virtual bool supportsChargeControl() const { return false; }
|
||||
virtual bool isAllowedToCharge() const { return false; }
|
||||
virtual void setAllowedToCharge(bool canCharge) { /* NO-OP*/ }
|
||||
};
|
||||
|
||||
} // namespace tt
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
class SdCardDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
enum class State {
|
||||
Mounted,
|
||||
Unmounted,
|
||||
Error,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class MountBehaviour {
|
||||
AtBoot, /** Only mount at boot */
|
||||
Anytime /** Mount/dismount any time */
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
MountBehaviour mountBehaviour;
|
||||
|
||||
public:
|
||||
|
||||
explicit SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {}
|
||||
~SdCardDevice() override = default;
|
||||
|
||||
Type getType() const final { return Type::SdCard; };
|
||||
|
||||
virtual bool mount(const std::string& mountPath) = 0;
|
||||
virtual bool unmount() = 0;
|
||||
virtual State getState() const = 0;
|
||||
/** Return empty string when not mounted or the mount path if mounted */
|
||||
virtual std::string getMountPath() const = 0;
|
||||
|
||||
virtual Lock& getLock() const = 0;
|
||||
|
||||
virtual MountBehaviour getMountBehaviour() const { return mountBehaviour; }
|
||||
bool isMounted() const { return getState() == State::Mounted; }
|
||||
};
|
||||
|
||||
/** Return the SdCard device if the path is within the SdCard mounted path (path std::string::starts_with() check)*/
|
||||
std::shared_ptr<SdCardDevice> _Nullable find(const std::string& path);
|
||||
|
||||
/**
|
||||
* Acquires an SD card lock if the path is an SD card path.
|
||||
* Always calls the function, but doesn't lock if the path is not an SD card path.
|
||||
*/
|
||||
template<typename ReturnType>
|
||||
inline ReturnType withSdCardLock(const std::string& path, std::function<ReturnType()> fn) {
|
||||
auto sdcard = find(path);
|
||||
if (sdcard != nullptr) {
|
||||
auto scoped_lockable = sdcard->getLock().asScopedLock();
|
||||
scoped_lockable.lock(portMAX_DELAY);
|
||||
}
|
||||
|
||||
return fn();
|
||||
}
|
||||
|
||||
} // namespace tt::hal
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SdCardDevice.h"
|
||||
|
||||
#include <Tactility/hal/spi/Spi.h>
|
||||
|
||||
#include <sd_protocol_types.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <hal/spi_types.h>
|
||||
#include <soc/gpio_num.h>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
/**
|
||||
* SD card interface at the default SPI interface
|
||||
*/
|
||||
class SpiSdCardDevice final : public SdCardDevice {
|
||||
|
||||
public:
|
||||
|
||||
struct Config {
|
||||
Config(
|
||||
gpio_num_t spiPinCs,
|
||||
gpio_num_t spiPinCd,
|
||||
gpio_num_t spiPinWp,
|
||||
gpio_num_t spiPinInt,
|
||||
MountBehaviour mountBehaviourAtBoot,
|
||||
/** When custom lock is nullptr, use the SPI default one */
|
||||
std::shared_ptr<Lock> _Nullable customLock = nullptr,
|
||||
std::vector<gpio_num_t> csPinWorkAround = std::vector<gpio_num_t>(),
|
||||
spi_host_device_t spiHost = SPI2_HOST,
|
||||
int spiFrequencyKhz = SDMMC_FREQ_DEFAULT
|
||||
) : spiFrequencyKhz(spiFrequencyKhz),
|
||||
spiPinCs(spiPinCs),
|
||||
spiPinCd(spiPinCd),
|
||||
spiPinWp(spiPinWp),
|
||||
spiPinInt(spiPinInt),
|
||||
mountBehaviourAtBoot(mountBehaviourAtBoot),
|
||||
customLock(customLock ? std::move(customLock) : nullptr),
|
||||
csPinWorkAround(std::move(csPinWorkAround)),
|
||||
spiHost(spiHost)
|
||||
{}
|
||||
|
||||
int spiFrequencyKhz;
|
||||
gpio_num_t spiPinCs; // Clock
|
||||
gpio_num_t spiPinCd; // Card detect
|
||||
gpio_num_t spiPinWp; // Write-protect
|
||||
gpio_num_t spiPinInt; // Interrupt
|
||||
SdCardDevice::MountBehaviour mountBehaviourAtBoot;
|
||||
std::shared_ptr<Lock> _Nullable customLock;
|
||||
std::vector<gpio_num_t> csPinWorkAround;
|
||||
spi_host_device_t spiHost;
|
||||
bool formatOnMountFailed = false;
|
||||
uint16_t maxOpenFiles = 4;
|
||||
uint16_t allocUnitSize = 16 * 1024;
|
||||
bool statusCheckEnabled = false;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
std::string mountPath;
|
||||
sdmmc_card_t* card = nullptr;
|
||||
std::shared_ptr<Config> config;
|
||||
|
||||
bool applyGpioWorkAround();
|
||||
bool mountInternal(const std::string& mountPath);
|
||||
|
||||
public:
|
||||
|
||||
explicit SpiSdCardDevice(std::unique_ptr<Config> config) : SdCardDevice(config->mountBehaviourAtBoot),
|
||||
config(std::move(config))
|
||||
{}
|
||||
|
||||
std::string getName() const final { return "SD Card"; }
|
||||
std::string getDescription() const final { return "SD card via SPI interface"; }
|
||||
|
||||
bool mount(const std::string& mountPath) final;
|
||||
bool unmount() final;
|
||||
std::string getMountPath() const final { return mountPath; }
|
||||
|
||||
Lock& getLock() const final {
|
||||
if (config->customLock) {
|
||||
return *config->customLock;
|
||||
} else {
|
||||
return *spi::getLock(config->spiHost);
|
||||
}
|
||||
}
|
||||
|
||||
State getState() const override;
|
||||
|
||||
sdmmc_card_t* _Nullable getCard() { return card; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "SpiCompat.h"
|
||||
|
||||
#include <Tactility/Lock.h>
|
||||
#include <Tactility/RtosCompat.h>
|
||||
|
||||
namespace tt::hal::spi {
|
||||
|
||||
constexpr TickType_t defaultTimeout = 10 / portTICK_PERIOD_MS;
|
||||
|
||||
enum class InitMode {
|
||||
ByTactility, // Tactility will initialize it in the correct bootup phase
|
||||
ByExternal, // The device is already initialized and Tactility should assume it works
|
||||
Disabled // Not initialized by default
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
spi_host_device_t device;
|
||||
spi_common_dma_t dma;
|
||||
spi_bus_config_t config;
|
||||
/** Whether this bus should be initialized when device starts up */
|
||||
InitMode initMode;
|
||||
/** Whether configuration can be changed. */
|
||||
bool isMutable;
|
||||
/** Optional custom lock */
|
||||
std::shared_ptr<Lock> _Nullable lock;
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
Started,
|
||||
Stopped,
|
||||
Unknown
|
||||
};
|
||||
|
||||
/** Start communications */
|
||||
bool start(spi_host_device_t device);
|
||||
|
||||
/** Stop communications */
|
||||
bool stop(spi_host_device_t device);
|
||||
|
||||
/** @return true if communications were started successfully */
|
||||
bool isStarted(spi_host_device_t device);
|
||||
|
||||
/** @return the lock that represents the specified device. Can be used with third party SPI implementations or native API calls (e.g. ESP-IDF). */
|
||||
std::shared_ptr<Lock> getLock(spi_host_device_t device);
|
||||
|
||||
} // namespace tt::hal::spi
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Gpio.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <driver/spi_common.h>
|
||||
#else
|
||||
|
||||
#define SPI_HOST_MAX 3
|
||||
|
||||
typedef int spi_host_device_t;
|
||||
struct spi_bus_config_t {
|
||||
gpio_num_t miso_io_num;
|
||||
gpio_num_t mosi_io_num;
|
||||
gpio_num_t sclk_io_num;
|
||||
};
|
||||
struct spi_common_dma_t {};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::hal::touch {
|
||||
|
||||
class Display;
|
||||
|
||||
class TouchDevice : public Device {
|
||||
|
||||
public:
|
||||
|
||||
Type getType() const override { return Type::Touch; }
|
||||
|
||||
virtual bool start(lv_display_t* display) = 0;
|
||||
virtual bool stop() = 0;
|
||||
|
||||
virtual lv_indev_t* _Nullable getLvglIndev() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::hal::uart {
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
struct Configuration {
|
||||
/** The unique name for this UART */
|
||||
std::string name;
|
||||
/** The port idenitifier (e.g. UART_NUM_0) */
|
||||
uart_port_t port;
|
||||
/** Receive GPIO pin */
|
||||
gpio_num_t rxPin;
|
||||
/** Transmit GPIO pin */
|
||||
gpio_num_t txPin;
|
||||
/** Read-To-Send GPIO pin */
|
||||
gpio_num_t rtsPin;
|
||||
/** Clear-To-Send Send GPIO pin */
|
||||
gpio_num_t ctsPin;
|
||||
/** Receive buffer size in bytes */
|
||||
unsigned int rxBufferSize;
|
||||
/** Transmit buffer size in bytes */
|
||||
unsigned int txBufferSize;
|
||||
/** Native configuration */
|
||||
uart_config_t config;
|
||||
};
|
||||
|
||||
|
||||
#else
|
||||
|
||||
struct Configuration {
|
||||
/** The unique name for this UART */
|
||||
std::string name;
|
||||
/** Initial baud rate in bits per second */
|
||||
uint32_t baudRate;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/RtosCompat.h>
|
||||
|
||||
#include "../Gpio.h"
|
||||
#include "Tactility/Lock.h"
|
||||
#include "UartCompat.h"
|
||||
#include "Tactility/hal/uart/Configuration.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace tt::hal::uart {
|
||||
|
||||
constexpr TickType_t defaultTimeout = 10 / portTICK_PERIOD_MS;
|
||||
|
||||
enum class InitMode {
|
||||
ByTactility, // Tactility will initialize it in the correct bootup phase
|
||||
ByExternal, // The device is already initialized and Tactility should assume it works
|
||||
Disabled // Not initialized by default
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
Started,
|
||||
Stopped,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class Uart {
|
||||
|
||||
private:
|
||||
|
||||
uint32_t id;
|
||||
|
||||
public:
|
||||
|
||||
Uart();
|
||||
virtual ~Uart();
|
||||
|
||||
inline uint32_t getId() const { return id; }
|
||||
|
||||
virtual bool start() = 0;
|
||||
virtual bool isStarted() const = 0;
|
||||
|
||||
virtual bool stop() = 0;
|
||||
|
||||
/**
|
||||
* Read up to a specified amount of bytes
|
||||
* @param[out] buffer
|
||||
* @param[in] bufferSize
|
||||
* @param[in] timeout
|
||||
* @return the amount of bytes that were read
|
||||
*/
|
||||
virtual size_t readBytes(std::byte* buffer, size_t bufferSize, TickType_t timeout = defaultTimeout) = 0;
|
||||
|
||||
size_t readBytes(std::uint8_t* buffer, size_t bufferSize, TickType_t timeout = defaultTimeout) {
|
||||
return readBytes(reinterpret_cast<std::byte*>(buffer), bufferSize, timeout);
|
||||
}
|
||||
|
||||
/** Read a single byte */
|
||||
virtual bool readByte(std::byte* output, TickType_t timeout = defaultTimeout) = 0;
|
||||
|
||||
inline bool readByte(char* output, TickType_t timeout = defaultTimeout) {
|
||||
return readByte(reinterpret_cast<std::byte*>(output), timeout);
|
||||
}
|
||||
|
||||
inline bool readByte(uint8_t* output, TickType_t timeout = defaultTimeout) {
|
||||
return readByte(reinterpret_cast<std::byte*>(output), timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read up to a specified amount of bytes
|
||||
* @param[in] buffer
|
||||
* @param[in] bufferSize
|
||||
* @param[in] timeout
|
||||
* @return the amount of bytes that were read
|
||||
*/
|
||||
virtual size_t writeBytes(const std::byte* buffer, size_t bufferSize, TickType_t timeout = defaultTimeout) = 0;
|
||||
|
||||
inline size_t writeBytes(const std::uint8_t* buffer, size_t bufferSize, TickType_t timeout = defaultTimeout) {
|
||||
return writeBytes(reinterpret_cast<const std::byte*>(buffer), bufferSize, timeout);
|
||||
}
|
||||
|
||||
/** @return the amount of bytes available for reading */
|
||||
virtual size_t available(TickType_t timeout = defaultTimeout) = 0;
|
||||
|
||||
/** Set the baud rate for the specified port */
|
||||
virtual bool setBaudRate(uint32_t baudRate, TickType_t timeout = defaultTimeout) = 0;
|
||||
|
||||
/** Get the baud rate for the specified port */
|
||||
virtual uint32_t getBaudRate() = 0;
|
||||
|
||||
/** Flush input buffers */
|
||||
virtual void flushInput() = 0;
|
||||
|
||||
/**
|
||||
* Write a string (excluding null terminator character)
|
||||
* @param[in] buffer
|
||||
* @param[in] timeout
|
||||
* @return the amount of bytes that were written
|
||||
*/
|
||||
bool writeString(const char* buffer, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/**
|
||||
* Read a buffer as a string until the specified character (the "untilChar" is included in the result)
|
||||
* @warning if the input data doesn't return "untilByte" then the device goes out of memory
|
||||
*/
|
||||
std::string readStringUntil(char untilChar, TickType_t timeout = defaultTimeout);
|
||||
|
||||
/**
|
||||
* Read a buffer as a byte array until the specified character (the "untilChar" is included in the result)
|
||||
* @return the amount of bytes read from UART
|
||||
*/
|
||||
size_t readUntil(std::byte* buffer, size_t bufferSize, uint8_t untilByte, TickType_t timeout = defaultTimeout, bool addNullTerminator = true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a UART.
|
||||
* @param[in] name the UART name as specified in the board configuration.
|
||||
* @return the UART when it was successfully opened, or nullptr when it is in use.
|
||||
*/
|
||||
std::unique_ptr<Uart> open(std::string name);
|
||||
|
||||
std::vector<std::string> getNames();
|
||||
|
||||
} // namespace tt::hal::uart
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <driver/uart.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <hal/uart_types.h>
|
||||
|
||||
#else
|
||||
|
||||
#define UART_NUM_MAX 3
|
||||
typedef int uart_port_t;
|
||||
|
||||
typedef struct {
|
||||
} uart_config_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::hal::usb {
|
||||
|
||||
enum class Mode {
|
||||
Default, // Default state of USB stack
|
||||
None, // State after TinyUSB was used and (partially) deinitialized
|
||||
MassStorageSdmmc
|
||||
};
|
||||
|
||||
bool startMassStorageWithSdmmc();
|
||||
void stop();
|
||||
Mode getMode();
|
||||
bool isSupported();
|
||||
|
||||
bool canRebootIntoMassStorageSdmmc();
|
||||
void rebootIntoMassStorageSdmmc();
|
||||
bool isUsbBootMode();
|
||||
void resetUsbBootMode();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace tt::kernel {
|
||||
|
||||
enum class SystemEvent {
|
||||
BootInitHalBegin,
|
||||
BootInitHalEnd,
|
||||
BootInitI2cBegin,
|
||||
BootInitI2cEnd,
|
||||
BootInitSpiBegin,
|
||||
BootInitSpiEnd,
|
||||
BootInitUartBegin,
|
||||
BootInitUartEnd,
|
||||
BootInitLvglBegin,
|
||||
BootInitLvglEnd,
|
||||
BootSplash,
|
||||
/** Gained IP address */
|
||||
NetworkConnected,
|
||||
NetworkDisconnected,
|
||||
/** An important system time-related event, such as NTP update or time-zone change */
|
||||
Time,
|
||||
};
|
||||
|
||||
/** Value 0 mean "no subscription" */
|
||||
typedef uint32_t SystemEventSubscription;
|
||||
|
||||
typedef std::function<void(SystemEvent)> OnSystemEvent;
|
||||
|
||||
void systemEventPublish(SystemEvent event);
|
||||
|
||||
SystemEventSubscription systemEventAddListener(SystemEvent event, OnSystemEvent handler);
|
||||
|
||||
void systemEventRemoveListener(SystemEventSubscription subscription);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
// Forward declaration
|
||||
class ServiceContext;
|
||||
|
||||
class Service {
|
||||
|
||||
public:
|
||||
|
||||
Service() = default;
|
||||
virtual ~Service() = default;
|
||||
|
||||
virtual void onStart(ServiceContext& serviceContext) {}
|
||||
virtual void onStop(ServiceContext& serviceContext) {}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::shared_ptr<Service> create() { return std::shared_ptr<T>(new T); }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "ServiceManifest.h"
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
class Paths;
|
||||
|
||||
/**
|
||||
* The public representation of a service instance.
|
||||
* @warning Do not store references or pointers to these! You can retrieve them via the Loader service.
|
||||
*/
|
||||
class ServiceContext {
|
||||
|
||||
protected:
|
||||
|
||||
virtual ~ServiceContext() = default;
|
||||
|
||||
public:
|
||||
|
||||
/** @return a reference ot the service's manifest */
|
||||
virtual const service::ServiceManifest& getManifest() const = 0;
|
||||
|
||||
/** Retrieve the paths that are relevant to this service */
|
||||
virtual std::unique_ptr<Paths> getPaths() const = 0;
|
||||
};
|
||||
|
||||
class Paths {
|
||||
|
||||
public:
|
||||
|
||||
Paths() = default;
|
||||
virtual ~Paths() = default;
|
||||
|
||||
/**
|
||||
* Returns the directory path for the data location for a service.
|
||||
* The data directory is intended to survive OS upgrades.
|
||||
* The path will not end with a "/".
|
||||
*/
|
||||
virtual std::string getDataDirectory() const = 0;
|
||||
|
||||
/**
|
||||
* @see getDataDirectory(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getDataDirectoryLvgl() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the full path for an entry inside the data location for a service.
|
||||
* The data directory is intended to survive OS upgrades.
|
||||
* Configuration data should be stored here.
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
virtual std::string getDataPath(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* @see getDataPath(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getDataPathLvgl(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* Returns the directory path for the system location for a service.
|
||||
* The system directory is not intended to survive OS upgrades.
|
||||
* You should not store configuration data here.
|
||||
* The path will not end with a "/".
|
||||
* This is mainly used for core services.
|
||||
*/
|
||||
virtual std::string getSystemDirectory() const = 0;
|
||||
|
||||
/**
|
||||
* @see getSystemDirectory(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getSystemDirectoryLvgl() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the full path for an entry inside the system location for an app.
|
||||
* The data directory is not intended to survive OS upgrades.
|
||||
* You should not store configuration data here.
|
||||
* This is mainly used for core apps (system/boot/settings type).
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
virtual std::string getSystemPath(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* @see getSystemPath(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getSystemPathLvgl(const std::string& childPath) const = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/service/Service.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
// Forward declarations
|
||||
class ServiceContext;
|
||||
|
||||
typedef std::shared_ptr<Service>(*CreateService)();
|
||||
|
||||
/** A ledger that describes the main parts of a service. */
|
||||
struct ServiceManifest {
|
||||
/** The identifier by which the app is launched by the system and other apps. */
|
||||
std::string id {};
|
||||
|
||||
/** Create the instance of the app */
|
||||
CreateService createService = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "ServiceManifest.h"
|
||||
#include "Service.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
/** Register a service.
|
||||
* @param[in] the service manifest
|
||||
*/
|
||||
void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart = true);
|
||||
|
||||
/** Register a service.
|
||||
* @param[in] the service manifest
|
||||
*/
|
||||
void addService(const ServiceManifest& manifest, bool autoStart = true);
|
||||
|
||||
/** Start a service.
|
||||
* @param[in] the service id as defined in its manifest
|
||||
* @return true on success
|
||||
*/
|
||||
bool startService(const std::string& id);
|
||||
|
||||
/** Stop a service.
|
||||
* @param[in] the service id as defined in its manifest
|
||||
* @return true on success or false when service wasn't running.
|
||||
*/
|
||||
bool stopService(const std::string& id);
|
||||
|
||||
/** Find a service manifest by its id.
|
||||
* @param[in] id the id as defined in the manifest
|
||||
* @return the matching manifest or nullptr when it wasn't found
|
||||
*/
|
||||
std::shared_ptr<const ServiceManifest> _Nullable findManifestId(const std::string& id);
|
||||
|
||||
/** Find a ServiceContext by its manifest id.
|
||||
* @param[in] id the id as defined in the manifest
|
||||
* @return the service context or nullptr when it wasn't found
|
||||
*/
|
||||
std::shared_ptr<ServiceContext> _Nullable findServiceContextById(const std::string& id);
|
||||
|
||||
/** Find a Service by its manifest id.
|
||||
* @param[in] id the id as defined in the manifest
|
||||
* @return the service context or nullptr when it wasn't found
|
||||
*/
|
||||
std::shared_ptr<Service> _Nullable findServiceById(const std::string& id);
|
||||
|
||||
/** Find a Service by its manifest id.
|
||||
* @param[in] id the id as defined in the manifest
|
||||
* @return the service context or nullptr when it wasn't found
|
||||
*/
|
||||
template <typename T>
|
||||
std::shared_ptr<T> _Nullable findServiceById(const std::string& id) {
|
||||
return std::static_pointer_cast<T>(findServiceById(id));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <esp_now.h>
|
||||
#include <functional>
|
||||
|
||||
namespace tt::service::espnow {
|
||||
|
||||
typedef int ReceiverSubscription;
|
||||
constexpr ReceiverSubscription NO_SUBSCRIPTION = -1;
|
||||
|
||||
enum class Mode {
|
||||
Station,
|
||||
AccessPoint
|
||||
};
|
||||
|
||||
struct EspNowConfig {
|
||||
uint8_t masterKey[ESP_NOW_KEY_LEN];
|
||||
uint8_t address[ESP_NOW_ETH_ALEN];
|
||||
Mode mode;
|
||||
uint8_t channel;
|
||||
bool longRange;
|
||||
bool encrypt;
|
||||
|
||||
EspNowConfig(
|
||||
uint8_t masterKey[16],
|
||||
Mode mode,
|
||||
uint8_t channel,
|
||||
bool longRange,
|
||||
bool encrypt
|
||||
) : mode(mode), channel(channel), longRange(longRange), encrypt(encrypt) {
|
||||
memcpy((void*)this->masterKey, (void*)masterKey, 16);
|
||||
}
|
||||
};
|
||||
|
||||
void enable(const EspNowConfig& config);
|
||||
|
||||
void disable();
|
||||
|
||||
bool isEnabled();
|
||||
|
||||
bool addPeer(const esp_now_peer_info_t& peer);
|
||||
|
||||
bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength);
|
||||
|
||||
ReceiverSubscription subscribeReceiver(std::function<void(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length)> onReceive);
|
||||
|
||||
void unsubscribeReceiver(ReceiverSubscription subscription);
|
||||
|
||||
}
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/Mutex.h"
|
||||
#include "Tactility/PubSub.h"
|
||||
#include "Tactility/hal/gps/GpsDevice.h"
|
||||
#include "Tactility/service/Service.h"
|
||||
#include "Tactility/service/ServiceContext.h"
|
||||
#include "Tactility/service/gps/GpsState.h"
|
||||
|
||||
namespace tt::service::gps {
|
||||
|
||||
class GpsService final : public Service {
|
||||
|
||||
private:
|
||||
|
||||
struct GpsDeviceRecord {
|
||||
std::shared_ptr<hal::gps::GpsDevice> device = nullptr;
|
||||
hal::gps::GpsDevice::GgaSubscriptionId satelliteSubscriptionId = -1;
|
||||
hal::gps::GpsDevice::RmcSubscriptionId rmcSubscriptionId = -1;
|
||||
};
|
||||
|
||||
minmea_sentence_rmc rmcRecord;
|
||||
TickType_t rmcTime = 0;
|
||||
|
||||
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
||||
Mutex stateMutex;
|
||||
std::vector<GpsDeviceRecord> deviceRecords;
|
||||
std::shared_ptr<PubSub> statePubSub = std::make_shared<PubSub>();
|
||||
std::unique_ptr<Paths> paths;
|
||||
State state = State::Off;
|
||||
|
||||
bool startGpsDevice(GpsDeviceRecord& deviceRecord);
|
||||
static bool stopGpsDevice(GpsDeviceRecord& deviceRecord);
|
||||
|
||||
GpsDeviceRecord* _Nullable findGpsRecord(const std::shared_ptr<hal::gps::GpsDevice>& record);
|
||||
|
||||
void onGgaSentence(hal::Device::Id deviceId, const minmea_sentence_gga& gga);
|
||||
void onRmcSentence(hal::Device::Id deviceId, const minmea_sentence_rmc& rmc);
|
||||
|
||||
void setState(State newState);
|
||||
|
||||
void addGpsDevice(const std::shared_ptr<hal::gps::GpsDevice>& device);
|
||||
void removeGpsDevice(const std::shared_ptr<hal::gps::GpsDevice>& device);
|
||||
|
||||
bool getConfigurationFilePath(std::string& output) const;
|
||||
|
||||
public:
|
||||
|
||||
void onStart(tt::service::ServiceContext &serviceContext) final;
|
||||
void onStop(tt::service::ServiceContext &serviceContext) final;
|
||||
|
||||
bool addGpsConfiguration(hal::gps::GpsConfiguration configuration);
|
||||
bool removeGpsConfiguration(hal::gps::GpsConfiguration configuration);
|
||||
bool getGpsConfigurations(std::vector<hal::gps::GpsConfiguration>& configurations) const;
|
||||
|
||||
bool startReceiving();
|
||||
void stopReceiving();
|
||||
State getState() const;
|
||||
|
||||
bool hasCoordinates() const;
|
||||
bool getCoordinates(minmea_sentence_rmc& rmc) const;
|
||||
|
||||
/** @return GPS service pubsub that broadcasts State* objects */
|
||||
std::shared_ptr<PubSub> getStatePubsub() const { return statePubSub; }
|
||||
};
|
||||
|
||||
std::shared_ptr<GpsService> findGpsService();
|
||||
|
||||
} // tt::service::gps
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::service::gps {
|
||||
|
||||
enum class State {
|
||||
OnPending,
|
||||
On,
|
||||
OffPending,
|
||||
Off
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "./WifiGlobals.h"
|
||||
#include "./WifiSettings.h"
|
||||
|
||||
#include <Tactility/PubSub.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_wifi.h"
|
||||
#include "WifiSettings.h"
|
||||
#else
|
||||
#include <cstdint>
|
||||
// From esp_wifi_types.h in ESP-IDF 5.2
|
||||
typedef enum {
|
||||
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
|
||||
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
|
||||
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
|
||||
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
|
||||
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
|
||||
WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
|
||||
WIFI_AUTH_WPA2_ENTERPRISE = WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
|
||||
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
|
||||
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
|
||||
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
|
||||
WIFI_AUTH_OWE, /**< authenticate mode : OWE */
|
||||
WIFI_AUTH_WPA3_ENT_192, /**< authenticate mode : WPA3_ENT_SUITE_B_192_BIT */
|
||||
WIFI_AUTH_WPA3_EXT_PSK, /**< authenticate mode : WPA3_PSK_EXT_KEY */
|
||||
WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE, /**< authenticate mode: WPA3_PSK + WPA3_PSK_EXT_KEY */
|
||||
WIFI_AUTH_MAX
|
||||
} wifi_auth_mode_t;
|
||||
#endif
|
||||
|
||||
namespace tt::service::wifi {
|
||||
|
||||
enum class EventType {
|
||||
/** Radio was turned on */
|
||||
RadioStateOn,
|
||||
/** Radio is turning on. */
|
||||
RadioStateOnPending,
|
||||
/** Radio is turned off */
|
||||
RadioStateOff,
|
||||
/** Radio is turning off */
|
||||
RadioStateOffPending,
|
||||
/** Started scanning for access points */
|
||||
ScanStarted,
|
||||
/** Finished scanning for access points */ // TODO: 1 second validity
|
||||
ScanFinished,
|
||||
Disconnected,
|
||||
ConnectionPending,
|
||||
ConnectionSuccess,
|
||||
ConnectionFailed
|
||||
};
|
||||
|
||||
enum class RadioState {
|
||||
OnPending,
|
||||
On,
|
||||
ConnectionPending,
|
||||
ConnectionActive,
|
||||
OffPending,
|
||||
Off,
|
||||
};
|
||||
|
||||
struct Event {
|
||||
EventType type;
|
||||
};
|
||||
|
||||
struct ApRecord {
|
||||
std::string ssid;
|
||||
int8_t rssi;
|
||||
int32_t channel;
|
||||
wifi_auth_mode_t auth_mode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get wifi pubsub that broadcasts Event objects
|
||||
* @return PubSub
|
||||
*/
|
||||
std::shared_ptr<PubSub> getPubsub();
|
||||
|
||||
/** @return Get the current radio state */
|
||||
RadioState getRadioState();
|
||||
|
||||
/** For logging purposes */
|
||||
const char* radioStateToString(RadioState state);
|
||||
|
||||
/**
|
||||
* @brief Request scanning update. Returns immediately. Results are through pubsub.
|
||||
*/
|
||||
void scan();
|
||||
|
||||
/** @return true if wifi is actively scanning */
|
||||
bool isScanning();
|
||||
|
||||
/** @return true the ssid name or empty string */
|
||||
std::string getConnectionTarget();
|
||||
|
||||
/** @return the access points from the last scan (if any). It only contains public APs. */
|
||||
std::vector<ApRecord> getScanResults();
|
||||
|
||||
/**
|
||||
* @brief Overrides the default scan result size of 16.
|
||||
* @param[in] records the record limit for the scan result (84 bytes per record!)
|
||||
*/
|
||||
void setScanRecords(uint16_t records);
|
||||
|
||||
/**
|
||||
* @brief Enable/disable the radio. Ignores input if desired state matches current state.
|
||||
* @param[in] enabled
|
||||
*/
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Connect to a network. Disconnects any existing connection.
|
||||
* Returns immediately but runs in the background. Results are through pubsub.
|
||||
* @param[in] ap
|
||||
* @param[in] remember whether to save the ap data to the settings upon successful connection
|
||||
*/
|
||||
void connect(const settings::WifiApSettings* ap, bool remember);
|
||||
|
||||
/** @brief Disconnect from the access point. Doesn't have any effect when not connected. */
|
||||
void disconnect();
|
||||
|
||||
/** @return true if the connection isn't unencrypted. */
|
||||
bool isConnectionSecure();
|
||||
|
||||
/** @return the RSSI value (negative number) or return 1 when not connected. */
|
||||
int getRssi();
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#define TT_WIFI_AUTO_CONNECT true // Default setting for new Wi-Fi entries
|
||||
|
||||
#define TT_WIFI_SCAN_RECORD_LIMIT 16 // default, can be overridden
|
||||
|
||||
#define TT_WIFI_SSID_LIMIT 32 // 32 characters/octets, according to IEEE 802.11-2020 spec
|
||||
#define TT_WIFI_CREDENTIALS_PASSWORD_LIMIT 64 // 64 characters/octets, according to IEEE 802.11-2020 spec
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "WifiGlobals.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace tt::service::wifi::settings {
|
||||
|
||||
/**
|
||||
* This struct is stored as-is into NVS flash.
|
||||
*
|
||||
* The SSID and secret are increased by 1 byte to facilitate string null termination.
|
||||
* This makes it easier to use the char array as a string in various places.
|
||||
*/
|
||||
struct WifiApSettings {
|
||||
char ssid[TT_WIFI_SSID_LIMIT + 1] = { 0 };
|
||||
char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT + 1] = { 0 };
|
||||
int32_t channel = 0;
|
||||
bool auto_connect = true;
|
||||
};
|
||||
|
||||
bool contains(const char* ssid);
|
||||
|
||||
bool load(const char* ssid, WifiApSettings* settings);
|
||||
|
||||
bool save(const WifiApSettings* settings);
|
||||
|
||||
bool remove(const char* ssid);
|
||||
|
||||
void setEnableOnBoot(bool enable);
|
||||
|
||||
bool shouldEnableOnBoot();
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::time {
|
||||
|
||||
/**
|
||||
* Set the timezone
|
||||
* @param[in] name human-readable name
|
||||
* @param[in] code the technical code (from timezones.csv)
|
||||
*/
|
||||
void setTimeZone(const std::string& name, const std::string& code);
|
||||
|
||||
/**
|
||||
* Get the name of the timezone
|
||||
*/
|
||||
std::string getTimeZoneName();
|
||||
|
||||
/**
|
||||
* Get the code of the timezone (see timezones.csv)
|
||||
*/
|
||||
std::string getTimeZoneCode();
|
||||
|
||||
/** @return true when clocks should be shown as a 24 hours one instead of 12 hours */
|
||||
bool isTimeFormat24Hour();
|
||||
|
||||
/** Set whether clocks should be shown as a 24 hours instead of 12 hours
|
||||
* @param[in] show24Hour
|
||||
*/
|
||||
void setTimeFormat24Hour(bool show24Hour);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user