UI refactor (#50)

This commit is contained in:
Matthew Colvin 2023-10-22 13:35:49 -05:00 committed by GitHub
parent 59897369ec
commit 7c9a0623fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 4726 additions and 2307 deletions

View File

@ -81,6 +81,6 @@
"variant": "cpp"
},
"cmake.sourceDirectory": "${workspaceFolder}/.pio/libdeps/esp32/Adafruit BusIO",
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"idf.portWin": "COM8"
}

View File

@ -3,6 +3,7 @@
#pragma once
#include "BatteryInterface.h"
#include "DisplayAbstract.h"
#include "KeyPressAbstract.hpp"
#include "wifiHandlerInterface.h"
#include "Notification.hpp"
@ -16,24 +17,24 @@ public:
/// @brief Override in order to do setup of hardware devices post construction
virtual void init() = 0;
/// @brief Override to processing in main thread
virtual void loopHandler() = 0;
/// @brief Override to allow printing of a message for debugging
/// @param message - Debug message
virtual void debugPrint(const char* fmt, ...) = 0;
virtual void debugPrint(const char *fmt, ...) = 0;
virtual std::shared_ptr<BatteryInterface> battery() = 0;
virtual std::shared_ptr<BatteryInterface> battery() = 0;
virtual std::shared_ptr<DisplayAbstract> display() = 0;
virtual std::shared_ptr<wifiHandlerInterface> wifi() = 0;
virtual std::shared_ptr<KeyPressAbstract> keys() = 0;
virtual char getCurrentDevice() = 0;
virtual void setCurrentDevice(char currentDevice) = 0;
virtual bool getWakeupByIMUEnabled() = 0;
virtual void setWakeupByIMUEnabled(bool wakeupByIMUEnabled) = 0;
virtual uint16_t getSleepTimeout() = 0;
virtual void setSleepTimeout(uint16_t sleepTimeout) = 0;
protected:
};

View File

@ -0,0 +1,20 @@
#include "HardwareFactory.hpp"
#if OMOTE_SIM
#include "HardwareSimulator.hpp"
#endif
#if OMOTE_ESP32
#include "HardwareRevX.hpp"
#endif
#if OMOTE_SIM
std::unique_ptr<HardwareAbstract> HardwareFactory::mHardware =
std::make_unique<HardwareSimulator>();
#endif
#if OMOTE_ESP32
std::unique_ptr<HardwareAbstract> HardwareFactory::mHardware =
std::make_unique<HardwareRevX>();
#endif
HardwareAbstract &HardwareFactory::getAbstract() { return *mHardware; }

View File

@ -0,0 +1,11 @@
#include "HardwareAbstract.hpp"
#include <memory>
/**
* @brief The HardwareFactory is responsible for making the
*/
class HardwareFactory {
public:
static HardwareAbstract &getAbstract();
static std::unique_ptr<HardwareAbstract> mHardware;
};

View File

@ -0,0 +1,8 @@
#include "KeyPressAbstract.hpp"
KeyPressAbstract::KeyPressAbstract() {}
void KeyPressAbstract::RegisterKeyPressHandler(
std::function<bool(KeyPressAbstract::KeyEvent)> aKeyEventHandler) {
mKeyEventHandler = std::move(aKeyEventHandler);
}

View File

@ -0,0 +1,73 @@
#pragma once
#include "Notification.hpp"
#include <memory>
class KeyPressAbstract {
public:
// Keys from Top Down left to right.
enum class KeyId {
Power,
// Top 4 Buttons left to right
Stop,
Rewind,
Play,
FastForward,
// Buttons around D Pad
Menu,
Info,
Back,
Source,
// D Pad
Up,
Down,
Left,
Right,
Center,
// Volume Channel and 2 between
VolUp,
VolDown,
Mute,
Record,
ChannelUp,
ChannelDown,
// Bottom 4 buttons left to right
Aux1,
Aux2,
Aux3,
Aux4,
INVALID
};
class KeyEvent {
public:
enum class Type { Press, Release, INVALID };
KeyEvent() = default;
KeyEvent(const KeyId aId, const Type aType) : mId(aId), mType(aType) {}
KeyId mId = KeyId::INVALID;
Type mType = Type::INVALID;
};
KeyPressAbstract();
/// @brief Register a SINGLE handler to be used for proccessing keys
/// @param aKeyEventHandler - Callable the Handles KeyEvent
void RegisterKeyPressHandler(std::function<bool(KeyEvent)> aKeyEventHandler);
protected:
/// @brief Function ment to be called regularly to allow
/// proccesssing of key presses by calling mKeyEventHandler
/// best case this is done on a seprate thread/task
/// since it could take a while to handle a KeyPress
virtual void HandleKeyPresses() = 0;
/// @brief Function to queue up Key events to be handled later on by
/// HandleKeyPresses() hopefully on a seprate thread or task
/// This function should be implemented in a way that makes it ISR
/// safe
/// @param aJustOccuredKeyEvent - A Key even that just occured
virtual void QueueKeyEvent(KeyEvent aJustOccuredKeyEvent) = 0;
std::function<bool(KeyEvent)> mKeyEventHandler;
};

View File

@ -1,25 +1,54 @@
#pragma once
#include <string>
#include <memory>
#include <functional>
#include <memory>
#include <string>
typedef struct {
std::string ssid;
int rssi;
} WifiInfo;
class wifiHandlerInterface {
public:
wifiHandlerInterface() = default;
struct WifiInfo {
WifiInfo() = default;
WifiInfo(std::string aSsid, int aRssi) : ssid(aSsid), rssi(aRssi) {}
typedef struct {
bool isConnected;
std::string IP;
std::string ssid;
}wifiStatus;
std::string ssid = "";
int rssi = 0;
};
class wifiHandlerInterface{
public:
virtual bool isAvailable() = 0;
virtual void scan() = 0;
virtual void connect(std::shared_ptr<std::string> ssid, std::shared_ptr<std::string> password) = 0;
virtual void onScanDone(std::function<void (std::shared_ptr<std::vector<WifiInfo>>)> function) = 0;
virtual void onStatusUpdate(std::function<void (std::shared_ptr<wifiStatus>)> function) = 0;
virtual void begin() = 0;
struct wifiStatus {
wifiStatus() = default;
wifiStatus(bool aConnected, std::string aIp, std::string aSsid)
: isConnected(aConnected), IP(aIp), ssid(aSsid){};
bool isConnected = false;
std::string IP = "";
std::string ssid = "";
};
typedef std::vector<WifiInfo> ScanDoneDataTy;
typedef Notification<ScanDoneDataTy> ScanNotificationTy;
/// @brief Initialize the wifi handler
virtual void begin() = 0;
/// @brief Trigger a scan scan for wifi networks
virtual void scan() = 0;
/// @brief Attempt a connection to the wifi using the provided credentials
virtual void connect(std::string ssid, std::string password) = 0;
/// @brief Get the status of the current wifi connection
virtual wifiStatus GetStatus() = 0;
// Register for Scan Notification to handle when scans are completed
std::shared_ptr<ScanNotificationTy> ScanCompleteNotification() {
return mScanNotification;
};
// Register for Status notifications to handle changes in status
std::shared_ptr<Notification<wifiStatus>> WifiStatusNotification() {
return mStatusUpdate;
};
protected:
std::shared_ptr<ScanNotificationTy> mScanNotification =
std::make_shared<ScanNotificationTy>();
std::shared_ptr<Notification<wifiStatus>> mStatusUpdate =
std::make_shared<Notification<wifiStatus>>();
};

View File

@ -1,27 +0,0 @@
// OMOTE Hardware Abstraction
// 2023 Matthew Colvin
#pragma once
#include <lvgl.h>
#include <string>
class HardwareInterface {
public:
struct batteryStatus {
/// @brief Percent of battery remaining (0-100]
int percentage;
/// @brief Voltage of battery in millivolts
int voltage;
/// @brief True - Battery is Charging
/// False - Battery discharging
bool isCharging;
};
HardwareInterface() = default;
virtual void init() = 0;
virtual void sendIR() = 0;
virtual void MQTTPublish(const char *topic, const char *payload) = 0;
virtual batteryStatus getBatteryPercentage() = 0;
virtual void debugPrint(std::string message) = 0;
};

View File

@ -1,29 +1,90 @@
#pragma once
#include <vector>
#include <functional>
#include <map>
#include <memory>
template <class... notifyData>
class Notification{
public:
typedef std::function<void(notifyData...)> HandlerTy;
template <class... notifyData> class Handler;
template <class... notifyData> class Notification {
friend class Handler<notifyData...>;
Notification() = default;
void onNotify(HandlerTy aHandler);
void notify(notifyData... notifySendData);
public:
typedef std::function<void(notifyData...)> HandlerTy;
typedef int HandlerID;
private:
std::vector<HandlerTy> mFunctionHandlers;
Notification() { mIdMaker = 0; };
void notify(notifyData... notifySendData);
protected:
HandlerID onNotify(HandlerTy aHandler);
void unregister(HandlerID aHandler);
private:
std::map<HandlerID, HandlerTy> mFunctionHandlers;
HandlerID mIdMaker;
};
template <class... handlerData>
void Notification<handlerData...>::onNotify(HandlerTy aHandler){
mFunctionHandlers.push_back(std::move(aHandler));
int Notification<handlerData...>::onNotify(HandlerTy aHandler) {
if (aHandler) {
mFunctionHandlers[++mIdMaker] = std::move(aHandler);
return mIdMaker;
} else {
return -1;
}
}
template <class... outboundData>
void Notification<outboundData...>::notify(outboundData... notifySendData){
for (auto handler : mFunctionHandlers){
handler(notifySendData...);
void Notification<outboundData...>::notify(outboundData... notifySendData) {
for (auto handler : mFunctionHandlers) {
handler.second(notifySendData...);
}
}
template <class... handlerData>
void Notification<handlerData...>::unregister(HandlerID aHandlerId) {
auto handlerToUnRegister =
std::find_if(mFunctionHandlers.begin(), mFunctionHandlers.end(),
[aHandlerId](auto registeredHandler) {
return aHandlerId == registeredHandler.first;
});
if (handlerToUnRegister != mFunctionHandlers.end()) {
mFunctionHandlers.erase(handlerToUnRegister);
}
}
template <class... notifyData> class Handler {
public:
typedef std::function<void(notifyData...)> callableTy;
void operator=(Handler &other) = delete;
Handler() = default;
Handler(std::shared_ptr<Notification<notifyData...>> aNotification,
callableTy aCallable = nullptr)
: mNotification(aNotification),
mHandlerId(aNotification->onNotify(aCallable)) {}
virtual ~Handler() {
if (mHandlerId >= 0) {
mNotification->unregister(mHandlerId);
}
}
}
void operator=(callableTy aHandler) {
if (mHandlerId >= 0) {
mNotification->unregister(mHandlerId);
mHandlerId = -1;
}
if (aHandler) {
mHandlerId = mNotification->onNotify(aHandler);
}
}
void
SetNotification(std::shared_ptr<Notification<notifyData...>> aNotification) {
mNotification = aNotification;
}
private:
std::shared_ptr<Notification<notifyData...>> mNotification = nullptr;
int mHandlerId = -1;
};

View File

@ -2,8 +2,6 @@
#include "display.hpp"
#include "wifihandler.hpp"
std::shared_ptr<HardwareRevX> HardwareRevX::mInstance = nullptr;
void HardwareRevX::initIO() {
// Button Pin Definition
pinMode(SW_1, OUTPUT);
@ -50,9 +48,7 @@ void HardwareRevX::initIO() {
gpio_deep_sleep_hold_dis();
}
HardwareRevX::HardwareRevX():
HardwareAbstract(){
}
HardwareRevX::HardwareRevX() : HardwareAbstract() {}
HardwareRevX::WakeReason getWakeReason() {
// Find out wakeup cause
@ -74,11 +70,15 @@ void HardwareRevX::init() {
Serial.begin(115200);
mDisplay = Display::getInstance();
mBattery = std::make_shared<Battery>(ADC_BAT,CRG_STAT);
mBattery = std::make_shared<Battery>(ADC_BAT, CRG_STAT);
mWifiHandler = wifiHandler::getInstance();
mKeys = std::make_shared<Keys>();
restorePreferences();
mDisplay->onTouch([this]([[maybe_unused]] auto touchPoint){ standbyTimer = this->getSleepTimeout();});
mTouchHandler.SetNotification(mDisplay->TouchNotification());
mTouchHandler = [this]([[maybe_unused]] auto touchPoint) {
standbyTimer = this->getSleepTimeout();
};
setupIMU();
setupIR();
@ -86,37 +86,26 @@ void HardwareRevX::init() {
debugPrint("Finished Hardware Setup in %d", millis());
}
void HardwareRevX::debugPrint(const char* fmt, ...)
{
void HardwareRevX::debugPrint(const char *fmt, ...) {
char result[100];
va_list arguments;
va_start(arguments, fmt);
vsnprintf(result, 100, fmt, arguments);
va_end (arguments);
va_end(arguments);
Serial.print(result);
}
std::shared_ptr<HardwareRevX> HardwareRevX::getInstance(){
if (!mInstance) {
mInstance = std::shared_ptr<HardwareRevX>(new HardwareRevX());
}
return mInstance;
}
std::shared_ptr<wifiHandlerInterface> HardwareRevX::wifi()
{
std::shared_ptr<wifiHandlerInterface> HardwareRevX::wifi() {
return mWifiHandler;
}
std::shared_ptr<BatteryInterface> HardwareRevX::battery(){
return mBattery;
}
std::shared_ptr<BatteryInterface> HardwareRevX::battery() { return mBattery; }
std::shared_ptr<DisplayAbstract> HardwareRevX::display(){
return mDisplay;
}
std::shared_ptr<DisplayAbstract> HardwareRevX::display() { return mDisplay; }
std::shared_ptr<KeyPressAbstract> HardwareRevX::keys() { return mKeys; }
void HardwareRevX::activityDetection() {
static int accXold;
@ -142,27 +131,21 @@ void HardwareRevX::activityDetection() {
accZold = accZ;
}
char HardwareRevX::getCurrentDevice(){
return currentDevice;
}
char HardwareRevX::getCurrentDevice() { return currentDevice; }
void HardwareRevX::setCurrentDevice(char currentDevice){
void HardwareRevX::setCurrentDevice(char currentDevice) {
this->currentDevice = currentDevice;
}
bool HardwareRevX::getWakeupByIMUEnabled(){
return wakeupByIMUEnabled;
}
bool HardwareRevX::getWakeupByIMUEnabled() { return wakeupByIMUEnabled; }
void HardwareRevX::setWakeupByIMUEnabled(bool wakeupByIMUEnabled){
void HardwareRevX::setWakeupByIMUEnabled(bool wakeupByIMUEnabled) {
this->wakeupByIMUEnabled = wakeupByIMUEnabled;
}
uint16_t HardwareRevX::getSleepTimeout(){
return sleepTimeout;
}
uint16_t HardwareRevX::getSleepTimeout() { return sleepTimeout; }
void HardwareRevX::setSleepTimeout(uint16_t sleepTimeout){
void HardwareRevX::setSleepTimeout(uint16_t sleepTimeout) {
this->sleepTimeout = sleepTimeout;
standbyTimer = sleepTimeout;
}
@ -287,7 +270,7 @@ void HardwareRevX::restorePreferences() {
currentDevice = preferences.getUChar("currentDevice");
sleepTimeout = preferences.getUInt("sleepTimeout");
// setting the default to prevent a 0ms sleep timeout
if(sleepTimeout == 0){
if (sleepTimeout == 0) {
sleepTimeout = SLEEP_TIMEOUT;
}
}
@ -320,7 +303,7 @@ void HardwareRevX::startTasks() {}
void HardwareRevX::loopHandler() {
standbyTimer < 2000 ? mDisplay->sleep() : mDisplay->wake();
// TODO move to debug task
// Blink debug LED at 1 Hz
digitalWrite(USER_LED, millis() % 1000 > 500);
@ -335,31 +318,4 @@ void HardwareRevX::loopHandler() {
}
IMUTaskTimer = millis();
}
// Keypad Handling
customKeypad.getKey(); // Populate key list
for (int i = 0; i < LIST_MAX;
i++) { // Handle multiple keys (Not really necessary in this case)
if (customKeypad.key[i].kstate == PRESSED ||
customKeypad.key[i].kstate == HOLD) {
standbyTimer =
sleepTimeout; // Reset the sleep timer when a button is pressed
int keyCode = customKeypad.key[i].kcode;
Serial.println(customKeypad.key[i].kchar);
// Send IR codes depending on the current device (tabview page)
if (currentDevice == 1) {
IrSender.sendRC5(IrSender.encodeRC5X(
0x00, keyMapTechnisat[keyCode / ROWS][keyCode % ROWS]));
} else if (currentDevice == 2) {
IrSender.sendSony((keyCode / ROWS) * (keyCode % ROWS), 15);
}
}
}
// IR Test
// tft.drawString("IR Command: ", 10, 90, 1);
// decode_results results;
// if (IrReceiver.decode(&results)) {
// IrReceiver.resume(); // Enable receiving of the next value
//} //tft.drawString(String(results.command) + " ", 80, 90, 1);
//
}

View File

@ -2,44 +2,44 @@
#include "SparkFunLIS3DH.h"
#include "HardwareAbstract.hpp"
#include "lvgl.h"
#include "battery.hpp"
#include "lvgl.h"
#include "wifihandler.hpp"
#include <Arduino.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <IRutils.h>
#include <Keypad.h> // modified for inverted logic
#include <Preferences.h>
#include <PubSubClient.h>
#include <functional>
#include <memory>
#include "wifihandler.hpp"
#include "omoteconfig.h"
#include "BatteryInterface.h"
#include "wifiHandlerInterface.h"
#include "display.hpp"
#include "keys.hpp"
#include "wifiHandlerInterface.h"
class HardwareRevX : public HardwareAbstract {
public:
enum class WakeReason { RESET, IMU, KEYPAD };
static std::shared_ptr<HardwareRevX> getInstance();
static std::weak_ptr<HardwareRevX> getRefrence() { return getInstance(); }
HardwareRevX();
// HardwareAbstract
virtual void init() override;
virtual void debugPrint(const char* fmt, ...) override;
virtual void debugPrint(const char *fmt, ...) override;
virtual std::shared_ptr<BatteryInterface> battery() override;
virtual std::shared_ptr<DisplayAbstract> display() override;
virtual std::shared_ptr<wifiHandlerInterface> wifi() override;
virtual std::shared_ptr<KeyPressAbstract> keys() override;
virtual char getCurrentDevice() override;
virtual void setCurrentDevice(char currentDevice) override;
virtual bool getWakeupByIMUEnabled() override;
virtual void setWakeupByIMUEnabled(bool wakeupByIMUEnabled) override;
@ -48,7 +48,8 @@ public:
/// @brief To be ran in loop out in main
// TODO move to a freertos task
void loopHandler();
void loopHandler() override;
protected:
// Init Functions to setup hardware
void initIO();
@ -65,11 +66,10 @@ protected:
void startTasks();
private:
HardwareRevX();
std::shared_ptr<Battery> mBattery;
std::shared_ptr<Display> mDisplay;
std::shared_ptr<wifiHandler> mWifiHandler;
std::shared_ptr<Keys> mKeys;
// IMU Motion Detection
LIS3DH IMU = LIS3DH(I2C_MODE, 0x19); // Default constructor is I2C, addr 0x19.
int standbyTimer = SLEEP_TIMEOUT;
@ -86,32 +86,6 @@ private:
IRsend IrSender = IRsend(IR_LED, true);
IRrecv IrReceiver = IRrecv(IR_RX);
// Keypad declarations
static const byte ROWS = 5; // four rows
static const byte COLS = 5; // four columns
// define the symbols on the buttons of the keypads
char hexaKeys[ROWS][COLS] = {
{'s', '^', '-', 'm', 'r'}, // source, channel+, Volume-, mute, record
{'i', 'r', '+', 'k', 'd'}, // info, right, Volume+, OK, down
{'4', 'v', '1', '3', '2'}, // blue, channel-, red, yellow, green
{'>', 'o', 'b', 'u', 'l'}, // forward, off, back, up, left
{'?', 'p', 'c', '<', '='} // ?, play, config, rewind, stop
};
byte rowPins[ROWS] = {SW_A, SW_B, SW_C, SW_D,
SW_E}; // connect to the row pinouts of the keypad
byte colPins[COLS] = {SW_1, SW_2, SW_3, SW_4,
SW_5}; // connect to the column pinouts of the keypad
Keypad customKeypad =
Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
byte keyMapTechnisat[ROWS][COLS] = {{0x69, 0x20, 0x11, 0x0D, 0x56},
{0x4F, 0x37, 0x10, 0x57, 0x51},
{0x6E, 0x21, 0x6B, 0x6D, 0x6C},
{0x34, 0x0C, 0x22, 0x50, 0x55},
{'?', 0x35, 0x2F, 0x32, 0x36}};
byte virtualKeyMapTechnisat[10] = {0x1, 0x2, 0x3, 0x4, 0x5,
0x6, 0x7, 0x8, 0x9, 0x0};
static std::shared_ptr<HardwareRevX> mInstance;
Handler<TS_Point> mTouchHandler;
};

View File

@ -1,43 +1,39 @@
#include "display.hpp"
#include "omoteconfig.h"
#include "Wire.h"
#include "driver/ledc.h"
#include "omoteconfig.h"
std::shared_ptr<Display> Display::getInstance()
{
if (DisplayAbstract::mInstance == nullptr)
{
DisplayAbstract::mInstance = std::shared_ptr<Display>(new Display(LCD_BL, LCD_EN));
}
return std::static_pointer_cast<Display>(mInstance);
std::shared_ptr<Display> Display::getInstance() {
if (DisplayAbstract::mInstance == nullptr) {
DisplayAbstract::mInstance =
std::shared_ptr<Display>(new Display(LCD_BL, LCD_EN));
}
return std::static_pointer_cast<Display>(mInstance);
}
Display::Display(int backlight_pin, int enable_pin): DisplayAbstract(),
mBacklightPin(backlight_pin),
mEnablePin(enable_pin),
tft(TFT_eSPI()),
touch(Adafruit_FT6206())
{
pinMode(mEnablePin, OUTPUT);
digitalWrite(mEnablePin, HIGH);
pinMode(mBacklightPin, OUTPUT);
digitalWrite(mBacklightPin, HIGH);
Display::Display(int backlight_pin, int enable_pin)
: DisplayAbstract(), mBacklightPin(backlight_pin), mEnablePin(enable_pin),
tft(TFT_eSPI()), touch(Adafruit_FT6206()) {
pinMode(mEnablePin, OUTPUT);
digitalWrite(mEnablePin, HIGH);
pinMode(mBacklightPin, OUTPUT);
digitalWrite(mBacklightPin, HIGH);
setupBacklight(); // This eliminates the flash of the backlight
setupBacklight(); // This eliminates the flash of the backlight
// Slowly charge the VSW voltage to prevent a brownout
// Workaround for hardware rev 1!
for(int i = 0; i < 100; i++){
digitalWrite(this->mEnablePin, HIGH); // LCD Logic off
delayMicroseconds(1);
digitalWrite(this->mEnablePin, LOW); // LCD Logic on
}
// Slowly charge the VSW voltage to prevent a brownout
// Workaround for hardware rev 1!
for (int i = 0; i < 100; i++) {
digitalWrite(this->mEnablePin, HIGH); // LCD Logic off
delayMicroseconds(1);
digitalWrite(this->mEnablePin, LOW); // LCD Logic on
}
setupTFT();
setupTouchScreen();
mFadeTaskMutex = xSemaphoreCreateBinary();
xSemaphoreGive(mFadeTaskMutex);
setupTFT();
setupTouchScreen();
mFadeTaskMutex = xSemaphoreCreateBinary();
xSemaphoreGive(mFadeTaskMutex);
}
void Display::setupBacklight() {
@ -62,10 +58,6 @@ void Display::setupBacklight() {
ledc_timer_config(&ledc_timer);
}
void Display::onTouch(Notification<TS_Point>::HandlerTy aTouchHandler){
mTouchEvent.onNotify(std::move(aTouchHandler));
}
void Display::setupTFT() {
delay(100);
tft.init();
@ -75,25 +67,22 @@ void Display::setupTFT() {
tft.setSwapBytes(true);
}
void Display::setupTouchScreen(){
// Configure i2c pins and set frequency to 400kHz
Wire.begin(SDA, SCL, 400000);
touch.begin(128); // Initialize touchscreen and set sensitivity threshold
void Display::setupTouchScreen() {
// Configure i2c pins and set frequency to 400kHz
Wire.begin(TFT_SDA, TFT_SCL, 400000);
touch.begin(128); // Initialize touchscreen and set sensitivity threshold
}
void Display::setBrightness(uint8_t brightness)
{
void Display::setBrightness(uint8_t brightness) {
mAwakeBrightness = brightness;
Serial.print("Set Brightness:");
Serial.println(mAwakeBrightness);
startFade();
}
uint8_t Display::getBrightness(){
return mAwakeBrightness;
}
uint8_t Display::getBrightness() { return mAwakeBrightness; }
void Display::setCurrentBrightness(uint8_t brightness){
void Display::setCurrentBrightness(uint8_t brightness) {
mBrightness = brightness;
auto duty = static_cast<int>(mBrightness);
ledcWrite(LCD_BACKLIGHT_LEDC_CHANNEL, duty);
@ -101,25 +90,24 @@ void Display::setCurrentBrightness(uint8_t brightness){
// Serial.println(mBrightness);
}
void Display::turnOff()
{
digitalWrite(this->mBacklightPin, HIGH);
digitalWrite(this->mEnablePin, HIGH);
pinMode(this->mBacklightPin, INPUT);
pinMode(this->mEnablePin, INPUT);
gpio_hold_en((gpio_num_t) mBacklightPin);
gpio_hold_en((gpio_num_t) mEnablePin);
void Display::turnOff() {
digitalWrite(this->mBacklightPin, HIGH);
digitalWrite(this->mEnablePin, HIGH);
pinMode(this->mBacklightPin, INPUT);
pinMode(this->mEnablePin, INPUT);
gpio_hold_en((gpio_num_t)mBacklightPin);
gpio_hold_en((gpio_num_t)mEnablePin);
}
void Display::screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data){
// int16_t touchX, touchY;
void Display::screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
// int16_t touchX, touchY;
touchPoint = touch.getPoint();
int16_t touchX = touchPoint.x;
int16_t touchY = touchPoint.y;
bool touched = false;
if ((touchX > 0) || (touchY > 0)) {
touched = true;
mTouchEvent.notify(touchPoint);
mTouchEvent->notify(touchPoint);
}
if (!touched) {
@ -140,48 +128,50 @@ void Display::screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data){
}
}
void Display::fadeImpl(void* ){
void Display::fadeImpl(void *) {
bool fadeDone = false;
while(!fadeDone){
while (!fadeDone) {
fadeDone = getInstance()->fade();
vTaskDelay(3 / portTICK_PERIOD_MS); // 3 miliseconds between steps
// 0 - 255 will take about .75 seconds to fade up.
vTaskDelay(3 / portTICK_PERIOD_MS); // 3 miliseconds between steps
// 0 - 255 will take about .75 seconds to fade up.
}
xSemaphoreTake(getInstance()->mFadeTaskMutex,portMAX_DELAY);
xSemaphoreTake(getInstance()->mFadeTaskMutex, portMAX_DELAY);
getInstance()->mDisplayFadeTask = nullptr;
xSemaphoreGive(getInstance()->mFadeTaskMutex);
vTaskDelete(nullptr); // Delete Fade Task
}
bool Display::fade(){
//Early return no fade needed.
if (mBrightness == mAwakeBrightness ||
isAsleep && mBrightness == 0){return true;}
bool fadeDown = isAsleep || mBrightness > mAwakeBrightness;
if (fadeDown){
bool Display::fade() {
// Early return no fade needed.
if (mBrightness == mAwakeBrightness || isAsleep && mBrightness == 0) {
return true;
}
bool fadeDown = isAsleep || mBrightness > mAwakeBrightness;
if (fadeDown) {
setCurrentBrightness(mBrightness - 1);
auto setPoint = isAsleep ? 0 : mAwakeBrightness;
return mBrightness == setPoint;
}else{
} else {
setCurrentBrightness(mBrightness + 1);
return mBrightness == mAwakeBrightness;
}
}
void Display::startFade(){
xSemaphoreTake(mFadeTaskMutex,portMAX_DELAY);
void Display::startFade() {
xSemaphoreTake(mFadeTaskMutex, portMAX_DELAY);
// Only Create Task if it is needed
if(mDisplayFadeTask == nullptr){
xTaskCreate(&Display::fadeImpl, "Display Fade Task",
1024, nullptr, 5, &mDisplayFadeTask);
if (mDisplayFadeTask == nullptr) {
xTaskCreate(&Display::fadeImpl, "Display Fade Task", 1024, nullptr, 5,
&mDisplayFadeTask);
}
xSemaphoreGive(mFadeTaskMutex);
}
void Display::flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
void Display::flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);

View File

@ -2,10 +2,10 @@
#include "DisplayAbstract.h"
#include "HardwareAbstract.hpp"
#include "Notification.hpp"
#include <Adafruit_FT6206.h>
#include <memory>
#include <TFT_eSPI.h>
#include "driver/ledc.h"
#include <Adafruit_FT6206.h>
#include <TFT_eSPI.h>
#include <memory>
/*LEDC Channel to use for the LCD backlight*/
#define LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_5
@ -16,58 +16,71 @@
#define DEFAULT_BACKLIGHT_BRIGHTNESS 128
class Display : public DisplayAbstract {
public:
static std::shared_ptr<Display> getInstance();
class Display: public DisplayAbstract
{
public:
static std::shared_ptr<Display> getInstance();
/// @brief Set brightness setting and fade to it
/// @param brightness
virtual void setBrightness(uint8_t brightness) override;
virtual uint8_t getBrightness() override;
virtual void turnOff() override;
void onTouch(Notification<TS_Point>::HandlerTy aTouchHandler);
/// @brief Set brightness setting and fade to it
/// @param brightness
virtual void setBrightness(uint8_t brightness) override;
virtual uint8_t getBrightness() override;
virtual void turnOff() override;
inline void wake() {if(isAsleep) {isAsleep = false; startFade();}}
inline void sleep() {if(!isAsleep){isAsleep = true; startFade();}}
std::shared_ptr<Notification<TS_Point>> TouchNotification() {
return mTouchEvent;
}
protected:
virtual void flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
virtual void screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) override;
/// @brief Fade toward brightness based on isAwake
/// @return True - Fade complete
/// False - Fade set point not reached
bool fade();
/// @brief Start the Fade task
void startFade();
inline void wake() {
if (isAsleep) {
isAsleep = false;
startFade();
}
}
inline void sleep() {
if (!isAsleep) {
isAsleep = true;
startFade();
}
}
/// @brief Set the actual display brightness right now
/// @param brightness
void setCurrentBrightness(uint8_t brightness);
protected:
virtual void flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p);
virtual void screenInput(lv_indev_drv_t *indev_driver,
lv_indev_data_t *data) override;
private:
Display(int backlight_pin, int enable_pin);
void setupTFT();
void setupTouchScreen();
void setupBacklight();
int mEnablePin;
int mBacklightPin;
TFT_eSPI tft;
/// @brief Fade toward brightness based on isAwake
/// @return True - Fade complete
/// False - Fade set point not reached
bool fade();
/// @brief Start the Fade task
void startFade();
Adafruit_FT6206 touch;
TS_Point touchPoint;
TS_Point oldPoint;
Notification<TS_Point> mTouchEvent;
/// @brief Set the actual display brightness right now
/// @param brightness
void setCurrentBrightness(uint8_t brightness);
TaskHandle_t mDisplayFadeTask = nullptr;
SemaphoreHandle_t mFadeTaskMutex = nullptr;
static void fadeImpl(void* aBrightness);
private:
Display(int backlight_pin, int enable_pin);
void setupTFT();
void setupTouchScreen();
void setupBacklight();
uint8_t mBrightness = 0; // Current display brightness
uint8_t mAwakeBrightness = 100; // Current setting for brightness when awake
bool isAsleep = false;
int mEnablePin;
int mBacklightPin;
TFT_eSPI tft;
Adafruit_FT6206 touch;
TS_Point touchPoint;
TS_Point oldPoint;
std::shared_ptr<Notification<TS_Point>> mTouchEvent =
std::make_shared<Notification<TS_Point>>();
TaskHandle_t mDisplayFadeTask = nullptr;
SemaphoreHandle_t mFadeTaskMutex = nullptr;
static void fadeImpl(void *aBrightness);
uint8_t mBrightness = 0; // Current display brightness
uint8_t mAwakeBrightness = 100; // Current setting for brightness when awake
bool isAsleep = false;
};

View File

@ -0,0 +1,61 @@
#include "keys.hpp"
Keys::Keys() {
static constexpr auto MaxQueueableKeyPresses = 5;
mKeyPressQueueHandle = xQueueCreate(MaxQueueableKeyPresses, sizeof(KeyEvent));
xTaskCreate(KeyGrabberTask, "KeyGrabber", 1024, this, 1, &mKeyGrabbingTask);
xTaskCreate(KeyProccessor, "KeyProccessor", 4096, this, 1, &mKeyHandlingTask);
}
void Keys::KeyGrabberTask(void *aSelf) {
auto self = reinterpret_cast<Keys *>(aSelf);
while (true) {
self->GrabKeys();
vTaskDelay(5 / portTICK_PERIOD_MS); // 5 ms between key grabs
}
}
void Keys::KeyProccessor(void *aSelf) {
auto self = reinterpret_cast<Keys *>(aSelf);
while (true) {
self->HandleKeyPresses();
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
void Keys::HandleKeyPresses() {
KeyPressAbstract::KeyEvent eventToHandle;
while (xQueueReceive(mKeyPressQueueHandle, &eventToHandle, 0) == pdTRUE) {
if (mKeyEventHandler) {
mKeyEventHandler(eventToHandle);
}
}
};
void Keys::QueueKeyEvent(KeyEvent aJustOccuredKeyEvent) {
BaseType_t higherPriorityTaskAwoke;
xQueueSendFromISR(mKeyPressQueueHandle, &aJustOccuredKeyEvent,
&higherPriorityTaskAwoke);
};
void Keys::GrabKeys() {
if (!customKeypad.getKeys()) {
return; // no activity return early.
}
for (int i = 0; i < LIST_MAX; i++) {
if (customKeypad.key[i].kstate == PRESSED ||
customKeypad.key[i].kstate == RELEASED) {
// May need to think about resetting sleep timer in key handler....
// standbyTimer =
// sleepTimeout; // Reset the sleep timer when a button is
// pressed
auto eventType = customKeypad.key[i].kstate == PRESSED
? KeyEvent::Type::Press
: KeyEvent::Type::Release;
const auto keyChar = customKeypad.key[i].kchar;
auto stateChange = customKeypad.key[i].stateChanged;
if (charKeyToKeyIds.count(keyChar) > 0 && stateChange) {
QueueKeyEvent(KeyEvent(charKeyToKeyIds.at(keyChar), eventType));
}
}
}
}

View File

@ -0,0 +1,76 @@
#pragma once
#include "KeyPressAbstract.hpp"
#include "omoteconfig.h"
#include <Keypad.h> // modified for inverted logic
#include <map>
class Keys : public KeyPressAbstract {
public:
Keys();
void HandleKeyPresses() override;
void QueueKeyEvent(KeyEvent aJustOccuredKeyEvent) override;
protected:
void GrabKeys();
private:
static void KeyGrabberTask(void *aSelf);
static void KeyProccessor(void *aSelf);
QueueHandle_t mKeyPressQueueHandle;
TaskHandle_t mKeyGrabbingTask;
TaskHandle_t mKeyHandlingTask;
// Keypad declarations
static const byte ROWS = 5; // four rows
static const byte COLS = 5; // four columns
// define the symbols on the buttons of the keypads
char hexaKeys[ROWS][COLS] = {
{'s', '^', '-', 'm', 'r'}, // source, channel+, Volume-, mute, record
{'i', 'R', '+', 'k', 'd'}, // info, right, Volume+, OK, down
{'4', 'v', '1', '3', '2'}, // blue, channel-, red, yellow, green
{'>', 'o', 'b', 'u', 'L'}, // forward, off, back, up, left
{'?', 'p', 'c', '<', '='} // ?, play, config, rewind, stop
};
// TODO what is '?' lol
// TODO Should be able to optomize this out by reordering Ids at some point
// or even using interrupts to trigger key press queueing
static inline const std::map<char, KeyId> charKeyToKeyIds{
{'o', KeyId::Power},
// Top 4 Buttons left to right
{'=', KeyId::Stop},
{'<', KeyId::Rewind},
{'p', KeyId::Play},
{'>', KeyId::FastForward},
// Buttons around D Pad
{'c', KeyId::Menu},
{'i', KeyId::Info},
{'b', KeyId::Back},
{'s', KeyId::Source},
// D Pad
{'u', KeyId::Up},
{'d', KeyId::Down},
{'L', KeyId::Left},
{'R', KeyId::Right},
{'k', KeyId::Center},
// Volume Channel and 2 between
{'+', KeyId::VolUp},
{'-', KeyId::VolDown},
{'m', KeyId::Mute},
{'r', KeyId::Record},
{'^', KeyId::ChannelUp},
{'v', KeyId::ChannelDown},
// Bottom 4 buttons left to right
{'1', KeyId::Aux1},
{'2', KeyId::Aux2},
{'3', KeyId::Aux3},
{'4', KeyId::Aux4}};
byte rowPins[ROWS] = {SW_A, SW_B, SW_C, SW_D,
SW_E}; // connect to the row pinouts of the keypad
byte colPins[COLS] = {SW_1, SW_2, SW_3, SW_4,
SW_5}; // connect to the column pinouts of the keypad
Keypad customKeypad =
Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
};

View File

@ -45,8 +45,8 @@
#define IR_VCC 25 // IR receiver power
#define IR_LED 33 // IR LED output
#define SCL 22
#define SDA 19
#define TFT_SCL 22
#define TFT_SDA 19
#define ACC_INT 20
#define CRG_STAT 21 // battery charger feedback

View File

@ -1,230 +1,116 @@
#include "wifihandler.hpp"
#include "HardwareAbstract.hpp"
#include <Arduino.h>
#include <Preferences.h>
#include "HardwareAbstract.hpp"
#include "WiFi.h"
#include <WiFi.h>
std::shared_ptr<wifiHandler> wifiHandler::mInstance = nullptr;
// WiFi status event
void wifiHandler::WiFiEvent(WiFiEvent_t event){
int no_networks = 0;
switch (event)
{
case ARDUINO_EVENT_WIFI_SCAN_DONE:
{
Serial.println("WIFI scan done\n");
no_networks = WiFi.scanComplete();
std::vector<WifiInfo> *vec = new std::vector<WifiInfo>();
std::shared_ptr<std::vector<WifiInfo>> info = std::shared_ptr<std::vector<WifiInfo>>(vec);
for (int i = 0; i < no_networks; i++)
{
info->push_back(WifiInfo {
.ssid = std::string(WiFi.SSID(i).c_str()),
.rssi = WiFi.RSSI(i)
});
}
if (no_networks < 0)
{
Serial.println("Scan failed");
}
else
{
// TODO Convert To callbacks
//this->display.clear_wifi_networks();
Serial.print(no_networks);
Serial.print(" found\n");
//this->display.wifi_scan_complete( no_networks);
}
this->scan_notification.notify(info);
if (WiFi.isConnected() == false)
{
WiFi.reconnect();
}
break;
}
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
this->update_credentials();
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
case ARDUINO_EVENT_WIFI_STA_STOP:
this->update_status();
default:
Serial.print("Wifi Status: ");
Serial.println(WiFi.status());
break;
}
if (WiFi.status() == WL_CONNECT_FAILED)
{
Serial.println("connection failed.");
WiFi.disconnect();
}
Serial.println(WiFi.status());
}
bool wifiHandler::isAvailable(){
return true;
}
std::shared_ptr<wifiHandler> wifiHandler::getInstance()
{
if(mInstance)
{
return mInstance;
}
mInstance = std::shared_ptr<wifiHandler>(new wifiHandler());
std::shared_ptr<wifiHandler> wifiHandler::getInstance() {
if (mInstance) {
return mInstance;
}
mInstance = std::shared_ptr<wifiHandler>(new wifiHandler());
return mInstance;
};
wifiHandler::wifiHandler()
{
this->password = "";
this->SSID = "";
}
void wifiHandler::update_status()
{
Serial.println("update_status");
std::shared_ptr<wifiStatus> status = std::make_shared<wifiStatus>(wifiStatus());
//wifiStatus *status = new wifiStatus();
status->isConnected = WiFi.isConnected();
//status->IP = WiFi.localIP();
IPAddress ip = WiFi.localIP();
String ip_str = ip.toString();
status->IP = ip.toString().c_str();
//ip.copy(status->IP, ip.length());
String ssid = WiFi.SSID();
status->ssid = WiFi.SSID().c_str();
//this->wifi_status.isConnected = WiFi.isConnected();
//this->wifi_status.IP = WiFi.localIP();
//this->wifi_status.isConnected = true;
//Serial.println(WiFi.localIP());
this->status_update.notify(status);
}
void wifiHandler::update_credentials()
{
// No connection was attempted so don't try to to save the creds
if(!this->connect_attempt) return;
#if 0
if (strcmp(temporary_password, wifiHandler::password) != 0 || strcmp(temporary_ssid, wifiHandler::SSID) != 0)
{
strcpy(wifiHandler::password, temporary_password);
strcpy(wifiHandler::SSID, temporary_ssid);
Preferences preferences;
preferences.begin("wifiSettings", false);
String tempString = wifiHandler::password;
preferences.putString("password", tempString);
tempString = wifiHandler::SSID;
preferences.putString("SSID", tempString);
preferences.end();
void wifiHandler::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t aEventInfo) {
int no_networks = 0;
switch (event) {
case ARDUINO_EVENT_WIFI_SCAN_DONE: {
no_networks = WiFi.scanComplete();
auto info = std::vector<WifiInfo>(no_networks);
for (int i = 0; i < no_networks; i++) {
auto ssid =
WiFi.SSID(i).c_str() ? std::string(WiFi.SSID(i).c_str()) : "No SSID";
info[i] = WifiInfo(ssid, WiFi.RSSI(i));
}
#else
if (this->temporary_password->compare(this->password) != 0 || this->temporary_ssid->compare(this->SSID))
{
this->password = *(this->temporary_password);
this->SSID = *(this->temporary_ssid);
Preferences preferences;
preferences.begin("wifiSettings", false);
String tempString = this->temporary_password->c_str();
preferences.putString("password", tempString);
tempString = this->temporary_ssid->c_str();
preferences.putString("SSID", tempString);
preferences.end();
mScanNotification->notify(info);
if (WiFi.isConnected() == false) {
WiFi.reconnect();
}
break;
}
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
StoreCredentials();
WiFi.setAutoConnect(true);
UpdateStatus();
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
case ARDUINO_EVENT_WIFI_STA_STOP:
UpdateStatus();
break;
default:
break;
}
#endif
this->connect_attempt = false;
}
void wifiHandler::scan()
{
void wifiHandler::UpdateStatus() {
Serial.println("UpdateStatus");
mCurrentStatus.isConnected = WiFi.isConnected();
mCurrentStatus.IP = WiFi.localIP().toString().c_str();
mCurrentStatus.ssid =
mCurrentStatus.isConnected ? WiFi.SSID().c_str() : mConnectionAttemptSSID;
mStatusUpdate->notify(mCurrentStatus);
}
void wifiHandler::StoreCredentials() {
// No connection was attempted so don't try to to save the creds
if (!mIsConnectionAttempt) {
return;
}
mPassword = mConnectionAttemptPassword;
mSSID = mConnectionAttemptSSID;
Preferences preferences;
preferences.begin("wifiSettings", false);
preferences.putString("password", mPassword.c_str());
preferences.putString("SSID", mSSID.c_str());
preferences.end();
mConnectionAttemptPassword.clear();
mConnectionAttemptSSID.clear();
mIsConnectionAttempt = false;
}
void wifiHandler::scan() {
Serial.println("scan called");
/* If the */
WiFi.status();
if (WiFi.isConnected() != true)
{
WiFi.setAutoReconnect(false);
WiFi.scanNetworks(true);
}
void wifiHandler::begin() {
WiFi.setHostname("OMOTE");
WiFi.mode(WIFI_STA);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t aEventInfo) {
mInstance->WiFiEvent(event, aEventInfo);
});
Preferences preferences;
preferences.begin("wifiSettings", false);
String ssid = preferences.getString("SSID");
String password = preferences.getString("password");
preferences.end();
// Attempt Connection with stored Credentials
if (!ssid.isEmpty()) {
connect(ssid.c_str(), password.c_str());
} else {
Serial.println("no SSID or password stored");
WiFi.disconnect();
}
WiFi.scanNetworks(true);
WiFi.setSleep(true);
}
void wifiHandler::begin()
{
//this->display = display;
WiFi.setHostname("OMOTE");
WiFi.mode(WIFI_STA);
//WiFi.onEvent([this] (WiFiEvent_t event) {mInstance->WiFiEvent(event);});
WiFi.onEvent([] (WiFiEvent_t event) {mInstance->WiFiEvent(event);});
Preferences preferences;
preferences.begin("wifiSettings",false);
String ssid = preferences.getString("SSID");
String password = preferences.getString("password");
preferences.end();
/* If the SSID is not empty, there was a value stored in the preferences and we try to use it.*/
if (!ssid.isEmpty())
{
Serial.print("Connecting to wifi ");
Serial.println(ssid);
//strcpy(this->SSID, ssid.c_str());
//strcpy(this->password, password.c_str());
this->SSID = ssid.c_str();
this->password = password.c_str();
this->connect(std::make_shared<std::string>(std::string(this->SSID)), std::make_shared<std::string>(std::string(this->password)));
}
else
{
Serial.println("no SSID or password stored");
/*Set first character to \0 indicates an empty string*/
this->SSID[0] = '\0';
this->password[0] = '\0';
WiFi.disconnect();
}
WiFi.setSleep(true);
void wifiHandler::connect(std::string ssid, std::string password) {
Serial.printf("Attempting Wifi Connection To %s \n", ssid.c_str());
mIsConnectionAttempt = true;
mConnectionAttemptPassword = password;
mConnectionAttemptSSID = ssid;
auto status = WiFi.begin(mConnectionAttemptSSID.c_str(),
mConnectionAttemptPassword.c_str());
}
void wifiHandler::onScanDone(std::function<void (std::shared_ptr<std::vector<WifiInfo>>)> function){
this->scan_notification.onNotify(std::move(function));
}
void wifiHandler::onStatusUpdate(std::function<void (std::shared_ptr<wifiStatus>)> function){
this->status_update.onNotify(std::move(function));
}
void wifiHandler::connect(std::shared_ptr<std::string> ssid, std::shared_ptr<std::string> password)
{
this->connect_attempt = true;
this->temporary_password = password;
this->temporary_ssid = ssid;
WiFi.begin(ssid->c_str(), password->c_str());
}
void wifiHandler::turnOff()
{
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
}
void wifiHandler::disconnect(){
WiFi.disconnect();
}
bool wifiHandler::isConnected()
{
return WiFi.isConnected();
}
std::string wifiHandler::getIP()
{
return std::string(WiFi.localIP().toString().c_str());
}

View File

@ -1,99 +1,53 @@
#pragma once
#include "wifiHandlerInterface.h"
#include "Notification.hpp"
#include "memory.h"
#include "wifiHandlerInterface.h"
#include <WiFi.h>
#define STRING_SIZE 50
class wifiHandler : public wifiHandlerInterface {
public:
static std::shared_ptr<wifiHandler> getInstance();
class wifiHandler: public wifiHandlerInterface {
public:
wifiHandler();
static std::shared_ptr<wifiHandler> getInstance();
/**
* @brief Function to initialize the wifi handler
*
*/
void begin();
// wifiHandlerInterface Implementation
void begin() override;
void scan() override;
void connect(std::string ssid, std::string password) override;
wifiStatus GetStatus() override { return mCurrentStatus; };
//
/**
* @brief Connect to the wifi using the provided credetials
*
* @param SSID
* @param password
*/
void connect(std::shared_ptr<std::string> ssid, std::shared_ptr<std::string> password);
//void connect(const char* SSID, const char* password);
protected:
wifiHandler() = default;
static std::shared_ptr<wifiHandler> mInstance;
/**
* @brief Function to store the credentials when we have had a
* successful connection
*/
void StoreCredentials();
private:
/**
* @brief Handler for incoming arduino wifi events
* @param event - a Wifi event
*/
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t aEventInfo);
/**
* @brief function to trigger asynchronous scan for wifi networks
*
*/
void scan();
bool isAvailable();
void onScanDone(std::function<void (std::shared_ptr<std::vector<WifiInfo>>)> function);
void onStatusUpdate(std::function<void (std::shared_ptr<wifiStatus>)> function);
/**
* @brief Update Internal status and send out a notification
*/
void UpdateStatus();
wifiStatus mCurrentStatus;
/**
* @brief Variables used to track wifi connection attempts
*/
bool mIsConnectionAttempt = false;
std::string mConnectionAttemptPassword;
std::string mConnectionAttemptSSID;
private:
Notification<std::shared_ptr<std::vector<WifiInfo>>> scan_notification;
Notification<std::shared_ptr<wifiStatus>> status_update;
/**
* @brief Function to update the wifi credentials. This function is called in the wifi event callback function
* after a connection is established. Only then is the new credentials stored and the old stored credentials
* overwritten.
*
* @param temporary_ssid
* @param temporary_password
*/
void update_credentials();
void WiFiEvent(WiFiEvent_t event);
/**
* @brief Function to turn off wifi
*
*/
void turnOff();
/**
* @brief Function to get the IP address of this device
*
* @return String IP Address of the device
*/
std::string getIP();
wifiStatus wifi_status;
static std::shared_ptr<wifiHandler> mInstance;
bool connect_attempt = false;
std::shared_ptr<std::string> temporary_password;
std::shared_ptr<std::string> temporary_ssid;
void update_status();
/**
* @brief Internal variable to store the wifi password
*
*/
std::string password;
/**
* @brief Function to disconnect from the network
*
*/
void disconnect();
/**
* @brief Function to determine wether or not we are connected to a network
*
* @return true Device is connected to wifi network
* @return false Device is not connected to wifi network
*/
bool isConnected();
/**
* @brief Internal variable to store the wifi SSID
*
*/
std::string SSID;
/**
* @brief Verified Working User and Pass to Wifi network
*/
std::string mPassword;
std::string mSSID;
};

View File

@ -3,70 +3,60 @@
#include "SDLDisplay.hpp"
#include <sstream>
HardwareSimulator::HardwareSimulator(): HardwareAbstract(),
mTickThread([](){
while(true){
std::this_thread::sleep_for(std::chrono::milliseconds(2));
lv_tick_inc(2); /*Tell lvgl that 2 milliseconds were elapsed*/
}}),
mBattery(std::make_shared<BatterySimulator>()),
mDisplay(SDLDisplay::getInstance()),
mWifiHandler(std::make_shared<wifiHandlerSim>())
{
mHardwareStatusTitleUpdate = std::thread([this] {
int dataToShow = 0;
while (true)
{
std::stringstream title;
switch (dataToShow){
case 0:
title << "Batt:" << mBattery->getPercentage() << "%" << std::endl;
break;
case 1:
title << "BKLght: " << static_cast<int>(mDisplay->getBrightness()) << std::endl;
dataToShow = -1;
break;
default:
dataToShow = -1;
}
dataToShow++;
mDisplay->setTitle(title.str());
std::this_thread::sleep_for(std::chrono::seconds(2));
HardwareSimulator::HardwareSimulator()
: HardwareAbstract(), mTickThread([]() {
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
lv_tick_inc(2); /*Tell lvgl that 2 milliseconds were elapsed*/
}
});
}),
mBattery(std::make_shared<BatterySimulator>()),
mDisplay(SDLDisplay::getInstance()),
mWifiHandler(std::make_shared<wifiHandlerSim>()),
mKeys(std::make_shared<KeyPressSim>()) {
mHardwareStatusTitleUpdate = std::thread([this] {
int dataToShow = 0;
while (true) {
std::stringstream title;
switch (dataToShow) {
case 0:
title << "Batt:" << mBattery->getPercentage() << "%" << std::endl;
break;
case 1:
title << "BKLght: " << static_cast<int>(mDisplay->getBrightness())
<< std::endl;
dataToShow = -1;
break;
default:
dataToShow = -1;
}
dataToShow++;
mDisplay->setTitle(title.str());
std::this_thread::sleep_for(std::chrono::seconds(2));
}
});
}
std::shared_ptr<BatteryInterface> HardwareSimulator::battery(){
return mBattery;
std::shared_ptr<BatteryInterface> HardwareSimulator::battery() {
return mBattery;
}
std::shared_ptr<DisplayAbstract> HardwareSimulator::display(){
return mDisplay;
std::shared_ptr<DisplayAbstract> HardwareSimulator::display() {
return mDisplay;
}
std::shared_ptr<wifiHandlerInterface> HardwareSimulator::wifi(){
return mWifiHandler;
std::shared_ptr<wifiHandlerInterface> HardwareSimulator::wifi() {
return mWifiHandler;
}
std::shared_ptr<KeyPressAbstract> HardwareSimulator::keys() { return mKeys; }
char HardwareSimulator::getCurrentDevice(){
return 0;
}
char HardwareSimulator::getCurrentDevice() { return 0; }
void HardwareSimulator::setCurrentDevice(char currentDevice){
void HardwareSimulator::setCurrentDevice(char currentDevice) {}
}
bool HardwareSimulator::getWakeupByIMUEnabled() { return true; }
bool HardwareSimulator::getWakeupByIMUEnabled(){
return true;
}
void HardwareSimulator::setWakeupByIMUEnabled(bool wakeupByIMUEnabled) {}
void HardwareSimulator::setWakeupByIMUEnabled(bool wakeupByIMUEnabled){
uint16_t HardwareSimulator::getSleepTimeout() { return 20000; }
}
uint16_t HardwareSimulator::getSleepTimeout(){
return 20000;
}
void HardwareSimulator::setSleepTimeout(uint16_t sleepTimeout){
}
void HardwareSimulator::setSleepTimeout(uint16_t sleepTimeout) {}

View File

@ -1,8 +1,9 @@
#pragma once
#include "HardwareAbstract.hpp"
#include "batterySimulator.hpp"
#include "KeyPressSim.hpp"
#include "SDLDisplay.hpp"
#include "batterySimulator.hpp"
#include "wifiHandlerSim.hpp"
#include <thread>
@ -10,28 +11,30 @@
class HardwareSimulator : public HardwareAbstract {
public:
HardwareSimulator();
virtual void init() override {};
virtual void debugPrint(const char* fmt, ...) override {
void init() override{};
void loopHandler() override{};
void debugPrint(const char *fmt, ...) override {
va_list arguments;
va_start(arguments, fmt);
vprintf(fmt, arguments);
va_end(arguments);
}
virtual std::shared_ptr<BatteryInterface> battery() override;
virtual std::shared_ptr<DisplayAbstract> display() override;
virtual std::shared_ptr<wifiHandlerInterface> wifi() override;
std::shared_ptr<BatteryInterface> battery() override;
std::shared_ptr<DisplayAbstract> display() override;
std::shared_ptr<wifiHandlerInterface> wifi() override;
std::shared_ptr<KeyPressAbstract> keys() override;
virtual char getCurrentDevice() override;
virtual void setCurrentDevice(char currentDevice) override;
char getCurrentDevice() override;
void setCurrentDevice(char currentDevice) override;
virtual bool getWakeupByIMUEnabled() override;
virtual void setWakeupByIMUEnabled(bool wakeupByIMUEnabled) override;
bool getWakeupByIMUEnabled() override;
void setWakeupByIMUEnabled(bool wakeupByIMUEnabled) override;
virtual uint16_t getSleepTimeout() override;
virtual void setSleepTimeout(uint16_t sleepTimeout) override;
uint16_t getSleepTimeout() override;
void setSleepTimeout(uint16_t sleepTimeout) override;
private:
std::thread mTickThread;
@ -40,4 +43,5 @@ private:
std::shared_ptr<BatterySimulator> mBattery;
std::shared_ptr<SDLDisplay> mDisplay;
std::shared_ptr<wifiHandlerSim> mWifiHandler;
std::shared_ptr<KeyPressSim> mKeys;
};

View File

@ -0,0 +1,44 @@
#include "KeyPressSim.hpp"
#include <memory>
KeyPressSim::KeyPressSim()
: mKeyHandlerThread([this] {
while (true) {
HandleKeyPresses();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}) {
SDL_AddEventWatch(KeyPressSim::GrabKeyImpl, this);
};
int KeyPressSim::GrabKeyImpl(void *aSelf, SDL_Event *aEvent) {
reinterpret_cast<KeyPressSim *>(aSelf)->GrabKeys(aEvent);
return 0;
}
void KeyPressSim::GrabKeys(SDL_Event *aEvent) {
if (aEvent->type == SDL_KEYDOWN || aEvent->type == SDL_KEYUP) {
auto keyEventType = aEvent->type == SDL_KEYDOWN ? KeyEvent::Type::Press
: KeyEvent::Type::Release;
const auto SDLK_key = aEvent->key.keysym.sym;
if (KeyMap.count(SDLK_key) > 0) {
QueueKeyEvent(KeyEvent(KeyMap.at(SDLK_key), keyEventType));
}
}
};
void KeyPressSim::HandleKeyPresses() {
std::lock_guard lock(mQueueGaurd);
while (!mKeyEventQueue.empty()) {
if (mKeyEventHandler) {
mKeyEventHandler(mKeyEventQueue.front());
}
mKeyEventQueue.pop();
}
};
void KeyPressSim::QueueKeyEvent(KeyEvent aJustOccuredKeyEvent) {
std::lock_guard lock(mQueueGaurd);
mKeyEventQueue.push(aJustOccuredKeyEvent);
};

View File

@ -0,0 +1,59 @@
#include "KeyPressAbstract.hpp"
#include "SDL2/SDL.h"
#include <map>
#include <mutex>
#include <queue>
#include <thread>
class KeyPressSim : public KeyPressAbstract {
public:
static constexpr auto MaxQueueableKeyEvents = 3;
KeyPressSim();
static int GrabKeyImpl(void *aSelf, SDL_Event *aEvent);
void GrabKeys(SDL_Event *aEvent);
void HandleKeyPresses() override;
void QueueKeyEvent(KeyEvent aJustOccuredKeyEvent) override;
private:
std::thread mKeyGrabberThread;
std::thread mKeyHandlerThread;
std::queue<KeyEvent> mKeyEventQueue;
std::mutex mQueueGaurd;
using Key = KeyPressAbstract::KeyId;
static inline const std::map<SDL_Keycode, KeyPressAbstract::KeyId> KeyMap{
{SDLK_p, Key::Power},
// Video Control row
{SDLK_5, Key::Stop},
{SDLK_6, Key::Rewind},
{SDLK_7, Key::Play},
{SDLK_8, Key::FastForward},
// D-Pad
{SDLK_UP, Key::Up},
{SDLK_DOWN, Key::Down},
{SDLK_LEFT, Key::Left},
{SDLK_RIGHT, Key::Right},
{SDLK_SPACE, Key::Center},
// General Keys
{SDLK_h, Key::Menu},
{SDLK_j, Key::Info},
{SDLK_l, Key::Back},
{SDLK_k, Key::Source},
// Volume/ Channel UP and down
{SDLK_w, Key::VolUp},
{SDLK_s, Key::VolDown},
{SDLK_e, Key::ChannelUp},
{SDLK_d, Key::ChannelDown},
// Mute & record
{SDLK_x, Key::Mute},
{SDLK_c, Key::Record},
// Aux Keys
{SDLK_1, Key::Aux1},
{SDLK_2, Key::Aux2},
{SDLK_3, Key::Aux3},
{SDLK_4, Key::Aux4}};
};

View File

@ -1,39 +1,39 @@
#include "SDLDisplay.hpp"
#include "sdl/sdl.h"
#include <string>
#include <string>
std::shared_ptr<SDLDisplay> SDLDisplay::getInstance(){
if (!DisplayAbstract::mInstance){
DisplayAbstract::mInstance = std::shared_ptr<SDLDisplay>(new SDLDisplay());
}
return std::static_pointer_cast<SDLDisplay>(mInstance);
std::shared_ptr<SDLDisplay> SDLDisplay::getInstance() {
if (!DisplayAbstract::mInstance) {
DisplayAbstract::mInstance = std::shared_ptr<SDLDisplay>(new SDLDisplay());
}
return std::static_pointer_cast<SDLDisplay>(mInstance);
}
void SDLDisplay::setBrightness(uint8_t brightness){
mBrightness = brightness;
void SDLDisplay::setBrightness(uint8_t brightness) { mBrightness = brightness; }
uint8_t SDLDisplay::getBrightness() { return mBrightness; }
void SDLDisplay::turnOff() {}
void SDLDisplay::flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p) {
sdl_display_flush(disp, area, color_p);
}
uint8_t SDLDisplay::getBrightness(){
return mBrightness;
void SDLDisplay::screenInput(lv_indev_drv_t *indev_driver,
lv_indev_data_t *data) {
sdl_mouse_read(indev_driver, data);
}
void SDLDisplay::turnOff(){
void SDLDisplay::setTitle(std::string aNewTitle) {
SDL_SetWindowTitle(mSimWindow, aNewTitle.c_str());
}
void SDLDisplay::flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p){
sdl_display_flush(disp,area,color_p);
}
SDLDisplay::SDLDisplay() : DisplayAbstract() {
sdl_init();
void SDLDisplay::screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data){
sdl_mouse_read(indev_driver,data);
}
void SDLDisplay::setTitle(std::string aNewTitle){
SDL_SetWindowTitle(mSimWindow,aNewTitle.c_str());
}
SDLDisplay::SDLDisplay(): DisplayAbstract() {
sdl_init();
mSimWindow = SDL_GetWindowFromID(1); // Get the SDL window via ID hopefully it is always 1...
// Get the SDL window via an event
SDL_Event aWindowIdFinder;
SDL_PollEvent(&aWindowIdFinder);
mSimWindow = SDL_GetWindowFromID(aWindowIdFinder.window.windowID);
}

View File

@ -1,25 +1,27 @@
#pragma once
#include <stdint.h>
#include "SDL2/SDL.h"
#include "DisplayAbstract.h"
#include "SDL2/SDL.h"
#include <stdint.h>
class SDLDisplay : public DisplayAbstract{
class SDLDisplay : public DisplayAbstract {
public:
static std::shared_ptr<SDLDisplay> getInstance();
static std::shared_ptr<SDLDisplay> getInstance();
virtual void setBrightness(uint8_t brightness) override;
virtual uint8_t getBrightness() override;
virtual void turnOff() override;
virtual void setBrightness(uint8_t brightness) override;
virtual uint8_t getBrightness() override;
virtual void turnOff() override;
void setTitle(std::string aNewTitle);
void setTitle(std::string aNewTitle);
protected:
virtual void flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) override;
virtual void screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) override;
virtual void flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p) override;
virtual void screenInput(lv_indev_drv_t *indev_driver,
lv_indev_data_t *data) override;
private:
SDLDisplay();
uint8_t mBrightness;
SDL_Window* mSimWindow;
SDLDisplay();
uint8_t mBrightness;
SDL_Window *mSimWindow;
};

View File

@ -4,7 +4,7 @@ template <typename T>
SimulatorSPSCQueue<T>::SimulatorSPSCQueue(uint32_t size)
{
this->size = size;
this->data = new T[](this->size + 1);
this->data = new T[this->size + 1];
this->rd_index = 0;
this->wr_index = 0;
}

View File

@ -1,68 +1,35 @@
#include "wifiHandlerSim.hpp"
std::shared_ptr<wifiHandlerSim> mInstance;
using WifiInfo = wifiHandlerInterface::WifiInfo;
std::shared_ptr<wifiHandlerSim> wifiHandlerSim::getInstance()
{
if(mInstance)
{
return mInstance;
}
mInstance = std::make_shared<wifiHandlerSim>(wifiHandlerSim());
return mInstance;
};
wifiHandlerSim::wifiHandlerSim() {}
wifiHandlerSim::wifiHandlerSim(){
void wifiHandlerSim::begin() {}
}
void wifiHandlerSim::begin(){
}
static wifiStatus status = {
.isConnected = true
, .IP = "172.0.0.1"
};
void wifiHandlerSim::connect(std::shared_ptr<std::string> ssid, std::shared_ptr<std::string> password){
status.ssid = *ssid;
std::shared_ptr<wifiStatus> new_status = std::make_shared<wifiStatus> (wifiStatus(std::move(status)));
this->status_update.notify(new_status);
void wifiHandlerSim::connect(std::string ssid, std::string password) {
if (mFakeStatusThread.joinable()) {
mFakeStatusThread.join();
mCurrentStatus.ssid = ssid;
mCurrentStatus.isConnected = true;
mFakeStatusThread = std::thread([this] {
std::this_thread::sleep_for(std::chrono::seconds(1));
mStatusUpdate->notify(mCurrentStatus);
});
}
}
static const WifiInfo wifis[] = {
{
.ssid = "High Signal Wifi"
, .rssi = -49
}
, {
.ssid = "Mid Signal Wifi"
, .rssi = -55
}
, {
.ssid = "Low Signal Wifi"
, .rssi = -65
}
, {
.ssid = "No Signal Wifi"
, .rssi = -90
}
};
WifiInfo("High Signal Wifi", -49), WifiInfo("Mid Signal Wifi", -55),
WifiInfo("Low Signal Wifi", -65), WifiInfo("No Signal Wifi", -90)};
void wifiHandlerSim::scan(){
std::shared_ptr<std::vector<WifiInfo>> info = std::make_shared<std::vector<WifiInfo>>(std::vector(std::begin(wifis), std::end(wifis)));
this->scan_notification.notify(info);
void wifiHandlerSim::scan() {
if (mFakeScanThread.joinable()) {
mFakeScanThread.join();
mFakeScanThread = std::thread([this] {
std::vector<WifiInfo> info =
std::vector(std::begin(wifis), std::end(wifis));
std::this_thread::sleep_for(std::chrono::seconds(2));
mScanNotification->notify(info);
});
}
}
bool wifiHandlerSim::isAvailable(){
return false;
}
void wifiHandlerSim::onScanDone(std::function<void (std::shared_ptr<std::vector<WifiInfo>>)> function){
this->scan_notification.onNotify(std::move(function));
}
void wifiHandlerSim::onStatusUpdate(std::function<void (std::shared_ptr<wifiStatus>)> function){
this->status_update.onNotify(std::move(function));
}

View File

@ -1,35 +1,21 @@
#pragma once
#include "wifiHandlerInterface.h"
#include "Notification.hpp"
#include "wifiHandlerInterface.h"
#include <atomic>
#include <memory>
#include <thread>
class wifiHandlerSim: public wifiHandlerInterface {
public:
wifiHandlerSim();
static std::shared_ptr<wifiHandlerSim> getInstance();
class wifiHandlerSim : public wifiHandlerInterface {
public:
wifiHandlerSim();
/**
* @brief Connect to the wifi using the provided credetials
*
* @param SSID
* @param password
*/
void connect(std::shared_ptr<std::string> ssid, std::shared_ptr<std::string> password);
//void connect(const char* SSID, const char* password);
void begin() override;
void scan() override;
void connect(std::string ssid, std::string password) override;
wifiStatus GetStatus() override { return mCurrentStatus; };
/**
* @brief function to trigger asynchronous scan for wifi networks
*
*/
void scan();
bool isAvailable();
void begin();
void onScanDone(std::function<void (std::shared_ptr<std::vector<WifiInfo>>)> function);
void onStatusUpdate(std::function<void (std::shared_ptr<wifiStatus>)> function);
private:
Notification<std::shared_ptr<std::vector<WifiInfo>>> scan_notification;
Notification<std::shared_ptr<wifiStatus>> status_update;
private:
std::thread mFakeScanThread = std::thread([] {});
std::thread mFakeStatusThread = std::thread([] {});
wifiStatus mCurrentStatus = wifiStatus(true, "172.0.0.1", "FakeNet");
};

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +0,0 @@
// OMOTE UI Images
// 2023 Matthew Colvin
#pragma once
#include "lvgl.h"
class Images{
public:
Images();
lv_obj_t* addAppleTVIcon(lv_obj_t* parent);
lv_obj_t* addAppleDisplayImage(lv_obj_t* parent);
lv_obj_t* addAppleBackIcon(lv_obj_t* parent);
lv_obj_t* addLowBrightnessIcon(lv_obj_t* parent);
lv_obj_t* addHighBrightnessIcon(lv_obj_t* parent);
lv_obj_t* addLightBulbIcon(lv_obj_t* parent);
lv_obj_t* addLeftGradiant(lv_obj_t* parent);
lv_obj_t* addRightGradiant(lv_obj_t* parent);
lv_obj_t* addWifiNoSignal(lv_obj_t* parent);
lv_obj_t* addWifiLowSignal(lv_obj_t* parent);
lv_obj_t* addWifiMidSignal(lv_obj_t* parent);
lv_obj_t* addWifiHighSignal(lv_obj_t* parent);
private:
// Make Image based on anImageDesc then
// add that image to parent.
lv_obj_t* addImg(lv_obj_t* parent, lv_img_dsc_t* anImgDesc);
void setupImageDescriptions();
lv_img_dsc_t appleTvIcon;
lv_img_dsc_t appleDisplayIcon;
lv_img_dsc_t appleBackIcon;
lv_img_dsc_t low_brightness;
lv_img_dsc_t high_brightness;
lv_img_dsc_t lightbulb_icon;
lv_img_dsc_t gradientLeft;
lv_img_dsc_t gradientRight;
lv_img_dsc_t wifiNoSignal;
lv_img_dsc_t wifiLowSignal;
lv_img_dsc_t wifiMidSignal;
lv_img_dsc_t wifiHighSignal;
};

View File

@ -3,13 +3,16 @@
#include "omoteconfig.h"
#include <functional>
using namespace UI::Basic;
std::shared_ptr<OmoteUI> OmoteUI::mInstance = nullptr;
// This can be used to flag out specific code for SIM only
// #if defined(IS_SIMULATOR) && (IS_SIMULATOR == true)
// #endif
OmoteUI::OmoteUI(std::shared_ptr<HardwareAbstract> aHardware) : mHardware(aHardware){}
OmoteUI::OmoteUI(std::shared_ptr<HardwareAbstract> aHardware)
: UIBase(aHardware) {}
// Set the page indicator scroll position relative to the tabview scroll
// position
@ -37,17 +40,18 @@ void OmoteUI::bl_slider_event_cb(lv_event_t *e) {
// Apple Key Event handler
void OmoteUI::appleKey_event_cb(lv_event_t *e) {
// Send IR command based on the event user data
//mHardware->debugPrint(std::to_string(50 + (int)e->user_data));
// mHardware->debugPrint(std::to_string(50 + (int)e->user_data));
}
// Wakeup by IMU Switch Event handler
void OmoteUI::WakeEnableSetting_event_cb(lv_event_t *e) {
this->mHardware->setWakeupByIMUEnabled(lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED));
this->mHardware->setWakeupByIMUEnabled(
lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED));
}
// Wakeup timeout dropdown Event handler
void OmoteUI::wakeTimeoutSetting_event_cb(lv_event_t *e){
lv_obj_t * drop = lv_event_get_target(e);
void OmoteUI::wakeTimeoutSetting_event_cb(lv_event_t *e) {
lv_obj_t *drop = lv_event_get_target(e);
int sleepTimeout = sleepTimeoutMap[lv_dropdown_get_selected(drop)];
mHardware->setSleepTimeout(sleepTimeout);
@ -97,13 +101,14 @@ void OmoteUI::virtualKeypad_event_cb(lv_event_t *e) {
// mHardware->debugPrint(buffer);
}
void OmoteUI::loopHandler(){
void OmoteUI::loopHandler() {
lv_timer_handler();
lv_task_handler();
}
void OmoteUI::create_status_bar(){
void OmoteUI::create_status_bar() {
// Create a status bar
lv_obj_t* statusbar = lv_btn_create(lv_scr_act());
lv_obj_t *statusbar = lv_btn_create(lv_scr_act());
lv_obj_set_size(statusbar, 240, 20);
lv_obj_set_style_shadow_width(statusbar, 0, LV_PART_MAIN);
lv_obj_set_style_bg_color(statusbar, lv_color_black(), LV_PART_MAIN);
@ -113,32 +118,41 @@ void OmoteUI::create_status_bar(){
this->WifiLabel = lv_label_create(statusbar);
lv_label_set_text(this->WifiLabel, "");
lv_obj_align(this->WifiLabel, LV_ALIGN_LEFT_MID, -8, 0);
lv_obj_set_style_text_font(this->WifiLabel, &lv_font_montserrat_12, LV_PART_MAIN);
lv_obj_set_style_text_font(this->WifiLabel, &lv_font_montserrat_12,
LV_PART_MAIN);
this->objBattPercentage = lv_label_create(statusbar);
lv_label_set_text(this->objBattPercentage, "");
lv_obj_align(this->objBattPercentage, LV_ALIGN_RIGHT_MID, -16, 0);
lv_obj_set_style_text_font(this->objBattPercentage, &lv_font_montserrat_12, LV_PART_MAIN);
lv_obj_set_style_text_font(this->objBattPercentage, &lv_font_montserrat_12,
LV_PART_MAIN);
this->objBattIcon = lv_label_create(statusbar);
lv_label_set_text(this->objBattIcon, LV_SYMBOL_BATTERY_EMPTY);
lv_obj_align(this->objBattIcon, LV_ALIGN_RIGHT_MID, 8, 0);
lv_obj_set_style_text_font(this->objBattIcon, &lv_font_montserrat_16, LV_PART_MAIN);
lv_obj_set_style_text_font(this->objBattIcon, &lv_font_montserrat_16,
LV_PART_MAIN);
batteryPoller = std::make_unique<poller>([&batteryIcon = objBattIcon, battery = mHardware->battery()](){
auto percent = battery->getPercentage();
if(percent > 95) lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_FULL);
else if(percent > 75) lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_3);
else if(percent > 50) lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_2);
else if(percent > 25) lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_1);
else lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_EMPTY);
});
batteryPoller = std::make_unique<poller>(
[&batteryIcon = objBattIcon, battery = mHardware->battery()]() {
auto percent = battery->getPercentage();
if (percent > 95)
lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_FULL);
else if (percent > 75)
lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_3);
else if (percent > 50)
lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_2);
else if (percent > 25)
lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_1);
else
lv_label_set_text(batteryIcon, LV_SYMBOL_BATTERY_EMPTY);
});
}
void OmoteUI::setup_settings(lv_obj_t* parent)
{
void OmoteUI::setup_settings(lv_obj_t *parent) {
// Add content to the settings tab
// With a flex layout, setting groups/boxes will position themselves automatically
// With a flex layout, setting groups/boxes will position themselves
// automatically
lv_obj_set_layout(parent, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_scrollbar_mode(parent, LV_SCROLLBAR_MODE_ACTIVE);
@ -148,19 +162,19 @@ void OmoteUI::setup_settings(lv_obj_t* parent)
/* Create main page for settings this->settingsMenu*/
this->settingsMainPage = lv_menu_page_create(this->settingsMenu, NULL);
lv_obj_t* cont = lv_menu_cont_create(this->settingsMainPage);
lv_obj_t *cont = lv_menu_cont_create(this->settingsMainPage);
lv_obj_set_layout(cont, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_ACTIVE);
//lv_obj_set_width(cont, lv_obj_get_width(parent));
// lv_obj_set_width(cont, lv_obj_get_width(parent));
this->display_settings(cont);
this->create_wifi_settings(this->settingsMenu, cont);
// Another setting for the battery
lv_obj_t* menuLabel = lv_label_create(cont);
lv_obj_t *menuLabel = lv_label_create(cont);
lv_label_set_text(menuLabel, "Battery");
lv_obj_t* menuBox = lv_obj_create(cont);
lv_obj_t *menuBox = lv_obj_create(cont);
lv_obj_set_size(menuBox, lv_pct(100), 125);
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(menuBox, 0, LV_PART_MAIN);
@ -168,79 +182,81 @@ void OmoteUI::setup_settings(lv_obj_t* parent)
lv_menu_set_page(this->settingsMenu, this->settingsMainPage);
}
void OmoteUI::ta_kb_event_cb(lv_event_t* e)
{
void OmoteUI::ta_kb_event_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * ta = lv_event_get_target(e);
lv_obj_t * kb = (lv_obj_t*) lv_event_get_user_data(e);
switch(code){
case LV_EVENT_FOCUSED:
lv_keyboard_set_textarea(kb, ta);
lv_obj_clear_flag(kb, LV_OBJ_FLAG_HIDDEN);
lv_obj_move_foreground(kb);
break;
case LV_EVENT_DEFOCUSED:
lv_keyboard_set_textarea(kb, NULL);
lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
break;
default:
break;
lv_obj_t *ta = lv_event_get_target(e);
lv_obj_t *kb = (lv_obj_t *)lv_event_get_user_data(e);
switch (code) {
case LV_EVENT_FOCUSED:
lv_keyboard_set_textarea(kb, ta);
lv_obj_clear_flag(kb, LV_OBJ_FLAG_HIDDEN);
lv_obj_move_foreground(kb);
break;
case LV_EVENT_DEFOCUSED:
lv_keyboard_set_textarea(kb, NULL);
lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
break;
default:
break;
}
}
void OmoteUI::create_keyboard()
{
void OmoteUI::create_keyboard() {
this->kb = lv_keyboard_create(lv_scr_act());
lv_obj_add_flag(this->kb, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_y(this->kb, 0);
}
void OmoteUI::hide_keyboard()
{
lv_obj_add_flag(this->kb, LV_OBJ_FLAG_HIDDEN);
}
void OmoteUI::hide_keyboard() { lv_obj_add_flag(this->kb, LV_OBJ_FLAG_HIDDEN); }
void OmoteUI::reset_settings_menu()
{
void OmoteUI::reset_settings_menu() {
lv_menu_set_page(this->settingsMenu, this->settingsMainPage);
}
void OmoteUI::attach_keyboard(lv_obj_t* textarea)
{
if (this->kb == NULL)
{
void OmoteUI::attach_keyboard(lv_obj_t *textarea) {
if (this->kb == NULL) {
this->create_keyboard();
}
lv_keyboard_set_textarea(this->kb, textarea);
lv_obj_add_event_cb(textarea, [] (lv_event_t* e) {mInstance->ta_kb_event_cb(e);}, LV_EVENT_FOCUSED, this->kb);
lv_obj_add_event_cb(textarea, [] (lv_event_t* e) {mInstance->ta_kb_event_cb(e);}, LV_EVENT_DEFOCUSED, this->kb);
lv_obj_add_event_cb(
textarea, [](lv_event_t *e) { mInstance->ta_kb_event_cb(e); },
LV_EVENT_FOCUSED, this->kb);
lv_obj_add_event_cb(
textarea, [](lv_event_t *e) { mInstance->ta_kb_event_cb(e); },
LV_EVENT_DEFOCUSED, this->kb);
}
void OmoteUI::layout_UI() {
// Set the background color
// Set the background color
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
this->create_keyboard();
// Setup a scrollable tabview for devices and settings
lv_obj_t* tabview;
tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 0); // Hide tab labels by setting their height to 0
lv_obj_t *tabview;
tabview =
lv_tabview_create(lv_scr_act(), LV_DIR_TOP,
0); // Hide tab labels by setting their height to 0
lv_obj_set_style_bg_color(tabview, lv_color_black(), LV_PART_MAIN);
lv_obj_set_size(tabview, SCREEN_WIDTH, 270); // 270 = screenHeight(320) - panel(30) - statusbar(20)
lv_obj_set_size(tabview, SCREEN_WIDTH,
270); // 270 = screenHeight(320) - panel(30) - statusbar(20)
lv_obj_align(tabview, LV_ALIGN_TOP_MID, 0, 20);
// Add 4 tabs (names are irrelevant since the labels are hidden)
lv_obj_t* tab1 = lv_tabview_add_tab(tabview, "Settings");
lv_obj_t* tab2 = lv_tabview_add_tab(tabview, "Technisat");
lv_obj_t* tab3 = lv_tabview_add_tab(tabview, "Apple TV");
lv_obj_t* tab4 = lv_tabview_add_tab(tabview, "Smart Home");
lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "Settings");
lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "Technisat");
lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "Apple TV");
lv_obj_t *tab4 = lv_tabview_add_tab(tabview, "Smart Home");
// Configure number button grid
static lv_coord_t col_dsc[] = { LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST }; // equal x distribution
static lv_coord_t row_dsc[] = { 52, 52, 52, 52, LV_GRID_TEMPLATE_LAST }; // manual y distribution to compress the grid a bit
// Configure number button grid
static lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1),
LV_GRID_TEMPLATE_LAST}; // equal x distribution
static lv_coord_t row_dsc[] = {
52, 52, 52, 52, LV_GRID_TEMPLATE_LAST}; // manual y distribution to
// compress the grid a bit
// Create a container with grid for tab2
lv_obj_set_style_pad_all(tab2, 0, LV_PART_MAIN);
lv_obj_t* cont = lv_obj_create(tab2);
lv_obj_t *cont = lv_obj_create(tab2);
lv_obj_set_style_shadow_width(cont, 0, LV_PART_MAIN);
lv_obj_set_style_bg_color(cont, lv_color_black(), LV_PART_MAIN);
lv_obj_set_style_border_width(cont, 0, LV_PART_MAIN);
@ -251,48 +267,57 @@ void OmoteUI::layout_UI() {
lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_radius(cont, 0, LV_PART_MAIN);
lv_obj_t* buttonLabel;
lv_obj_t* obj;
lv_obj_t *buttonLabel;
lv_obj_t *obj;
// Iterate through grid buttons configure them
for (int i = 0; i < 12; i++) {
uint8_t col = i % 3;
uint8_t row = i / 3;
// Create the button object
if ((row == 3) && ((col == 0) || (col == 2))) continue; // Do not create a complete fourth row, only a 0 button
if ((row == 3) && ((col == 0) || (col == 2)))
continue; // Do not create a complete fourth row, only a 0 button
obj = lv_btn_create(cont);
lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, col, 1, LV_GRID_ALIGN_STRETCH, row, 1);
lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, col, 1,
LV_GRID_ALIGN_STRETCH, row, 1);
lv_obj_set_style_bg_color(obj, this->color_primary, LV_PART_MAIN);
lv_obj_set_style_radius(obj, 14, LV_PART_MAIN);
lv_obj_set_style_shadow_color(obj, lv_color_hex(0x404040), LV_PART_MAIN);
lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); // Clicking a button causes a event in its container
lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); // Clicking a button causes
// a event in its container
// Create Labels for each button
buttonLabel = lv_label_create(obj);
if(i < 9){
lv_label_set_text_fmt(buttonLabel, std::to_string(i+1).c_str(), col, row);
lv_obj_set_user_data(obj, (void*)i); // Add user data so we can identify which button caused the container event
}
else{
buttonLabel = lv_label_create(obj);
if (i < 9) {
lv_label_set_text_fmt(buttonLabel, std::to_string(i + 1).c_str(), col,
row);
lv_obj_set_user_data(obj,
(void *)i); // Add user data so we can identify which
// button caused the container event
} else {
lv_label_set_text_fmt(buttonLabel, "0", col, row);
lv_obj_set_user_data(obj, (void*)9);
}
lv_obj_set_style_text_font(buttonLabel, &lv_font_montserrat_24, LV_PART_MAIN);
lv_obj_set_user_data(obj, (void *)9);
}
lv_obj_set_style_text_font(buttonLabel, &lv_font_montserrat_24,
LV_PART_MAIN);
lv_obj_center(buttonLabel);
}
// Create a shared event for all button inside container
lv_obj_add_event_cb(cont, [] (lv_event_t* e) {mInstance->virtualKeypad_event_cb(e);}, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(
cont, [](lv_event_t *e) { mInstance->virtualKeypad_event_cb(e); },
LV_EVENT_CLICKED, NULL);
// Add content to the Apple TV tab (3)
// Add a nice apple tv logo
lv_obj_t* appleImg = imgs.addAppleTVIcon(tab3);
lv_obj_t *appleImg = imgs.addAppleTVIcon(tab3);
// create two buttons and add their icons accordingly
lv_obj_t* button = lv_btn_create(tab3);
lv_obj_t *button = lv_btn_create(tab3);
lv_obj_align(button, LV_ALIGN_BOTTOM_LEFT, 10, 0);
lv_obj_set_size(button, 60, 60);
lv_obj_set_style_radius(button, 30, LV_PART_MAIN);
lv_obj_set_style_bg_color(button, color_primary, LV_PART_MAIN);
lv_obj_add_event_cb(button, [] (lv_event_t* e) {mInstance->appleKey_event_cb(e);}, LV_EVENT_CLICKED, (void*)1);
lv_obj_add_event_cb(
button, [](lv_event_t *e) { mInstance->appleKey_event_cb(e); },
LV_EVENT_CLICKED, (void *)1);
appleImg = imgs.addAppleDisplayImage(button);
lv_obj_align(appleImg, LV_ALIGN_CENTER, -3, 0);
@ -305,7 +330,9 @@ void OmoteUI::layout_UI() {
lv_obj_set_size(button, 60, 60);
lv_obj_set_style_radius(button, 30, LV_PART_MAIN);
lv_obj_set_style_bg_color(button, color_primary, LV_PART_MAIN);
lv_obj_add_event_cb(button, [] (lv_event_t* e) {mInstance->appleKey_event_cb(e);}, LV_EVENT_CLICKED, (void*)2);
lv_obj_add_event_cb(
button, [](lv_event_t *e) { mInstance->appleKey_event_cb(e); },
LV_EVENT_CLICKED, (void *)2);
appleImg = imgs.addAppleDisplayImage(button);
lv_obj_align(appleImg, LV_ALIGN_CENTER, 0, 0);
@ -321,39 +348,49 @@ void OmoteUI::layout_UI() {
lv_obj_set_scrollbar_mode(tab4, LV_SCROLLBAR_MODE_ACTIVE);
// Add a label, then a box for the light controls
lv_obj_t* menuLabel = lv_label_create(tab4);
lv_obj_t *menuLabel = lv_label_create(tab4);
lv_label_set_text(menuLabel, "Living Room");
lv_obj_t* menuBox = lv_obj_create(tab4);
lv_obj_t *menuBox = lv_obj_create(tab4);
lv_obj_set_size(menuBox, lv_pct(100), 79);
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(menuBox, 0, LV_PART_MAIN);
lv_obj_t* bulbIcon = imgs.addLightBulbIcon(menuBox);
lv_obj_t *bulbIcon = imgs.addLightBulbIcon(menuBox);
lv_obj_align(bulbIcon, LV_ALIGN_TOP_LEFT, 0, 0);
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Floor Lamp");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 22, 3);
lv_obj_t* lightToggleA = lv_switch_create(menuBox);
lv_obj_t *lightToggleA = lv_switch_create(menuBox);
lv_obj_set_size(lightToggleA, 40, 22);
lv_obj_align(lightToggleA, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_style_bg_color(lightToggleA, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
lv_obj_set_style_bg_color(lightToggleA, lv_color_lighten(color_primary, 50),
LV_PART_MAIN);
lv_obj_set_style_bg_color(lightToggleA, color_primary, LV_PART_INDICATOR);
lv_obj_add_event_cb(lightToggleA, [] (lv_event_t* e) {mInstance->smartHomeToggle_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)1);
lv_obj_add_event_cb(
lightToggleA,
[](lv_event_t *e) { mInstance->smartHomeToggle_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)1);
lv_obj_t *slider = lv_slider_create(menuBox);
lv_slider_set_range(slider, 0, 100);
lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30), LV_PART_INDICATOR);
lv_obj_set_style_bg_grad_color(slider, lv_color_lighten(lv_palette_main(LV_PALETTE_AMBER), 180), LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30),
LV_PART_INDICATOR);
lv_obj_set_style_bg_grad_color(
slider, lv_color_lighten(lv_palette_main(LV_PALETTE_AMBER), 180),
LV_PART_INDICATOR);
lv_obj_set_style_bg_grad_dir(slider, LV_GRAD_DIR_HOR, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider, lv_color_white(), LV_PART_KNOB);
lv_obj_set_style_bg_opa(slider, 255, LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50),
LV_PART_MAIN);
lv_slider_set_value(slider, 255, LV_ANIM_OFF);
lv_obj_set_size(slider, lv_pct(90), 10);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 37);
lv_obj_add_event_cb(slider, [] (lv_event_t* e) {mInstance->smartHomeSlider_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)1);
lv_obj_add_event_cb(
slider, [](lv_event_t *e) { mInstance->smartHomeSlider_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)1);
// Add another this->settingsMenu box for a second appliance
menuBox = lv_obj_create(tab4);
@ -367,26 +404,35 @@ void OmoteUI::layout_UI() {
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Ceiling Light");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 22, 3);
lv_obj_t* lightToggleB = lv_switch_create(menuBox);
lv_obj_t *lightToggleB = lv_switch_create(menuBox);
lv_obj_set_size(lightToggleB, 40, 22);
lv_obj_align(lightToggleB, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_style_bg_color(lightToggleB, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
lv_obj_set_style_bg_color(lightToggleB, lv_color_lighten(color_primary, 50),
LV_PART_MAIN);
lv_obj_set_style_bg_color(lightToggleB, color_primary, LV_PART_INDICATOR);
lv_obj_add_event_cb(lightToggleB, [] (lv_event_t* e) {mInstance->smartHomeToggle_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)2);
lv_obj_add_event_cb(
lightToggleB,
[](lv_event_t *e) { mInstance->smartHomeToggle_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)2);
slider = lv_slider_create(menuBox);
lv_slider_set_range(slider, 0, 100);
lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30), LV_PART_INDICATOR);
lv_obj_set_style_bg_grad_color(slider, lv_color_lighten(lv_palette_main(LV_PALETTE_AMBER), 180), LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30),
LV_PART_INDICATOR);
lv_obj_set_style_bg_grad_color(
slider, lv_color_lighten(lv_palette_main(LV_PALETTE_AMBER), 180),
LV_PART_INDICATOR);
lv_obj_set_style_bg_grad_dir(slider, LV_GRAD_DIR_HOR, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider, lv_color_white(), LV_PART_KNOB);
lv_obj_set_style_bg_opa(slider, 255, LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50),
LV_PART_MAIN);
lv_slider_set_value(slider, 255, LV_ANIM_OFF);
lv_obj_set_size(slider, lv_pct(90), 10);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 37);
lv_obj_add_event_cb(slider, [] (lv_event_t* e) {mInstance->smartHomeSlider_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)2);
lv_obj_add_event_cb(
slider, [](lv_event_t *e) { mInstance->smartHomeSlider_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)2);
// Add another room (empty for now)
menuLabel = lv_label_create(tab4);
@ -397,21 +443,20 @@ void OmoteUI::layout_UI() {
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(menuBox, 0, LV_PART_MAIN);
// Set current page according to the current Device
currentDevice = this->mHardware->getCurrentDevice();
lv_tabview_set_act(tabview, currentDevice, LV_ANIM_OFF);
// Create a page indicator
panel = lv_obj_create(lv_scr_act());
lv_obj_clear_flag(panel, LV_OBJ_FLAG_CLICKABLE); // This indicator will not be clickable
lv_obj_clear_flag(
panel, LV_OBJ_FLAG_CLICKABLE); // This indicator will not be clickable
lv_obj_set_size(panel, SCREEN_WIDTH, 30);
lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_ROW);
lv_obj_align(panel, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_scrollbar_mode(panel, LV_SCROLLBAR_MODE_OFF);
// This small hidden button enables the page indicator to scroll further
lv_obj_t* btn = lv_btn_create(panel);
lv_obj_t *btn = lv_btn_create(panel);
lv_obj_set_size(btn, 50, lv_pct(100));
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
lv_obj_set_style_opa(btn, LV_OPA_TRANSP, LV_PART_MAIN);
@ -419,7 +464,7 @@ void OmoteUI::layout_UI() {
btn = lv_btn_create(panel);
lv_obj_clear_flag(btn, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_size(btn, 150, lv_pct(100));
lv_obj_t* label = lv_label_create(btn);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text_fmt(label, "Settings");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
@ -456,10 +501,15 @@ void OmoteUI::layout_UI() {
lv_obj_set_size(btn, 50, lv_pct(100));
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
lv_obj_set_style_opa(btn, LV_OPA_TRANSP, LV_PART_MAIN);
// Make the indicator scroll together with the tabs by creating a scroll event
lv_obj_add_event_cb(lv_tabview_get_content(tabview), [] (lv_event_t* e) {mInstance->store_scroll_value_event_cb(e);}, LV_EVENT_SCROLL, NULL);
lv_obj_add_event_cb(tabview, [] (lv_event_t* e) {mInstance->tabview_device_event_cb(e);}, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(
lv_tabview_get_content(tabview),
[](lv_event_t *e) { mInstance->store_scroll_value_event_cb(e); },
LV_EVENT_SCROLL, NULL);
lv_obj_add_event_cb(
tabview, [](lv_event_t *e) { mInstance->tabview_device_event_cb(e); },
LV_EVENT_VALUE_CHANGED, NULL);
// Initialize scroll position for the indicator
lv_event_send(lv_tabview_get_content(tabview), LV_EVENT_SCROLL, NULL);
@ -472,10 +522,10 @@ void OmoteUI::layout_UI() {
lv_obj_add_style(panel, &style_btn, 0);
// Make the indicator fade out at the sides using gradient bitmaps
lv_obj_t* img1 = imgs.addLeftGradiant(lv_scr_act());
lv_obj_t *img1 = imgs.addLeftGradiant(lv_scr_act());
lv_obj_align(img1, LV_ALIGN_BOTTOM_LEFT, 0, 0);
lv_obj_set_size(img1, 30, 30); // stretch the 1-pixel high image to 30px
lv_obj_t* img2 = imgs.addRightGradiant(lv_scr_act());
lv_obj_t *img2 = imgs.addRightGradiant(lv_scr_act());
lv_obj_align(img2, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
lv_obj_set_size(img2, 30, 30);

View File

@ -2,18 +2,19 @@
// 2023 Matthew Colvin
#pragma once
#include "HardwareAbstract.hpp"
#include "Images.hpp"
#include "UIBase.hpp"
#include "lvgl.h"
#include "poller.hpp"
#include <algorithm>
#include <memory>
#include <stdio.h>
#include <string>
#include "poller.hpp"
namespace UI::Basic {
/// @brief Singleton to allow UI code to live separately from the Initialization
/// of resources.
class OmoteUI {
class OmoteUI : public UIBase {
public:
OmoteUI(std::shared_ptr<HardwareAbstract> aHardware);
@ -47,86 +48,88 @@ public:
void smartHomeSlider_event_cb(lv_event_t *e);
// Virtual Keypad Event handler
void virtualKeypad_event_cb(lv_event_t *e);
void wifi_settings_cb(lv_event_t* event);
void wifi_settings_cb(lv_event_t *event);
void connect_btn_cb(lv_event_t* event);
void connect_btn_cb(lv_event_t *event);
void password_field_event_cb(lv_event_t* e);
void password_field_event_cb(lv_event_t *e);
// Use LVGL to layout the ui and register the callbacks
void layout_UI();
void ta_kb_event_cb(lv_event_t* e);
void ta_kb_event_cb(lv_event_t *e);
void wifi_scan_done(std::shared_ptr<std::vector<WifiInfo>> info);
void loopHandler();
/**
* @brief Function to hide the keyboard. If the keyboard is attached to a text area, it will be hidden when the
* text area is defocused. This function can be used if the keyboard need to be hidden due to some script event.
*
* @brief Function to hide the keyboard. If the keyboard is attached to a text
* area, it will be hidden when the text area is defocused. This function can
* be used if the keyboard need to be hidden due to some script event.
*
*/
void hide_keyboard();
/**
* @brief Function to show the keyboard. If a text area needs the keybaord, it should be attached to the text area
* using the approbiate function. The keyboard will then show up when the text area is focused. This function is
* @brief Function to show the keyboard. If a text area needs the keybaord, it
* should be attached to the text area using the approbiate function. The
* keyboard will then show up when the text area is focused. This function is
* needed if the keyboard should be shown due to some script or other trigger.
*
*
*/
void show_keyboard();
private:
static std::shared_ptr<OmoteUI> mInstance;
std::shared_ptr<HardwareAbstract> mHardware;
std::unique_ptr<poller> batteryPoller;
int sleepTimeoutMap[5] = {10000,30000,60000,180000,600000};
void reset_settings_menu();
void attach_keyboard(lv_obj_t* textarea);
void attach_keyboard(lv_obj_t *textarea);
std::shared_ptr<std::vector<WifiInfo>> found_wifi_networks;
/**
* @brief Keyboard object used whenever a keyboard is needed.
*
*/
lv_obj_t* kb;
/**
* @brief Keyboard object used whenever a keyboard is needed.
*
*/
lv_obj_t *kb;
/**
* @brief Function to create the keyboard object which can then be attached to different text areas.
*
*/
void create_keyboard();
/**
* @brief Function to create the keyboard object which can then be attached to
* different text areas.
*
*/
void create_keyboard();
/**
* @brief Set the up settings object
*
* @param parent
*
* @param parent
*/
void setup_settings(lv_obj_t* parent);
void setup_settings(lv_obj_t *parent);
/**
/**
* @brief LVGL Menu for settings pages as needed.
*
*
*/
lv_obj_t* settingsMenu;
lv_obj_t *settingsMenu;
/**
* @brief Main page of the settings menu
*
* @brief Main page of the settings menu
*
*/
lv_obj_t* settingsMainPage;
lv_obj_t *settingsMainPage;
/**
* @brief Battery percentage label
*
* @brief Battery percentage label
*
*/
lv_obj_t* objBattPercentage;
lv_obj_t *objBattPercentage;
/**
* @brief Battery icon object in the status bar
*
* @brief Battery icon object in the status bar
*
*/
lv_obj_t* objBattIcon;
lv_obj_t *objBattIcon;
void create_status_bar();
@ -138,104 +141,110 @@ void create_keyboard();
inline static const uint_fast8_t virtualKeyMapTechnisat[10] = {
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0};
/************************************** WIFI Settings Menu *******************************************************/
/************************************** WIFI Settings Menu
* *******************************************************/
/**
* @brief Container within the wifi selection page
*/
lv_obj_t* wifi_setting_cont;
lv_obj_t *wifi_setting_cont;
/**
* @brief Wifi settings entry point on the settings tab
*
*
*/
lv_obj_t* wifiOverview;
lv_obj_t *wifiOverview;
/**
* @brief Label in the wifi password page. This label is updated with the selected SSID when the credentials for
* a wifi network is entered.
*
* @brief Label in the wifi password page. This label is updated with the
* selected SSID when the credentials for a wifi network is entered.
*
*/
lv_obj_t* wifi_password_label;
lv_obj_t *wifi_password_label;
/**
* @brief Menu Subpage for the wifi password
*/
lv_obj_t* wifi_password_page;
lv_obj_t *wifi_password_page;
/**
* @brief Menu Subpage for wifi selection
*/
lv_obj_t* wifi_selection_page;
lv_obj_t *wifi_selection_page;
/**
* @brief Wifi Label shown in the top status bar
*/
lv_obj_t* WifiLabel;
lv_obj_t *WifiLabel;
/**
* @brief Number of wifi subpage needed to display the found wifi networks
*
*
*/
unsigned int no_subpages;
/**
* @brief number of wifi networks found
*
*
*/
unsigned int no_wifi_networks;
void wifi_status(std::shared_ptr<wifiStatus> status);
/**
* @brief callback function to get next wifi subpage. This callback can be used to get the next or previous page
*
* @param e lvgl event object
* @brief callback function to get next wifi subpage. This callback can be
* used to get the next or previous page
*
* @param e lvgl event object
*/
void next_wifi_selection_subpage(lv_event_t* e);
void next_wifi_selection_subpage(lv_event_t *e);
/**
* @brief Create a wifi selection sub page object
*
* @param menu LVGL Menu where the sub page should be added to
*
* @param menu LVGL Menu where the sub page should be added to
* @return lv_obj_t* Menu sub page object pointer
*/
lv_obj_t* create_wifi_selection_page(lv_obj_t* menu);
lv_obj_t *create_wifi_selection_page(lv_obj_t *menu);
/**
* @brief Method to create the wifi password sub page
*
* @param menu Menu where the sub page should be created
*
* @param menu Menu where the sub page should be created
* @return lv_obj_t* menu sub page object pointer
*/
lv_obj_t* create_wifi_password_page(lv_obj_t* menu);
lv_obj_t *create_wifi_password_page(lv_obj_t *menu);
/**
* @brief Method to create the wifi settings on the main page
*
* @param parent lv object parent where the main settings page should be added to
*
* @param parent lv object parent where the main settings page should be
* added to
*/
void create_wifi_main_page(lv_obj_t* parent);
void create_wifi_main_page(lv_obj_t *parent);
/**
* @brief Method to create wifi settings. This method will call the create_wifi_selection_page,
* the create_wifi_password_page, and the create_wifi_main_page
*
* @param menu Settings menu where the sub pages should be added to
* @param parent lv object parent where the main settings page should be added to
* @brief Method to create wifi settings. This method will call the
* create_wifi_selection_page, the create_wifi_password_page, and the
* create_wifi_main_page
*
* @param menu Settings menu where the sub pages should be added to
* @param parent lv object parent where the main settings page should be
* added to
*/
void create_wifi_settings(lv_obj_t* menu, lv_obj_t* parent);
void create_wifi_settings(lv_obj_t *menu, lv_obj_t *parent);
/**
* @brief Function to update the wifi selection sub page
*
* @param page index of the page to display
*
* @param page index of the page to display
*/
void update_wifi_selection_subpage(int page);
/**
* @brief Function to create the display settings page.
*
*
* @param parent LVGL object acting as a parent for the display settings page
*/
void display_settings(lv_obj_t* parent);
void display_settings(lv_obj_t *parent);
};
} // namespace UI::Basic

View File

@ -1,61 +1,78 @@
#include "OmoteUI.hpp"
void OmoteUI::display_settings(lv_obj_t* parent)
{
using namespace UI::Basic;
lv_obj_t* menuLabel = lv_label_create(parent);
void OmoteUI::display_settings(lv_obj_t *parent) {
lv_obj_t *menuLabel = lv_label_create(parent);
lv_label_set_text(menuLabel, "Display");
lv_obj_t* menuBox = lv_obj_create(parent);
lv_obj_t *menuBox = lv_obj_create(parent);
lv_obj_set_size(menuBox, lv_pct(100), 109);
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(menuBox, 0, LV_PART_MAIN);
lv_obj_t* brightnessIcon = imgs.addLowBrightnessIcon(menuBox);
lv_obj_t *brightnessIcon = imgs.addLowBrightnessIcon(menuBox);
lv_obj_align(brightnessIcon, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_t* slider = lv_slider_create(menuBox);
lv_obj_t *slider = lv_slider_create(menuBox);
lv_slider_set_range(slider, 0, 255);
lv_obj_set_style_bg_color(slider, lv_color_white(), LV_PART_KNOB);
lv_obj_set_style_bg_opa(slider, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
lv_slider_set_value(slider, mHardware->display()->getBrightness() , LV_ANIM_OFF);
lv_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50),
LV_PART_MAIN);
lv_slider_set_value(slider, mHardware->display()->getBrightness(),
LV_ANIM_OFF);
lv_obj_set_size(slider, lv_pct(66), 10);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 3);
brightnessIcon = imgs.addHighBrightnessIcon(menuBox);
lv_obj_align(brightnessIcon, LV_ALIGN_TOP_RIGHT, 0, -1);
lv_obj_add_event_cb(slider, [] (lv_event_t* e) {mInstance->bl_slider_event_cb(e);}, LV_EVENT_VALUE_CHANGED, nullptr);
lv_obj_add_event_cb(
slider, [](lv_event_t *e) { mInstance->bl_slider_event_cb(e); },
LV_EVENT_VALUE_CHANGED, nullptr);
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Lift to Wake");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 0, 32);
lv_obj_t* wakeToggle = lv_switch_create(menuBox);
lv_obj_t *wakeToggle = lv_switch_create(menuBox);
lv_obj_set_size(wakeToggle, 40, 22);
lv_obj_align(wakeToggle, LV_ALIGN_TOP_RIGHT, 0, 29);
lv_obj_set_style_bg_color(wakeToggle, lv_color_hex(0x505050), LV_PART_MAIN);
lv_obj_add_event_cb(wakeToggle, [] (lv_event_t* e) {mInstance->WakeEnableSetting_event_cb(e);}, LV_EVENT_VALUE_CHANGED, NULL);
if(mHardware->getWakeupByIMUEnabled()) lv_obj_add_state(wakeToggle, LV_STATE_CHECKED); // set default state
lv_obj_add_event_cb(
wakeToggle,
[](lv_event_t *e) { mInstance->WakeEnableSetting_event_cb(e); },
LV_EVENT_VALUE_CHANGED, NULL);
if (mHardware->getWakeupByIMUEnabled())
lv_obj_add_state(wakeToggle, LV_STATE_CHECKED); // set default state
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Timeout");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 0, 64);
lv_obj_t* drop = lv_dropdown_create(menuBox);
lv_obj_t *drop = lv_dropdown_create(menuBox);
lv_dropdown_set_options(drop, "10s\n"
"30s\n"
"1m\n"
"3m");
lv_obj_align(drop, LV_ALIGN_TOP_RIGHT, 0, 61);
lv_obj_set_size(drop, 70, 22);
//lv_obj_set_style_text_font(drop, &lv_font_montserrat_12, LV_PART_MAIN);
//lv_obj_set_style_text_font(lv_dropdown_get_list(drop), &lv_font_montserrat_12, LV_PART_MAIN);
// lv_obj_set_style_text_font(drop, &lv_font_montserrat_12, LV_PART_MAIN);
// lv_obj_set_style_text_font(lv_dropdown_get_list(drop),
// &lv_font_montserrat_12, LV_PART_MAIN);
lv_obj_set_style_pad_top(drop, 1, LV_PART_MAIN);
lv_obj_set_style_bg_color(drop, color_primary, LV_PART_MAIN);
lv_obj_set_style_bg_color(lv_dropdown_get_list(drop), color_primary, LV_PART_MAIN);
lv_obj_set_style_bg_color(lv_dropdown_get_list(drop), color_primary,
LV_PART_MAIN);
lv_obj_set_style_border_width(lv_dropdown_get_list(drop), 1, LV_PART_MAIN);
lv_obj_set_style_border_color(lv_dropdown_get_list(drop), lv_color_hex(0x505050), LV_PART_MAIN);
int sleepTimeoutMapSize = sizeof(sleepTimeoutMap)/sizeof(sleepTimeoutMap[0]);
lv_obj_set_style_border_color(lv_dropdown_get_list(drop),
lv_color_hex(0x505050), LV_PART_MAIN);
int sleepTimeoutMapSize =
sizeof(sleepTimeoutMap) / sizeof(sleepTimeoutMap[0]);
int currentTimeout = mHardware->getSleepTimeout();
for(int i = 0; i < sleepTimeoutMapSize; i++){
if(currentTimeout == sleepTimeoutMap[i]) lv_dropdown_set_selected(drop, i);
for (int i = 0; i < sleepTimeoutMapSize; i++) {
if (currentTimeout == sleepTimeoutMap[i])
lv_dropdown_set_selected(drop, i);
}
lv_obj_add_event_cb(drop, [] (lv_event_t* e) {mInstance->wakeTimeoutSetting_event_cb(e);}, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(
drop, [](lv_event_t *e) { mInstance->wakeTimeoutSetting_event_cb(e); },
LV_EVENT_VALUE_CHANGED, NULL);
}

View File

@ -3,6 +3,8 @@
#define WIFI_SUBPAGE_SIZE 3
static char* ssid;
using namespace UI::Basic;
lv_obj_t* OmoteUI::create_wifi_selection_page(lv_obj_t* menu)
{
/* Create sub page for wifi*/

View File

@ -0,0 +1,21 @@
#include "BasicUI.hpp"
#include "HardwareFactory.hpp"
#include "HomeScreen.hpp"
#include "ScreenManager.hpp"
using namespace UI;
BasicUI::BasicUI() : UIBase() {
HardwareFactory::getAbstract().keys()->RegisterKeyPressHandler(
[](auto aKeyEvent) {
return Screen::Manager::getInstance().distributeKeyEvent(aKeyEvent);
// Could potentially add a check here and display that a key event was
// unused.
});
Screen::Manager::getInstance().pushScreen(
std::make_unique<Screen::HomeScreen>());
HardwareFactory::getAbstract().wifi()->begin();
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "UIBase.hpp"
namespace UI {
class BasicUI : public UIBase {
public:
BasicUI();
};
} // namespace UI

View File

@ -0,0 +1,51 @@
#include "Demo.hpp"
#include "Slider.hpp"
using namespace UI::Page;
Demo::Demo() : Base(ID::Pages::Demo) {}
void Demo::AddSlider() {
auto fakeSlider = std::make_unique<Widget::Slider>([](auto data) {});
fakeSlider->SetHeight(lv_pct(10));
fakeSlider->SetWidth(GetContentWidth());
if (sliders.empty()) {
fakeSlider->AlignTo(this, LV_ALIGN_TOP_MID);
} else {
auto nextY = sliders.back()->GetY() + sliders.back()->GetHeight();
fakeSlider->SetY(nextY + 10);
}
sliders.push_back(AddElement(std::move(fakeSlider)));
}
bool Demo::OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
using id = KeyPressAbstract::KeyId;
using eventType = KeyPressAbstract::KeyEvent::Type;
bool used = true;
switch (aKeyEvent.mId) {
case id::Aux1:
if (aKeyEvent.mType == eventType::Press) {
AddSlider();
}
break;
case id::Aux2:
if (aKeyEvent.mType == eventType::Release) {
if (sliders.size() > 0) {
RemoveElement(sliders[0]);
sliders.erase(
sliders.begin()); // sliders is non owning so after removing delete
// it from non owning array
}
}
break;
case id::Aux3:
break;
case id::Aux4:
break;
default:
used = Page::Base::OnKeyEvent(aKeyEvent);
break;
}
return used;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "PageBase.hpp"
namespace UI::Page {
class Demo : public Base {
public:
Demo();
void AddSlider();
void OnShow() override{};
void OnHide() override{};
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent);
private:
std::vector<UIElement *> sliders;
};
} // namespace UI::Page

View File

@ -0,0 +1,16 @@
#include "DisplaySettings.hpp"
#include "BrightnessSlider.hpp"
#include "Label.hpp"
using namespace UI::Page;
DisplaySettings::DisplaySettings(std::shared_ptr<DisplayAbstract> aDisplay)
: Base(UI::ID::Pages::DisplaySettings), mDisplay(aDisplay),
mBrightnessSlider(AddElement<Widget::BrightnessSlider>(
std::make_unique<Widget::BrightnessSlider>(mDisplay))) {
SetBgColor(Color::GREY);
mBrightnessSlider->SetWidth(GetContentWidth());
mBrightnessSlider->SetHeight(80);
mBrightnessSlider->AlignTo(this, LV_ALIGN_TOP_MID);
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "DisplayAbstract.h"
#include "DropDown.hpp"
#include "PageBase.hpp"
namespace UI::Widget {
class BrightnessSlider;
} // namespace UI::Widget
namespace UI::Page {
class DisplaySettings : public Base {
public:
DisplaySettings(std::shared_ptr<DisplayAbstract> aDisplay);
std::string GetTitle() override { return "Display Settings"; };
private:
std::shared_ptr<DisplayAbstract> mDisplay;
Widget::BrightnessSlider *mBrightnessSlider;
};
} // namespace UI::Page

View File

@ -0,0 +1,43 @@
#include "SettingsPage.hpp"
#include "BackgroundScreen.hpp"
#include "Button.hpp"
#include "Colors.hpp"
#include "DisplaySettings.hpp"
#include "HardwareFactory.hpp"
#include "List.hpp"
#include "PopUpScreen.hpp"
#include "ScreenManager.hpp"
#include "Slider.hpp"
#include "SystemSettings.hpp"
#include "WifiSettings.hpp"
using namespace UI::Page;
using namespace UI::Color;
SettingsPage::SettingsPage()
: Base(ID::Pages::Settings), mSettingsList(AddElement<Widget::List>(
std::make_unique<Widget::List>())) {
mSettingsList->AddItem("Display", LV_SYMBOL_EYE_OPEN,
[this] { PushDisplaySettings(); });
mSettingsList->AddItem("Wifi", LV_SYMBOL_WIFI,
[this] { PushWifiSettings(); });
mSettingsList->AddItem("System", LV_SYMBOL_SETTINGS,
[this] { PushSystemSettings(); });
}
void SettingsPage::PushDisplaySettings() {
UI::Screen::Manager::getInstance().pushPopUp(
std::make_unique<DisplaySettings>(
HardwareFactory::getAbstract().display()));
}
void SettingsPage::PushSystemSettings() {
UI::Screen::Manager::getInstance().pushPopUp(
std::make_unique<SystemSettings>());
}
void SettingsPage::PushWifiSettings() {
UI::Screen::Manager::getInstance().pushPopUp(
std::make_unique<WifiSettings>(HardwareFactory::getAbstract().wifi()));
}

View File

@ -0,0 +1,28 @@
#include "HardwareAbstract.hpp"
#include "PageBase.hpp"
namespace UI::Widget {
class Button;
class List;
} // namespace UI::Widget
namespace UI::Page {
class SettingsPage : public Base {
public:
SettingsPage();
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override {
return false;
};
void PushDisplaySettings();
void PushSystemSettings();
void PushWifiSettings();
protected:
void OnShow() override{};
void OnHide() override{};
Widget::Button *mButton;
Widget::List *mSettingsList;
};
} // namespace UI::Page

View File

@ -0,0 +1,27 @@
#include "SystemSettings.hpp"
#include "HardwareFactory.hpp"
#include "Label.hpp"
using namespace UI::Page;
SystemSettings::SystemSettings()
: Base(ID::Pages::SystemSettings),
mTimeoutLabel(AddElement<Widget::Label>(
std::make_unique<Widget::Label>("TimeOut"))),
mScreenTimeOutDropDown(AddElement<Widget::DropDown<int>>(
std::make_unique<Widget::DropDown<int>>([this](int aTimeout) {
HardwareFactory::getAbstract().setSleepTimeout(aTimeout);
}))) {
mTimeoutLabel->AlignTo(this, LV_ALIGN_TOP_MID);
mTimeoutLabel->SetHeight(15);
mScreenTimeOutDropDown->SetHeight(30);
mScreenTimeOutDropDown->SetWidth(GetContentWidth());
mScreenTimeOutDropDown->AddItem("10 Seconds", 10000);
mScreenTimeOutDropDown->AddItem("15 Seconds", 15000);
mScreenTimeOutDropDown->AddItem("20 Seconds", 20000);
mScreenTimeOutDropDown->AlignTo(mTimeoutLabel, LV_ALIGN_OUT_BOTTOM_MID);
mScreenTimeOutDropDown->SetSelected(
HardwareFactory::getAbstract().getSleepTimeout());
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "DropDown.hpp"
#include "PageBase.hpp"
namespace UI::Widget {
class Label;
}
namespace UI::Page {
class SystemSettings : public Base {
public:
SystemSettings();
protected:
std::string GetTitle() override { return "System Settings"; }
private:
Widget::Label *mTimeoutLabel;
Widget::DropDown<int> *mScreenTimeOutDropDown;
};
} // namespace UI::Page

View File

@ -0,0 +1,79 @@
#include "WifiSettings.hpp"
#include "Keyboard.hpp"
#include "Label.hpp"
#include "List.hpp"
#include "LvglResourceManager.hpp"
using namespace UI;
using namespace UI::Page;
WifiSettings::WifiSettings(std::shared_ptr<wifiHandlerInterface> aWifi)
: Base(ID::Pages::WifiSettings), mWifi(aWifi),
mScanCompleteHandler(mWifi->ScanCompleteNotification()),
mScanStatusHandler(mWifi->WifiStatusNotification()),
mScanningText(AddElement<Widget::Label>(
std::make_unique<Widget::Label>("Scanning..."))),
mWifiNetworks(AddElement<Widget::List>(std::make_unique<Widget::List>())),
mPasswordGetter(nullptr) {
// Set Handler for when the wifi scan is done
mScanCompleteHandler = [this](auto aWifiInfos) {
if (aWifiInfos.empty()) {
mScanningText->SetText("No Networks Found");
return;
}
mScanningText->SetText("Networks Found");
// Create List of wifi infos that when pressed a Keyboard opens
for (WifiInfo wifiInfo : aWifiInfos) {
mWifiNetworks->AddItem(wifiInfo.ssid, LV_SYMBOL_WIFI, [this, wifiInfo] {
OpenPasswordKeyboard(wifiInfo);
});
}
};
mWifi->scan();
}
void WifiSettings::OpenPasswordKeyboard(WifiInfo aNetworkToConnectTo) {
// We already have a Keyboard don't launch another one.
if (mPasswordGetter) {
return;
}
auto keyboard = std::make_unique<Widget::Keyboard>(
[this, aNetworkToConnectTo](auto aUserEnteredPassword) {
// Attempt Connection when user finishes up with keyboard input
mWifi->connect(aNetworkToConnectTo.ssid, aUserEnteredPassword);
mScanningText->SetText("Attempting Connection to " +
aNetworkToConnectTo.ssid);
mPasswordGetter->AnimateOut();
StartHandlingStatusUpdates();
},
"Password:");
keyboard->OnKeyboardAnimatedOut([this] {
// Keyboard is done animating out remove it and null the ref
RemoveElement(mPasswordGetter);
mPasswordGetter = nullptr;
});
mPasswordGetter = AddElement<Widget::Keyboard>(std::move(keyboard));
}
void WifiSettings::StartHandlingStatusUpdates() {
mScanStatusHandler = [this](auto aWifiStatus) {
if (aWifiStatus.isConnected) {
mScanningText->SetText("Connected to " + aWifiStatus.ssid);
} else {
mScanningText->SetText("Failed To Connect To " + aWifiStatus.ssid);
}
};
}
void WifiSettings::SetHeight(lv_coord_t aHeight) {
Base::SetHeight(aHeight);
mScanningText->AlignTo(this, LV_ALIGN_TOP_MID);
mScanningText->SetHeight(20);
mScanningText->SetLongMode(LV_LABEL_LONG_SCROLL);
const auto padding = 10;
mWifiNetworks->AlignTo(mScanningText, LV_ALIGN_OUT_BOTTOM_MID, 0, padding);
mWifiNetworks->SetHeight(GetContentHeight() - mScanningText->GetBottom() -
padding);
};

View File

@ -0,0 +1,37 @@
#pragma once
#include "PageBase.hpp"
#include "wifiHandlerInterface.h"
namespace UI::Widget {
class List;
class Label;
class Keyboard;
} // namespace UI::Widget
namespace UI::Page {
class WifiSettings : public Base {
using WifiInfo = wifiHandlerInterface::WifiInfo;
public:
WifiSettings(std::shared_ptr<wifiHandlerInterface> aWifi);
std::string GetTitle() override { return "Wifi Settings"; };
void SetHeight(lv_coord_t aHeight) override;
protected:
void StartHandlingStatusUpdates();
void OpenPasswordKeyboard(WifiInfo aNetworkToConnectTo);
private:
std::shared_ptr<wifiHandlerInterface> mWifi;
Handler<wifiHandlerInterface::ScanDoneDataTy> mScanCompleteHandler;
Handler<wifiHandlerInterface::wifiStatus> mScanStatusHandler;
UI::Widget::Label *mScanningText;
UI::Widget::List *mWifiNetworks;
UI::Widget::Keyboard *mPasswordGetter;
};
} // namespace UI::Page

View File

@ -0,0 +1,28 @@
#include "HomeScreen.hpp"
#include "Colors.hpp"
#include "Demo.hpp"
#include "SettingsPage.hpp"
using namespace UI::Screen;
HomeScreen::HomeScreen()
: Base(UI::ID::Screens::Home),
mTabView(AddElement<Page::TabView>(
std::make_unique<Page::TabView>(ID(ID::Pages::INVALID_PAGE_ID)))) {
SetBgColor(UI::Color::BLACK);
SetPushAnimation(LV_SCR_LOAD_ANIM_FADE_IN);
// Adds pages to the Tab view
mTabView->AddTab(std::make_unique<Page::SettingsPage>());
mTabView->AddTab(std::make_unique<Page::Demo>());
}
void HomeScreen::SetBgColor(lv_color_t value, lv_style_selector_t selector) {
mTabView->SetBgColor(value, selector);
UI::UIElement::SetBgColor(value, selector);
}
bool HomeScreen::OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
return false;
};

View File

@ -0,0 +1,23 @@
#pragma once
#include "HardwareAbstract.hpp"
#include "PageBase.hpp"
#include "ScreenBase.hpp"
#include "TabView.hpp"
#include <string>
namespace UI::Screen {
class HomeScreen : public Base {
public:
HomeScreen();
void SetBgColor(lv_color_t value,
lv_style_selector_t selector = LV_PART_MAIN) override;
protected:
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override;
private:
Page::TabView *mTabView;
};
} // namespace UI::Screen

View File

@ -0,0 +1,44 @@
#include "BrightnessSlider.hpp"
#include "Label.hpp"
#include "Slider.hpp"
using namespace UI::Widget;
BrightnessSlider::BrightnessSlider(std::shared_ptr<DisplayAbstract> aDisplay)
: Base(ID::Widgets::BrightnessSlider), mDisplay(aDisplay),
mSlider(AddElement<Widget::Slider>(std::make_unique<Slider>(
[this](auto aNewBrightness) {
mDisplay->setBrightness(aNewBrightness);
},
0, 255))),
mLabel(AddElement<Widget::Label>(std::make_unique<Label>("Brightness"))) {
mLabel->AlignTo(this, LV_ALIGN_TOP_MID);
mSlider->AlignTo(mLabel, LV_ALIGN_OUT_BOTTOM_MID);
mSlider->SetWidth(GetContentWidth() - GetContentWidth() * 0.25f);
}
void BrightnessSlider::OnShow() {
mSlider->SetValue(mDisplay->getBrightness());
Base::OnShow();
}
void BrightnessSlider::SetHeight(lv_coord_t aHeight) {
Base::SetHeight(aHeight);
auto labelHeight = 12;
auto sliderHeight = aHeight * 0.60f - labelHeight;
mLabel->SetHeight(labelHeight);
mSlider->SetHeight(sliderHeight);
mLabel->AlignTo(this, LV_ALIGN_TOP_MID);
mSlider->AlignTo(mLabel, LV_ALIGN_OUT_BOTTOM_MID, 0,
mLabel->GetContentHeight());
}
bool BrightnessSlider::OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
using ID = KeyPressAbstract::KeyId;
using Type = KeyPressAbstract::KeyEvent::Type;
if (aKeyEvent.mId == ID::Aux1 && aKeyEvent.mType == Type::Press) {
SetVisiblity(!IsSetVisible());
return true;
}
return false;
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "DisplayAbstract.h"
#include "WidgetBase.hpp"
namespace UI::Widget {
class Slider;
class Label;
class BrightnessSlider : public Base {
public:
BrightnessSlider(std::shared_ptr<DisplayAbstract> aDisplay);
void SetHeight(lv_coord_t aHeight) override;
protected:
void OnShow() override;
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override;
private:
std::shared_ptr<DisplayAbstract> mDisplay;
Slider *mSlider;
Label *mLabel;
};
} // namespace UI::Widget

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
// OMOTE UI Images
// 2023 Matthew Colvin
#pragma once
#include "lvgl.h"
class Images {
public:
Images();
lv_obj_t *addAppleTVIcon(lv_obj_t *parent);
lv_obj_t *addAppleDisplayImage(lv_obj_t *parent);
lv_obj_t *addAppleBackIcon(lv_obj_t *parent);
lv_obj_t *addLowBrightnessIcon(lv_obj_t *parent);
lv_obj_t *addHighBrightnessIcon(lv_obj_t *parent);
lv_obj_t *addLightBulbIcon(lv_obj_t *parent);
lv_obj_t *addLeftGradiant(lv_obj_t *parent);
lv_obj_t *addRightGradiant(lv_obj_t *parent);
lv_obj_t *addWifiNoSignal(lv_obj_t *parent);
lv_obj_t *addWifiLowSignal(lv_obj_t *parent);
lv_obj_t *addWifiMidSignal(lv_obj_t *parent);
lv_obj_t *addWifiHighSignal(lv_obj_t *parent);
private:
// Make Image based on anImageDesc then
// add that image to parent.
lv_obj_t *addImg(lv_obj_t *parent, lv_img_dsc_t *anImgDesc);
void setupImageDescriptions();
lv_img_dsc_t appleTvIcon;
lv_img_dsc_t appleDisplayIcon;
lv_img_dsc_t appleBackIcon;
lv_img_dsc_t low_brightness;
lv_img_dsc_t high_brightness;
lv_img_dsc_t lightbulb_icon;
lv_img_dsc_t gradientLeft;
lv_img_dsc_t gradientRight;
lv_img_dsc_t wifiNoSignal;
lv_img_dsc_t wifiLowSignal;
lv_img_dsc_t wifiMidSignal;
lv_img_dsc_t wifiHighSignal;
};

View File

@ -0,0 +1,49 @@
#pragma once
#include <functional>
#include <mutex>
#include <queue>
namespace UI {
class UIBase;
} // namespace UI
class LvglResourceManager {
friend UI::UIBase;
public:
static LvglResourceManager &GetInstance() {
static LvglResourceManager mInstance;
return mInstance;
}
[[nodiscard]] std::scoped_lock<std::recursive_mutex> scopeLock() {
return std::scoped_lock(mLvglMutex);
}
void AttemptNow(std::function<void()> aLvglModifierFunction) {
// Attempt to take mutex and preform op if you can't then queue up.
if (mLvglMutex.try_lock()) {
aLvglModifierFunction();
mLvglMutex.unlock();
} else {
QueueForLater(aLvglModifierFunction);
}
}
void QueueForLater(std::function<void()> aLvglModifierFunction) {
mLvglTasks.push(std::move(aLvglModifierFunction));
}
protected:
LvglResourceManager(){};
void HandleQueuedTasks() {
auto lock = scopeLock();
while (!mLvglTasks.empty()) {
mLvglTasks.front()();
mLvglTasks.pop();
}
}
std::queue<std::function<void()>> mLvglTasks;
std::recursive_mutex mLvglMutex;
};

View File

@ -0,0 +1,15 @@
#include "UIBase.hpp"
#include "LvglResourceManager.hpp"
using namespace UI;
UIBase::UIBase() {}
void UIBase::loopHandler() {
{
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_timer_handler();
lv_task_handler();
}
LvglResourceManager::GetInstance().HandleQueuedTasks();
}

View File

@ -0,0 +1,19 @@
// OMOTE UI
// 2023 Matthew Colvin
#pragma once
#include "HardwareAbstract.hpp"
#include <memory>
namespace UI {
class UIBase {
public:
UIBase();
virtual void loopHandler();
protected:
};
} // namespace UI

View File

@ -0,0 +1,40 @@
#include "Animation.hpp"
using namespace UI;
Animation::Animation(std::function<void(int32_t)> aAnimator,
int32_t aAnimationTime, int32_t aStart, int32_t aEnd)
: mAnimator(aAnimator), mStart(aStart), mEnd(aEnd) {
lv_anim_init(&mAnimation);
mAnimation.user_data = this;
lv_anim_set_custom_exec_cb(&mAnimation, AnimatorImpl);
lv_anim_set_time(&mAnimation, aAnimationTime);
}
Animation::~Animation() { lv_anim_custom_del(&mAnimation, AnimatorImpl); }
void Animation::HandleAnimationComplete(
std::function<void()> onAnimationComplete) {
mOnComplete = onAnimationComplete;
}
void Animation::Start() {
lv_anim_set_values(&mAnimation, mStart, mEnd);
lv_anim_start(&mAnimation);
}
void Animation::Reverse() {
std::swap(mStart, mEnd);
Start();
}
//////////////////////// Statics /////////////////////////////////////////
void Animation::AnimatorImpl(lv_anim_t *aSelf, int32_t aNextValue) {
auto self = reinterpret_cast<Animation *>(aSelf->user_data);
self->mAnimator(aNextValue);
// TODO This probably will not support Overshoot animations.
if (self->mOnComplete && aNextValue == self->mEnd) {
self->mOnComplete();
}
}

View File

@ -0,0 +1,31 @@
#pragma once
#include <functional>
#include <lvgl.h>
namespace UI {
class Animation {
public:
Animation(std::function<void(int32_t)> aAnimator, int32_t aAnimationTime,
int32_t aStart = 0, int32_t aEnd = 100);
virtual ~Animation();
void HandleAnimationComplete(std::function<void()> onAnimationComplete);
void Start();
void Reverse();
private:
lv_anim_t mAnimation;
std::function<void(int32_t)> mAnimator = nullptr;
std::function<void()> mOnComplete = nullptr;
bool onCompleteCalled = false;
int32_t mStart = 0;
int32_t mEnd = 0;
static void AnimatorImpl(lv_anim_t *aSelf, int32_t aNextValue);
};
} // namespace UI

View File

@ -0,0 +1,110 @@
#pragma once
#include "Colors.hpp"
#include "UIElement.hpp"
#include <lvgl.h>
// Builder Class to make it easier to create boarder and Paddings
namespace UI {
/////////////////////////////////Border/////////////////////////////////
class Border {
friend class UIElement;
public:
Border() = default;
Border &Width(lv_coord_t aWidth) {
width = aWidth;
return *this;
}
Border &Color(lv_color_t aColor) {
color = aColor;
return *this;
}
Border &Opacity(lv_opa_t aOpacity) {
opacity = aOpacity;
return *this;
}
Border &Sides(lv_border_side_t aSides) {
sides = aSides;
return *this;
}
private:
lv_coord_t width = 0;
lv_color_t color = UI::Color::BLACK;
lv_opa_t opacity = 255;
lv_border_side_t sides = LV_BORDER_SIDE_FULL;
};
///////////////////////////////////////Outline////////////////////////////////
class Outline {
friend class UIElement;
public:
Outline() = default;
Outline &Width(lv_coord_t aWidth) {
width = aWidth;
return *this;
}
Outline &Color(lv_color_t aColor) {
color = aColor;
return *this;
}
Outline &Opacity(lv_opa_t aOpacity) {
opacity = aOpacity;
return *this;
}
Outline &Padding(lv_coord_t aPadding) {
padding = aPadding;
return *this;
}
private:
lv_coord_t width = 0;
lv_color_t color = UI::Color::BLACK;
lv_opa_t opacity = 255;
lv_coord_t padding = 0;
};
//////////////////////////////////////Padding////////////////////////////////
class Padding {
friend class UIElement;
public:
Padding() = default;
Padding &Top(lv_coord_t aTopPadding) {
top = aTopPadding;
return *this;
};
Padding &Bottom(lv_coord_t aBottomPadding) {
bottom = aBottomPadding;
return *this;
};
Padding &Left(lv_coord_t aLeftPadding) {
left = aLeftPadding;
return *this;
};
Padding &Right(lv_coord_t aRightPadding) {
right = aRightPadding;
return *this;
};
Padding &Row(lv_coord_t aRowPadding) {
row = aRowPadding;
return *this;
}
Padding &Column(lv_coord_t aColumnPadding) {
column = aColumnPadding;
return *this;
}
private:
lv_coord_t top = 0;
lv_coord_t bottom = 0;
lv_coord_t left = 0;
lv_coord_t right = 0;
lv_coord_t row = 0;
lv_coord_t column = 0;
};
} // namespace UI

View File

@ -0,0 +1,16 @@
#pragma once
#include <lvgl.h>
namespace UI::Color {
const auto WHITE = lv_color_white();
const auto BLACK = lv_color_black();
const auto RED = lv_color_make(255, 0, 0);
const auto GREEN = lv_color_make(0, 255, 0);
const auto BLUE = lv_color_make(0, 0, 255);
const auto PURPLE = lv_color_make(128, 0, 128);
const auto LILAC = lv_color_make(231, 209, 255);
const auto GREY = lv_color_make(105, 105, 105);
} // namespace UI::Color

View File

@ -0,0 +1,62 @@
#include "ScreenManager.hpp"
#include "PopUpScreen.hpp"
using namespace UI::Screen;
Manager Manager::mManager = Manager();
Manager &Manager::getInstance() { return mManager; }
Manager::Manager() {}
void Manager::pushScreen(Screen::Base::Ptr aScreen) {
if (!mScreens.empty()) {
mScreens.back()->OnHide();
}
mScreens.push_back(std::move(aScreen));
mScreens.back()->Show();
}
void Manager::pushScreen(Screen::Base::Ptr aScreen,
lv_scr_load_anim_t aPushAnimation) {
aScreen->SetPushAnimation(aPushAnimation);
pushScreen(std::move(aScreen));
}
void Manager::pushPopUp(Page::Base::Ptr aPopUpPage,
lv_scr_load_anim_t aPushAnimation) {
pushScreen(std::make_unique<PopUpScreen>(std::move(aPopUpPage)),
aPushAnimation);
}
UI::Screen::Base::Ptr Manager::popScreen(Screen::Base *aScreenToRemove) {
auto screenToPop = std::find_if(
mScreens.begin(), mScreens.end(), [aScreenToRemove](auto &screenInStack) {
return screenInStack.get() == aScreenToRemove;
});
bool isPopOffTopOfStack = mScreens.end() - 1 == screenToPop;
Screen::Base::Ptr retVal = nullptr;
if (screenToPop != mScreens.end()) {
retVal = std::move(*screenToPop);
mScreens.erase(screenToPop);
if (isPopOffTopOfStack) {
// Make sure to show correct top of stack
// since we popped the top screen off
mScreens.back()->Show();
// Make sure to notify the screen that just got moved off top
retVal->OnHide();
// In order to make sure the Transition can complete
// flag the removed screen to stay alive in case it falls
// out of scope and gets deleted quickly after this.
retVal->SetKeepAliveTime(mScreens.back()->GetTransitionTime() + 500);
}
}
return retVal;
}
bool Manager::distributeKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
// Send Key Even to top Screen for handling
return mScreens.back()->KeyEvent(aKeyEvent);
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "PageBase.hpp"
#include "ScreenBase.hpp"
#include <memory>
#include <vector>
namespace UI::Screen {
class Manager {
public:
static Manager &getInstance();
void pushScreen(Screen::Base::Ptr aScreen);
void pushScreen(Screen::Base::Ptr aScreen,
lv_scr_load_anim_t aPushAnimationOverride);
void
pushPopUp(UI::Page::Base::Ptr aPopUpPage,
lv_scr_load_anim_t aPushAnimation = LV_SCR_LOAD_ANIM_OVER_LEFT);
Screen::Base::Ptr popScreen(Screen::Base *aScreenToRemove);
bool distributeKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent);
private:
Manager();
static Manager mManager;
std::vector<Screen::Base::Ptr> mScreens;
};
} // namespace UI::Screen

View File

@ -0,0 +1,52 @@
#pragma once
#include "Colors.hpp"
#include <lvgl.h>
namespace UI {
class UIElement;
class TextStyle {
friend UIElement;
public:
TextStyle() = default;
TextStyle &Opacity(lv_opa_t aOpacity) {
opacity = aOpacity;
return *this;
}
TextStyle &Color(lv_color_t aColor) {
color = aColor;
return *this;
}
TextStyle &Decor(lv_text_decor_t aDecor) {
decor = aDecor;
return *this;
}
TextStyle &Align(lv_text_align_t aAlignment) {
align = aAlignment;
return *this;
}
TextStyle &LetterSpacing(lv_coord_t aLetterSpacing) {
letter_space = aLetterSpacing;
return *this;
}
TextStyle &LineSpacing(lv_coord_t aLineSpacing) {
line_space = aLineSpacing;
return *this;
}
TextStyle &Font(const lv_font_t *aFont) {
font = aFont;
return *this;
}
private:
lv_opa_t opacity = 255;
lv_color_t color = Color::WHITE;
lv_text_decor_t decor = LV_TEXT_DECOR_NONE;
lv_text_align_t align = LV_TEXT_ALIGN_CENTER;
lv_coord_t letter_space = 0;
lv_coord_t line_space = 0;
const lv_font_t *font = lv_font_default();
};
} // namespace UI

View File

@ -0,0 +1,333 @@
#include "UIElement.hpp"
#include "LvglResourceManager.hpp"
namespace UI {
UIElement::UIElement(lv_obj_t *aLvglSelf, ID aId)
: mLvglSelf(aLvglSelf), mId(aId) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
mLvglSelf->user_data = this;
// Register Handler so that all object are able to override OnLvglEvent to
// handle events easilyOnLvEvent
lv_obj_add_event_cb(mLvglSelf, UIElement::LvglEventHandler, LV_EVENT_ALL,
this);
}
UIElement::~UIElement() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
if (lv_obj_is_valid(LvglSelf())) {
if (mLvglKeepAliveTime > 0) {
lv_obj_del_delayed(LvglSelf(), mLvglKeepAliveTime);
} else {
lv_obj_del(LvglSelf());
}
lv_obj_remove_event_cb(mLvglSelf, UIElement::LvglEventHandler);
}
}
UIElement *UIElement::GetParent() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
if (auto parent = lv_obj_get_parent(mLvglSelf); parent) {
if (auto parentElem = parent->user_data; parentElem) {
return reinterpret_cast<UIElement *>(parentElem);
}
}
return nullptr;
}
UIElement *UIElement::AddElement(UIElement::Ptr anUIElement) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_set_parent(anUIElement->mLvglSelf, mLvglSelf);
anUIElement->OnAdded(this);
if (IsVisible() && anUIElement->IsSetVisible()) {
anUIElement->OnShow();
}
mContainedElements.push_back(std::move(anUIElement));
return mContainedElements[mContainedElements.size() - 1].get();
}
UIElement::Ptr UIElement::RemoveElement(UIElement *anElementRef) {
auto ElemToRemoveIter =
std::find_if(mContainedElements.begin(), mContainedElements.end(),
[anElementRef](auto &anElement) {
return anElement.get() == anElementRef;
});
if (ElemToRemoveIter != mContainedElements.end()) {
auto widget = std::move(*ElemToRemoveIter);
mContainedElements.erase(ElemToRemoveIter);
return widget;
}
return nullptr;
}
bool UIElement::KeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
if (OnKeyEvent(aKeyEvent)) {
return true;
}
for (auto &elem : mContainedElements) {
if (elem->KeyEvent(aKeyEvent)) {
return true;
}
}
return false;
};
bool UIElement::IsVisible() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return lv_obj_is_visible(mLvglSelf);
}
bool UIElement::IsSetVisible() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return !lv_obj_has_flag(mLvglSelf, LV_OBJ_FLAG_HIDDEN);
}
void UIElement::SetWidth(lv_coord_t aWidth) {
LvglResourceManager::GetInstance().AttemptNow(
[this, aWidth] { lv_obj_set_width(mLvglSelf, aWidth); });
}
void UIElement::SetHeight(lv_coord_t aHeight) {
LvglResourceManager::GetInstance().AttemptNow(
[this, aHeight] { lv_obj_set_height(mLvglSelf, aHeight); });
}
lv_coord_t UIElement::GetHeight() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_update_layout(mLvglSelf);
return lv_obj_get_height(mLvglSelf);
};
lv_coord_t UIElement::GetWidth() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_update_layout(mLvglSelf);
return lv_obj_get_width(mLvglSelf);
}
void UIElement::SetContentWidth(lv_coord_t aWidth) {
LvglResourceManager::GetInstance().AttemptNow(
[this, aWidth] { lv_obj_set_content_width(mLvglSelf, aWidth); });
}
void UIElement::SetContentHeight(lv_coord_t aHeight) {
LvglResourceManager::GetInstance().AttemptNow(
[this, aHeight] { lv_obj_set_content_height(mLvglSelf, aHeight); });
}
lv_coord_t UIElement::GetContentWidth() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_update_layout(mLvglSelf);
return lv_obj_get_content_width(mLvglSelf);
}
lv_coord_t UIElement::GetContentHeight() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_update_layout(mLvglSelf);
return lv_obj_get_content_height(mLvglSelf);
}
void UIElement::SetY(lv_coord_t aY) {
LvglResourceManager::GetInstance().AttemptNow(
[this, aY] { lv_obj_set_y(mLvglSelf, aY); });
};
void UIElement::SetX(lv_coord_t aX) {
LvglResourceManager::GetInstance().AttemptNow(
[this, aX] { lv_obj_set_x(mLvglSelf, aX); });
}
lv_coord_t UIElement::GetY() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_update_layout(mLvglSelf);
return lv_obj_get_y(mLvglSelf);
}
lv_coord_t UIElement::GetX() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_update_layout(mLvglSelf);
return lv_obj_get_x(mLvglSelf);
}
lv_coord_t UIElement::GetBottom() { return GetY() + GetHeight(); };
void UIElement::SetBorder(Border aNewBorder, lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aNewBorder, aStyle] {
lv_obj_set_style_border_color(mLvglSelf, aNewBorder.color, aStyle);
lv_obj_set_style_border_opa(mLvglSelf, aNewBorder.opacity, aStyle);
lv_obj_set_style_border_side(mLvglSelf, aNewBorder.sides, aStyle);
lv_obj_set_style_border_width(mLvglSelf, aNewBorder.width, aStyle);
});
}
Border UIElement::GetBorder(lv_style_selector_t aStyle) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return Border()
.Color(lv_obj_get_style_border_color(mLvglSelf, aStyle))
.Opacity(lv_obj_get_style_border_opa(mLvglSelf, aStyle))
.Sides(lv_obj_get_style_border_side(mLvglSelf, aStyle))
.Width(lv_obj_get_style_border_width(mLvglSelf, aStyle));
}
void UIElement::SetOutline(Outline aNewOutline, lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aNewOutline, aStyle] {
lv_obj_set_style_outline_color(mLvglSelf, aNewOutline.color, aStyle);
lv_obj_set_style_outline_opa(mLvglSelf, aNewOutline.opacity, aStyle);
lv_obj_set_style_outline_width(mLvglSelf, aNewOutline.width, aStyle);
lv_obj_set_style_outline_pad(mLvglSelf, aNewOutline.padding, aStyle);
});
};
Outline UIElement::GetOutline(lv_style_selector_t aStyle) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return Outline()
.Color(lv_obj_get_style_outline_color(mLvglSelf, aStyle))
.Opacity(lv_obj_get_style_outline_opa(mLvglSelf, aStyle))
.Padding(lv_obj_get_style_outline_pad(mLvglSelf, aStyle))
.Width(lv_obj_get_style_outline_width(mLvglSelf, aStyle));
};
void UIElement::SetPadding(Padding aNewPadding, lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aNewPadding, aStyle] {
lv_obj_set_style_pad_top(mLvglSelf, aNewPadding.top, aStyle);
lv_obj_set_style_pad_bottom(mLvglSelf, aNewPadding.bottom, aStyle);
lv_obj_set_style_pad_left(mLvglSelf, aNewPadding.left, aStyle);
lv_obj_set_style_pad_right(mLvglSelf, aNewPadding.right, aStyle);
lv_obj_set_style_pad_row(mLvglSelf, aNewPadding.row, aStyle);
lv_obj_set_style_pad_column(mLvglSelf, aNewPadding.column, aStyle);
});
};
void UIElement::SetAllPadding(lv_coord_t aNewPadding,
lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aNewPadding, aStyle] {
lv_obj_set_style_pad_all(mLvglSelf, aNewPadding, aStyle);
});
}
Padding UIElement::GetPadding(lv_style_selector_t aStyle) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return Padding()
.Top(lv_obj_get_style_pad_top(mLvglSelf, aStyle))
.Bottom(lv_obj_get_style_pad_bottom(mLvglSelf, aStyle))
.Left(lv_obj_get_style_pad_left(mLvglSelf, aStyle))
.Right(lv_obj_get_style_pad_right(mLvglSelf, aStyle))
.Row(lv_obj_get_style_pad_row(mLvglSelf, aStyle))
.Column(lv_obj_get_style_pad_column(mLvglSelf, aStyle));
};
void UIElement::SetTextStyle(TextStyle aNewTextStyle,
lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aNewTextStyle, aStyle] {
lv_obj_set_style_text_align(mLvglSelf, aNewTextStyle.align, aStyle);
lv_obj_set_style_text_color(mLvglSelf, aNewTextStyle.color, aStyle);
lv_obj_set_style_text_decor(mLvglSelf, aNewTextStyle.decor, aStyle);
lv_obj_set_style_text_font(mLvglSelf, aNewTextStyle.font, aStyle);
lv_obj_set_style_text_letter_space(mLvglSelf, aNewTextStyle.letter_space,
aStyle);
lv_obj_set_style_text_line_space(mLvglSelf, aNewTextStyle.line_space,
aStyle);
lv_obj_set_style_text_opa(mLvglSelf, aNewTextStyle.opacity, aStyle);
});
}
TextStyle UIElement::GetTextStyle(lv_style_selector_t aStyle) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return TextStyle()
.Align(lv_obj_get_style_text_align(mLvglSelf, aStyle))
.Color(lv_obj_get_style_text_color(mLvglSelf, aStyle))
.Decor(lv_obj_get_style_text_decor(mLvglSelf, aStyle))
.Font(lv_obj_get_style_text_font(mLvglSelf, aStyle))
.LetterSpacing(lv_obj_get_style_text_letter_space(mLvglSelf, aStyle))
.LineSpacing(lv_obj_get_style_text_line_space(mLvglSelf, aStyle))
.Opacity(lv_obj_get_style_text_opa(mLvglSelf, aStyle));
}
void UIElement::AlignTo(UIElement *anElementToAlignTo, lv_align_t anAlignment,
lv_coord_t aXoffset, lv_coord_t aYOffset) {
LvglResourceManager::GetInstance().AttemptNow([=] {
lv_obj_align_to(mLvglSelf, anElementToAlignTo->mLvglSelf, anAlignment,
aXoffset, aYOffset);
});
}
void UIElement::SetVisiblity(bool aVisible) {
if (aVisible == !lv_obj_has_flag(mLvglSelf, LV_OBJ_FLAG_HIDDEN)) {
return;
}
if (aVisible) {
Show();
} else {
Hide();
}
}
void UIElement::SetBgColor(lv_color_t aColor, lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aColor, aStyle] {
lv_obj_set_style_bg_color(mLvglSelf, aColor, aStyle);
});
};
void UIElement::SetBgOpacity(lv_opa_t aOpacity, lv_style_selector_t aStyle) {
LvglResourceManager::GetInstance().AttemptNow([this, aOpacity, aStyle] {
lv_obj_set_style_bg_opa(mLvglSelf, aOpacity, aStyle);
});
}
void UIElement::StartLvglEventHandler() {
if (mIsHandlingLvglEvents) {
return;
}
lv_obj_add_event_cb(mLvglSelf, UIElement::LvglEventHandler, LV_EVENT_ALL,
this);
mIsHandlingLvglEvents = true;
}
void UIElement::StopLvglEventHandler() {
if (!mIsHandlingLvglEvents) {
return;
}
lv_obj_remove_event_cb_with_user_data(mLvglSelf, UIElement::LvglEventHandler,
this);
mIsHandlingLvglEvents = false;
}
void UIElement::Show() {
if (IsSetVisible()) {
return;
}
{
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_clear_flag(mLvglSelf, LV_OBJ_FLAG_HIDDEN);
}
OnShow();
}
void UIElement::Hide() {
if (!IsSetVisible()) {
return;
}
{
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_obj_add_flag(mLvglSelf, LV_OBJ_FLAG_HIDDEN);
}
OnHide();
}
void UIElement::OnHide() {
for (auto &elem : mContainedElements) {
if (!IsSetVisible()) {
elem->OnHide();
}
}
}
void UIElement::OnShow() {
for (auto &elem : mContainedElements) {
if (IsSetVisible()) {
elem->OnShow();
}
}
}
//////////////////// Statics //////////////////////////
void UIElement::LvglEventHandler(lv_event_t *anEvent) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
reinterpret_cast<UIElement *>(anEvent->user_data)->OnLvglEvent(anEvent);
}
} // namespace UI

View File

@ -0,0 +1,176 @@
#pragma once
#include "BorderOutlinePadding.hpp"
#include "TextStyle.hpp"
#include "UIElementIds.hpp"
#include <lvgl.h>
#include "KeyPressAbstract.hpp"
#include <vector>
namespace UI {
class UIElement {
public:
using Ptr = std::unique_ptr<UIElement>;
UIElement(lv_obj_t *aLvglSelf, const ID aId = ID());
virtual ~UIElement();
virtual void SetBgColor(lv_color_t value,
lv_style_selector_t selector = LV_PART_MAIN);
virtual void SetBgOpacity(lv_opa_t aOpacity,
lv_style_selector_t aStyle = LV_PART_MAIN);
void SetVisiblity(bool aVisibility);
bool IsVisible();
bool IsSetVisible();
virtual void SetWidth(lv_coord_t aWidth);
virtual void SetHeight(lv_coord_t aHeight);
lv_coord_t GetWidth();
lv_coord_t GetHeight();
virtual void SetContentWidth(lv_coord_t aWidth);
virtual void SetContentHeight(lv_coord_t aHeight);
lv_coord_t GetContentWidth();
lv_coord_t GetContentHeight();
virtual void SetY(lv_coord_t aY);
virtual void SetX(lv_coord_t aX);
lv_coord_t GetY();
lv_coord_t GetX();
lv_coord_t GetBottom();
void AlignTo(UIElement *anElementToAlignWith, lv_align_t anAlignment,
lv_coord_t aXoffset = 0, lv_coord_t aYOffset = 0);
virtual void SetBorder(Border aNewBorder,
lv_style_selector_t aStyle = LV_PART_MAIN);
Border GetBorder(lv_style_selector_t aStyle = LV_PART_MAIN);
virtual void SetOutline(Outline aNewOutline,
lv_style_selector_t aStyle = LV_PART_MAIN);
Outline GetOutline(lv_style_selector_t aStyle = LV_PART_MAIN);
virtual void SetPadding(Padding aNewPadding,
lv_style_selector_t aStyle = LV_PART_MAIN);
virtual void SetAllPadding(lv_coord_t aNewPadding,
lv_style_selector_t aStyle = LV_PART_MAIN);
Padding GetPadding(lv_style_selector_t aStyle = LV_PART_MAIN);
virtual void SetTextStyle(TextStyle aNewStyle,
lv_style_selector_t aStyle = LV_PART_MAIN);
TextStyle GetTextStyle(lv_style_selector_t aStyle = LV_PART_MAIN);
UIElement *AddElement(UIElement::Ptr anElement);
template <class UIElemTy> UIElemTy *AddElement(UIElement::Ptr aWidget);
UIElement::Ptr RemoveElement(UIElement *aUIElementRef);
size_t GetNumContainedElements() { return mContainedElements.size(); }
ID GetID() { return mId; };
template <class UIElemTy> static UIElemTy GetElement(lv_obj_t *aLvglObject);
/// @brief There are use cases in which objects
/// need to stay alive in LVGL but can die
/// in terms of our usage this is a helper for these
/// use Sparingly!!!
/// @param aTimeToKeepLvglObj
void SetKeepAliveTime(uint32_t aTimeToKeepLvglObj) {
mLvglKeepAliveTime = aTimeToKeepLvglObj;
}
void StartLvglEventHandler();
void StopLvglEventHandler();
/// @brief Register a callback to run for Lvgl Events for objects that
/// are created from base classes.
void OnLvglEvent(std::function<void(lv_event_t *anEvent)> aLvglEventHandler) {
mLvglEventHandler = aLvglEventHandler;
}
/// @brief get Lvgl object reference to use in LVGL APIs
/// @return lvgl object a
lv_obj_t *LvglSelf() { return mLvglSelf; }
protected:
/// @brief Show Element
virtual void Show();
/// @brief Hide Element
virtual void Hide();
/// @brief Override in child class to run something after element is shown
virtual void OnShow();
/// @brief Override in child class to run something after element is hidden
virtual void OnHide();
/// @brief Override to run something when element is added to a parent
/// @param aNewParent - Parent UIElement just added to
virtual void OnAdded(UIElement *aNewParent){};
// Override in object to handle LVGL events for that object
virtual void OnLvglEvent(lv_event_t *anEvent) {
if (mLvglEventHandler) {
mLvglEventHandler(anEvent);
}
};
/// @brief Set KeyEvent to the UI element to see if it wants to handle it
virtual bool KeyEvent(KeyPressAbstract::KeyEvent aKeyEvent);
/// @brief Override to Handle KeyEvent for the specific element
/// @return true - Key event was used
/// fasle - Key event was unused
virtual bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) = 0;
private:
/// @brief Get Pointer to Parent Element
/// @return - nullptr Parent was not wrapped or did not exist
UIElement *GetParent();
static void LvglEventHandler(lv_event_t *anEvent);
lv_obj_t *mLvglSelf;
const ID mId;
uint32_t mLvglKeepAliveTime = 0;
bool mIsHandlingLvglEvents = true;
std::function<void(lv_event_t *)> mLvglEventHandler = nullptr;
/// @brief Elements that are currently in this element
std::vector<UIElement::Ptr> mContainedElements;
};
/**
* @brief This helper allows conversion between anLvglObject and a
* core element by using the user data that links the LVGL object
* to its C++ counterpart. Do note that it is possible that this
* user data is not always there if the Lvgl Object has not been wrapped
* by UIElement
*
* @tparam UIElemTy - Type of element you want to cast to
* @param aLvglObject - object to extract User data from
* @return UIElemTy - object stored in user data (See constructor of
* UIElement)
*/
template <class UIElemTy>
UIElemTy UIElement::GetElement(lv_obj_t *aLvglObject) {
auto UIElement = lv_obj_get_user_data(aLvglObject);
if (UIElement) {
return static_cast<UIElemTy>(UIElement);
}
return nullptr;
}
template <class UIElemTy>
UIElemTy *UIElement::AddElement(UIElement::Ptr anElement) {
return static_cast<UIElemTy *>(AddElement(std::move(anElement)));
}
} // namespace UI

View File

@ -0,0 +1,45 @@
#pragma once
namespace UI {
class ID {
public:
static constexpr auto INVALID = 0;
enum class Screens {
Background = static_cast<int>(INVALID) + 1,
Home,
PopUp,
INVALID_SCREEN_ID
};
enum class Widgets {
Slider = static_cast<int>(Screens::INVALID_SCREEN_ID) + 1,
Button,
Label,
List,
DropDown,
Keyboard,
BrightnessSlider,
INVALID_WIDGET_ID
};
enum class Pages {
Settings = static_cast<int>(Widgets::INVALID_WIDGET_ID) + 1,
DisplaySettings,
WifiSettings,
SystemSettings,
Demo,
INVALID_PAGE_ID
};
ID() : mId(INVALID){};
ID(ID::Screens aScreenId) : mId(static_cast<int>(aScreenId)){};
ID(ID::Widgets aWidgetId) : mId(static_cast<int>(aWidgetId)){};
ID(ID::Pages aPageId) : mId(static_cast<int>(aPageId)){};
private:
const int mId;
};
} // namespace UI

View File

@ -0,0 +1,13 @@
#include "PageBase.hpp"
#include "BackgroundScreen.hpp"
using namespace UI::Page;
Base::Base(ID aID)
: Base(lv_obj_create(Screen::BackgroundScreen::getLvInstance()), aID) {}
Base::Base(lv_obj_t *aLvglSelf, ID aID) : UIElement(aLvglSelf, aID) {
SetHeight(lv_pct(100));
SetWidth(lv_pct(100));
SetPadding(Padding()); // Set Padding to default
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "UIElement.hpp"
#include <string>
#include <vector>
namespace UI::Screen {
class PopUpScreen;
}
namespace UI::Page {
class Tab;
class TabView;
class Base : public UIElement {
// Classes that Own Pages
friend Tab; // Allow Tab to Forward all Key Events to its page
friend TabView; // Allow Tab view to call OnShow and OnHide Since it can show
// and Hide pages by swiping
friend UI::Screen::PopUpScreen; // Allow Pop up Screens pass events to the
// page it owns
public:
typedef std::unique_ptr<Base> Ptr;
Base(ID aID);
Base(lv_obj_t *aLvglSelf, ID aID);
virtual ~Base() = default;
// Override to have a title associated with your page.
virtual std::string GetTitle() { return ""; };
protected:
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) { return false; };
private:
};
} // namespace UI::Page

View File

@ -0,0 +1,62 @@
#include "TabView.hpp"
#include "BackgroundScreen.hpp"
#include <string>
using namespace UI::Page;
Tab::Tab(lv_obj_t *aTab, Base::Ptr aContent) :
Base(aTab, aContent->GetID()),
mContent(AddElement<Base>(std::move(aContent))) {}
/////////////////////TabView/////////////////////////////////////
TabView::TabView(ID aId)
: Base(lv_tabview_create(Screen::BackgroundScreen::getLvInstance(),
LV_DIR_TOP, 0),
aId) {}
void TabView::AddTab(Page::Base::Ptr aPage) {
auto tab = std::make_unique<Tab>(
lv_tabview_add_tab(LvglSelf(), aPage->GetTitle().c_str()), std::move(aPage));
mTabs.push_back(std::move(tab));
}
uint16_t TabView::GetCurrentTabIdx() {
return lv_tabview_get_tab_act(LvglSelf());
}
void TabView::SetCurrentTabIdx(uint16_t aTabToSetActive,
lv_anim_enable_t aIsDoAnimation) {
lv_tabview_set_act(LvglSelf(), aTabToSetActive, aIsDoAnimation);
}
void TabView::HandleTabChange() {
// Call OnShow() for the page we just swapped to in order to
// Notify the page that it is now showing and the other that the are now
// hidden
for (int i = 0; i < mTabs.size(); i++) {
if (GetCurrentTabIdx() == i) {
mTabs[i]->OnShow();
} else {
mTabs[i]->OnHide();
}
}
}
bool TabView::KeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
if (OnKeyEvent(aKeyEvent)) {
return true;
}
return mTabs[GetCurrentTabIdx()]->KeyEvent(aKeyEvent);
};
void TabView::OnLvglEvent(lv_event_t *anEvent) {
if (anEvent->code == LV_EVENT_VALUE_CHANGED) {
HandleTabChange();
}
}
void TabView::OnShow() { mTabs[GetCurrentTabIdx()]->OnShow(); }
void TabView::OnHide() { mTabs[GetCurrentTabIdx()]->OnHide(); }

View File

@ -0,0 +1,45 @@
#include "PageBase.hpp"
#include <functional>
namespace UI::Page {
class TabView;
class Tab : public Base {
friend TabView;
public:
typedef std::unique_ptr<Tab> Ptr;
Tab(lv_obj_t *aTab, Base::Ptr aContent);
void OnShow() override{mContent->OnShow();};
void OnHide() override{mContent->OnHide();};
private:
Base* mContent;
};
class TabView : public Base {
public:
TabView(ID aId);
void AddTab(Page::Base::Ptr aPage);
uint16_t GetCurrentTabIdx();
void SetCurrentTabIdx(uint16_t aTabToSetActive,
lv_anim_enable_t aIsDoAnimation = LV_ANIM_ON);
bool KeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override;
void OnShow() override;
void OnHide() override;
protected:
void OnLvglEvent(lv_event_t *anEvent) override;
private:
void HandleTabChange();
std::vector<Page::Tab::Ptr> mTabs;
};
} // namespace UI::Page

View File

@ -0,0 +1,28 @@
#include "poller.hpp"
#include <functional>
#include <memory>
using namespace std::chrono;
using namespace UI;
poller::poller(std::function<void()> aOnPollCb, milliseconds aPollTime)
: mIntermittentCallback(std::move(aOnPollCb)) {
mTimer = lv_timer_create(poller::onPoll, aPollTime.count(), this);
lv_timer_set_repeat_count(mTimer, -1); // Call forever
}
poller::~poller() {
if (mTimer) {
lv_timer_del(mTimer);
mTimer = nullptr;
}
}
void poller::onPoll(_lv_timer_t *aTimer) {
poller *currentPoller = reinterpret_cast<poller *>(aTimer->user_data);
if (currentPoller->mIntermittentCallback) {
currentPoller->mIntermittentCallback();
}
}

View File

@ -0,0 +1,29 @@
#include "lvgl.h"
#include <chrono>
#include <functional>
#include <memory>
namespace UI {
class poller {
public:
poller(std::function<void()> aOnPollCb,
std::chrono::milliseconds pollTime = std::chrono::seconds(5));
virtual ~poller();
void setPollPeriod(std::chrono::milliseconds aPollPeriod) {
lv_timer_set_period(mTimer, aPollPeriod.count());
}
inline void pause() { lv_timer_pause(mTimer); }
inline void resume() { lv_timer_resume(mTimer); }
inline void reset() { lv_timer_reset(mTimer); }
inline void runNext() { lv_timer_ready(mTimer); }
private:
lv_timer_t *mTimer = nullptr;
std::function<void()> mIntermittentCallback = nullptr;
// Static function registered to every timers callback to pass this object as
// context
static void onPoll(_lv_timer_t *aTimer);
};
} // namespace UI

View File

@ -0,0 +1,18 @@
#include "BackgroundScreen.hpp"
using namespace UI::Screen;
BackgroundScreen *BackgroundScreen::mInstance = nullptr;
BackgroundScreen *BackgroundScreen::GetInstance() {
if (!mInstance) {
mInstance = new BackgroundScreen();
}
return mInstance;
};
lv_obj_t *BackgroundScreen::getLvInstance() {
return GetInstance()->LvglSelf();
}
BackgroundScreen::BackgroundScreen() : Base(ID::Screens::Background) {}

View File

@ -0,0 +1,16 @@
#pragma once
#include "ScreenBase.hpp"
namespace UI::Screen {
/// @brief Due to the way LVGL utilizes screens we basically need a canvas to
/// create new pages on
/// by adding things to this screen and then moving them to their own page.
class BackgroundScreen : Base {
public:
static BackgroundScreen *GetInstance();
static lv_obj_t *getLvInstance();
private:
BackgroundScreen();
static BackgroundScreen *mInstance;
};
} // namespace UI::Screen

View File

@ -0,0 +1,35 @@
#include "PopUpScreen.hpp"
#include "Colors.hpp"
#include "ScreenManager.hpp"
using namespace UI;
using namespace UI::Screen;
PopUpScreen::PopUpScreen(Page::Base::Ptr aPage)
: Screen::Base(UI::ID::Screens::PopUp) {
mContentPage = AddElement<Page::Base>(std::move(aPage));
mExitButton = AddElement<Widget::Button>(std::make_unique<Widget::Button>(
[this] { UI::Screen::Manager::getInstance().popScreen(this); }));
mTitle = AddElement<Widget::Label>(
std::make_unique<Widget::Label>(mContentPage->GetTitle()));
mExitButton->SetWidth(lv_pct(10));
mExitButton->SetHeight(mExitButton->GetWidth());
mExitButton->SetBgColor(Color::RED);
mExitButton->AlignTo(this, LV_ALIGN_TOP_RIGHT, -5, 5);
mTitle->SetWidth(mExitButton->GetX());
mTitle->SetHeight(mExitButton->GetHeight());
mTitle->AlignTo(mExitButton, LV_ALIGN_OUT_LEFT_BOTTOM);
mTitle->SetTextStyle(mTitle->GetTextStyle().Align(LV_TEXT_ALIGN_CENTER));
mContentPage->SetHeight(GetHeight() - mExitButton->GetBottom() - 5);
mContentPage->SetY(mExitButton->GetBottom() + 5);
}
bool PopUpScreen::OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) {
return mContentPage->OnKeyEvent(aKeyEvent);
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "Button.hpp"
#include "Label.hpp"
#include "PageBase.hpp"
#include "ScreenBase.hpp"
namespace UI::Screen {
/// @brief A Screen that allows easy display of a page that
/// can be dismissed easily by an x
class PopUpScreen : public Base {
public:
PopUpScreen(UI::Page::Base::Ptr aPage);
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override;
private:
UI::Page::Base *mContentPage = nullptr;
Widget::Button *mExitButton = nullptr;
Widget::Label *mTitle = nullptr;
};
} // namespace UI::Screen

View File

@ -0,0 +1,26 @@
#include "ScreenBase.hpp"
using namespace UI::Screen;
Base::Base(ID aId) : UIElement(lv_obj_create(NULL), aId) {}
void Base::Show() {
lv_scr_load_anim(LvglSelf(), mPushAnimation, mTransitionAnimationTime,
mTransitionDelayTime, false);
UIElement::OnShow();
}
void Base::SetPushAnimation(lv_scr_load_anim_t aShowAnimation) {
mPushAnimation = aShowAnimation;
}
uint32_t Base::GetTransitionTime() {
return mTransitionAnimationTime + mTransitionDelayTime;
}
void Base::SetTransitionTimes(uint32_t aAnimationTime, uint32_t aDelay) {
mTransitionAnimationTime = aAnimationTime;
mTransitionDelayTime = aDelay;
}
bool Base::OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) { return false; };

View File

@ -0,0 +1,35 @@
#pragma once
#include "lvgl.h"
#include "UIElement.hpp"
#include <memory>
namespace UI::Screen {
class Manager;
class Base : public UIElement {
friend Manager;
public:
typedef std::unique_ptr<Base> Ptr;
Base(ID aId);
void SetPushAnimation(lv_scr_load_anim_t aPushAnimation);
uint32_t GetTransitionTime();
void SetTransitionTimes(uint32_t aAnimationTime, uint32_t aDelay = 0);
protected:
void Show() override;
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override;
private:
lv_scr_load_anim_t mPushAnimation = LV_SCR_LOAD_ANIM_NONE;
uint32_t mTransitionAnimationTime = 1000; // 1000 ms / 1 sec
uint32_t mTransitionDelayTime = 0;
};
} // namespace UI::Screen

View File

@ -0,0 +1,14 @@
#include "Button.hpp"
#include "BackgroundScreen.hpp"
using namespace UI::Widget;
Button::Button(std::function<void()> aOnPressHandler)
: Base(lv_btn_create(UI::Screen::BackgroundScreen::getLvInstance()),ID::Widgets::Button),
mOnPress(aOnPressHandler) {}
void Button::OnLvglEvent(lv_event_t *anEvent) {
if (anEvent->code == LV_EVENT_PRESSED) {
mOnPress();
}
};

View File

@ -0,0 +1,15 @@
#pragma once
#include "WidgetBase.hpp"
namespace UI::Widget {
class Button : public Base {
public:
Button(std::function<void()> aOnPressHandler);
void OnLvglEvent(lv_event_t *anEvent) override;
private:
std::function<void()> mOnPress;
};
} // namespace UI::Widget

View File

@ -0,0 +1,47 @@
#pragma once
#include "BackgroundScreen.hpp"
#include "WidgetBase.hpp"
#include <functional>
#include <string>
namespace UI::Widget {
template <typename T> class DropDown : public Base {
public:
DropDown(std::function<void(T)> aOnItemSelected)
: Base(lv_dropdown_create(UI::Screen::BackgroundScreen::getLvInstance()),
ID::Widgets::DropDown),
mSelectionHandler(aOnItemSelected) {
lv_dropdown_clear_options(LvglSelf());
}
void AddItem(std::string aOptionTitle, T aOptionData) {
lv_dropdown_add_option(LvglSelf(), aOptionTitle.c_str(),
LV_DROPDOWN_POS_LAST);
mOptionsData.push_back(aOptionData);
}
void SetSelected(T aOptionData) {
for (int i = 0; i < mOptionsData.size(); i++) {
if (mOptionsData[i] == aOptionData) {
lv_dropdown_set_selected(LvglSelf(), i);
}
}
}
// TODO Could Implement a remove Item but need to make sure
// correct order is retained in data vector.
protected:
void OnLvglEvent(lv_event_t *anEvent) override {
if (anEvent->code == LV_EVENT_VALUE_CHANGED) {
auto idx = lv_dropdown_get_selected(LvglSelf());
mSelectionHandler(mOptionsData[idx]);
}
};
private:
std::function<void(T)> mSelectionHandler;
std::vector<T> mOptionsData;
};
} // namespace UI::Widget

View File

@ -0,0 +1,57 @@
#include "Keyboard.hpp"
#include "BackgroundScreen.hpp"
#include <cmath>
using namespace UI;
using namespace UI::Widget;
Keyboard::Keyboard(std::function<void(std::string)> aOnUserCompletedTextEntry,
std::string aPrompt)
: Base(ID::Widgets::Keyboard),
mKeyboard(AddElement<Base>(std::make_unique<Base>(
lv_keyboard_create(LvglSelf()), ID::Widgets::INVALID_WIDGET_ID))),
mTextArea(AddElement<Base>(std::make_unique<Base>(
lv_textarea_create(LvglSelf()), ID::Widgets::INVALID_WIDGET_ID))),
mOnUserCompleteTextEntry(aOnUserCompletedTextEntry) {
lv_keyboard_set_textarea(mKeyboard->LvglSelf(), mTextArea->LvglSelf());
if (!aPrompt.empty()) {
lv_textarea_set_placeholder_text(mTextArea->LvglSelf(), aPrompt.c_str());
}
mKeyboard->OnLvglEvent([this](auto aEvent) {
if (aEvent->code == LV_EVENT_READY) {
std::string userEnteredText =
std::string(lv_textarea_get_text(mTextArea->LvglSelf()));
mOnUserCompleteTextEntry(userEnteredText);
}
});
}
void Keyboard::OnAdded(UIElement *aNewParent) {
auto selfHeight = ceil(aNewParent->GetContentHeight() * 0.60f);
// Align to final position and get Y for end of animation
SetHeight(selfHeight);
AlignTo(aNewParent, LV_ALIGN_BOTTOM_MID);
auto endAnimationY = GetY();
auto startAnimationY = aNewParent->GetBottom();
mAnimateIn = std::make_unique<Animation>([this](auto aY) { SetY(aY); }, 500,
startAnimationY, endAnimationY);
mAnimateIn->Start();
}
void Keyboard::AnimateOut() {
if (mOnKeyboardAnimatedOut) {
mAnimateIn->HandleAnimationComplete(mOnKeyboardAnimatedOut);
}
mAnimateIn->Reverse();
};
void Keyboard::SetHeight(lv_coord_t aHeight) {
Base::SetHeight(aHeight);
auto txtAreaHight = 33;
mTextArea->SetHeight(txtAreaHight);
mKeyboard->SetHeight(GetContentHeight() - txtAreaHight);
mTextArea->AlignTo(this, LV_ALIGN_TOP_MID);
mKeyboard->AlignTo(mTextArea, LV_ALIGN_OUT_BOTTOM_MID);
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "Animation.hpp"
#include "WidgetBase.hpp"
#include <memory>
#include <string>
namespace UI::Widget {
class Keyboard : public Base {
public:
Keyboard(std::function<void(std::string)> aOnUserCompletedTextEntry,
std::string aPrompt = "");
void OnAdded(UIElement *aNewParent) override;
void SetHeight(lv_coord_t aHeight) override;
void AnimateOut();
void OnKeyboardAnimatedOut(std::function<void()> aOnKeyboardAnimatedOut) {
mOnKeyboardAnimatedOut = aOnKeyboardAnimatedOut;
}
private:
std::function<void()> mOnKeyboardAnimatedOut;
std::function<void(std::string)> mOnUserCompleteTextEntry;
std::unique_ptr<Animation> mAnimateIn;
Base *mKeyboard;
Base *mTextArea;
};
} // namespace UI::Widget

View File

@ -0,0 +1,22 @@
#include "Label.hpp"
#include "BackgroundScreen.hpp"
#include "Colors.hpp"
#include "LvglResourceManager.hpp"
using namespace UI::Widget;
Label::Label(std::string aText)
: Base(lv_label_create(UI::Screen::BackgroundScreen::getLvInstance()),
ID::Widgets::Label) {
SetText(aText);
}
void Label::SetText(std::string aText) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_label_set_text(LvglSelf(), aText.c_str());
}
void Label::SetLongMode(lv_label_long_mode_t aLongMode) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_label_set_long_mode(LvglSelf(), aLongMode);
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "WidgetBase.hpp"
#include <string>
namespace UI::Widget {
class Label : public Base {
public:
Label(std::string aText);
void SetText(std::string aText);
void SetLongMode(lv_label_long_mode_t aLongMode);
};
} // namespace UI::Widget

View File

@ -0,0 +1,34 @@
#include "List.hpp"
#include "BackgroundScreen.hpp"
#include "LvglResourceManager.hpp"
using namespace UI;
using namespace UI::Widget;
ListItem::ListItem(lv_obj_t *aListItem, std::function<void()> onItemSelected)
: UIElement(aListItem, ID()), mSelectedHandler(std::move(onItemSelected)) {}
void ListItem::OnLvglEvent(lv_event_t *anEvent) {
if (anEvent->code == LV_EVENT_CLICKED) {
if (mSelectedHandler) {
mSelectedHandler();
}
}
}
List::List()
: Base(lv_list_create(Screen::BackgroundScreen::getLvInstance()),
ID::Widgets::List) {
StopLvglEventHandler();
}
void List::AddItem(std::string aTitle, const char *aSymbol,
std::function<void()> onItemSelected) {
lv_obj_t *lvListItem = nullptr;
{
auto lock = LvglResourceManager::GetInstance().scopeLock();
lvListItem = lv_list_add_btn(LvglSelf(), aSymbol, aTitle.c_str());
}
mListItems.push_back(
std::make_unique<ListItem>(lvListItem, std::move(onItemSelected)));
mListItems.back()->SetHeight(lv_pct(20));
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "WidgetBase.hpp"
#include <string>
namespace UI::Widget {
class ListItem : public UIElement {
public:
ListItem(lv_obj_t *aListItem, std::function<void()> onItemSelected);
protected:
void OnLvglEvent(lv_event_t *anEvent) override;
bool OnKeyEvent(KeyPressAbstract::KeyEvent anEvent) override {
return false;
};
private:
std::function<void()> mSelectedHandler;
};
class List : public Base {
public:
List();
void AddItem(std::string aTitle, const char *aSymbol,
std::function<void()> onItemSelected);
protected:
private:
std::vector<UIElement::Ptr> mListItems;
};
} // namespace UI::Widget

View File

@ -0,0 +1,31 @@
#include "Slider.hpp"
#include "BackgroundScreen.hpp"
#include "LvglResourceManager.hpp"
using namespace UI::Widget;
Slider::Slider(std::function<void(int32_t)> aOnSliderValueChange,
int32_t aMinVal, int32_t aMaxVal)
: Base(lv_slider_create(UI::Screen::BackgroundScreen::getLvInstance()),
ID::Widgets::Slider),
mOnSliderChange(std::move(aOnSliderValueChange)) {
auto lock = LvglResourceManager::GetInstance().scopeLock();
lv_slider_set_range(LvglSelf(), aMinVal, aMaxVal);
}
int32_t Slider::GetValue() {
auto lock = LvglResourceManager::GetInstance().scopeLock();
return lv_slider_get_value(LvglSelf());
}
void Slider::SetValue(int32_t aValue, lv_anim_enable_t aIsAnimate) {
LvglResourceManager::GetInstance().AttemptNow([this, aValue, aIsAnimate] {
lv_slider_set_value(LvglSelf(), aValue, aIsAnimate);
});
}
void Slider::OnLvglEvent(lv_event_t *anEvent) {
if (anEvent->code == LV_EVENT_VALUE_CHANGED) {
mOnSliderChange(GetValue());
}
}

View File

@ -0,0 +1,19 @@
#include "WidgetBase.hpp"
namespace UI::Widget {
class Slider : public Base {
public:
Slider(std::function<void(int32_t)> OnSliderValueChange, int32_t aMinVal = 0,
int32_t aMaxVal = 100);
int32_t GetValue();
void SetValue(int32_t aValue, lv_anim_enable_t aIsAnimate = LV_ANIM_ON);
protected:
void OnLvglEvent(lv_event_t *anEvent) override;
private:
std::function<void(int32_t)> mOnSliderChange;
};
} // namespace UI::Widget

View File

@ -0,0 +1,23 @@
#include "WidgetBase.hpp"
#include "BackgroundScreen.hpp"
using namespace UI;
using namespace UI::Widget;
// Constructor typically used as a container for other widgets
Base::Base(ID anId)
: UIElement(lv_obj_create(Screen::BackgroundScreen::getLvInstance()),
anId) {
SetWidth(lv_pct(100));
SetHeight(lv_pct(100));
SetPadding(Padding());
SetOutline(Outline());
SetBorder(Border());
SetBgOpacity(LV_OPA_TRANSP);
}
Base::Base(lv_obj_t *aLvglSelf, ID anId) : UIElement(aLvglSelf, anId) {
SetWidth(lv_pct(100));
SetHeight(lv_pct(100));
SetBorder(Border());
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "UIElement.hpp"
#include <memory>
namespace UI {
namespace Screen {
class PopUpScreen;
}
} // namespace UI
namespace UI::Widget {
class Base : public UIElement {
// Classes that Own Widgets
friend class UI::Screen::PopUpScreen;
public:
typedef std::unique_ptr<Base> Ptr;
Base(ID anId);
Base(lv_obj_t *aLvglSelf, ID anId);
virtual ~Base() override = default;
protected:
bool OnKeyEvent(KeyPressAbstract::KeyEvent aKeyEvent) override {
return false;
};
private:
};
} // namespace UI::Widget

View File

@ -1,26 +0,0 @@
#include "poller.hpp"
#include <functional>
#include <memory>
using namespace std::chrono;
poller::poller(std::function<void()> aOnPollCb, milliseconds aPollTime):mIntermittentCallback(std::move(aOnPollCb)){
mTimer = lv_timer_create(poller::onPoll,aPollTime.count(),this);
lv_timer_set_repeat_count(mTimer,-1); // Call forever
}
poller::~poller(){
if(mTimer){
lv_timer_del(mTimer);
mTimer = nullptr;
}
}
void poller::onPoll(_lv_timer_t* aTimer){
poller* currentPoller = reinterpret_cast<poller*>(aTimer->user_data);
if(currentPoller->mIntermittentCallback){
currentPoller->mIntermittentCallback();
}
}

View File

@ -1,23 +0,0 @@
#include <chrono>
#include <memory>
#include <functional>
#include "lvgl.h"
class poller{
public:
poller(std::function<void()> aOnPollCb, std::chrono::milliseconds pollTime = std::chrono::seconds(5));
virtual ~poller();
void setPollPeriod(std::chrono::milliseconds aPollPeriod){ lv_timer_set_period(mTimer, aPollPeriod.count());}
inline void pause() { lv_timer_pause(mTimer);}
inline void resume() { lv_timer_resume(mTimer);}
inline void reset() { lv_timer_reset(mTimer);}
inline void runNext() { lv_timer_ready(mTimer);}
private:
lv_timer_t* mTimer = nullptr;
std::function<void()> mIntermittentCallback = nullptr;
// Static function registered to every timers callback to pass this object as context
static void onPoll(_lv_timer_t* aTimer);
};

View File

@ -22,7 +22,7 @@ build_flags =
-D LV_CONF_SKIP
;------------- LVGL ------------------------------------------
-D LV_MEM_CUSTOM=1
-D LV_MEM_SIZE="\(48U * 1024U\)"
-D LV_MEM_SIZE="(48U * 1024U)"
-D LV_FONT_MONTSERRAT_12=1
-D LV_FONT_MONTSERRAT_16=1
-D LV_FONT_MONTSERRAT_24=1
@ -39,6 +39,16 @@ build_flags =
; ------------- Includes ------------------------------------
-I OmoteUI
-I OmoteUI/core
-I OmoteUI/core/screen
-I OmoteUI/core/widget
-I OmoteUI/core/page
-I OmoteUI/UIs
;-I OmoteUI/UIs/Basic
-I OmoteUI/UIs/BasicRefactored
-I OmoteUI/UIs/BasicRefactored/screen
-I OmoteUI/UIs/BasicRefactored/page
-I OmoteUI/UIs/BasicRefactored/widget
-I HAL
-I HAL/HardwareModules
@ -49,7 +59,8 @@ lib_deps =
lib_archive = false
build_src_filter =
+<../OmoteUI/*>
+<../HAL/HardwareAbstract.cpp>
-<../OmoteUI/UIs/Basic/*>
+<../HAL/*.cpp>
+<../HAL/HardwareModules/*.cpp>
@ -77,6 +88,7 @@ lib_deps =
Preferences
build_flags =
${env.build_flags}
-D OMOTE_ESP32
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
@ -97,12 +109,15 @@ build_flags =
-D LV_TICK_CUSTOM=1
-D LV_TICK_CUSTOM_INCLUDE="\"Arduino.h\""
-D LV_TICK_CUSTOM_SYS_TIME_EXPR="'(millis())'"
; ------------- Includes --------------------------------------------
-I HAL/Targets/ESP32
-I HAL/Targets/ESP32/battery
-I HAL/Targets/ESP32/display
-I HAL/Targets/ESP32/wifiHandler
-I HAL/Targets/ESP32/keys
monitor_filters = esp32_exception_decoder
build_unflags =
-std=gnu++11
@ -116,15 +131,16 @@ build_src_filter =
platform = native@^1.1.3
build_flags =
${env.build_flags}
-D OMOTE_SIM
;-D LV_LOG_LEVEL=LV_LOG_LEVEL_INFO
;-D LV_LOG_PRINTF=1
-lSDL2 ; SDL2 must be installed on system! Windows:msys2 ubuntu:apt-get
-lpthread ; std::thread for Linux
; --------- SDL drivers options -----------------------------
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_DRV_NO_CONF
-D USE_SDL
-D SDL_MAIN_HANDLED
-D SDL_MAIN_HANDLED
-D SDL_HOR_RES=SCREEN_WIDTH
-D SDL_VER_RES=SCREEN_HEIGHT
-D SDL_ZOOM=1
@ -136,6 +152,7 @@ build_flags =
debug_build_flags =
-g ;Allow debugging in vscode
-O0 ;No Optomizations
lib_deps =
${env.lib_deps}

View File

@ -0,0 +1,18 @@
#include "BasicUI.hpp"
#include "HardwareFactory.hpp"
namespace OMOTE {
std::shared_ptr<UI::UIBase> ui = nullptr;
void setup() {
HardwareFactory::getAbstract().init();
ui = std::make_unique<UI::BasicUI>();
lv_timer_handler(); // Run the LVGL UI once before the loop takes over
}
void loop() {
HardwareFactory::getAbstract().loopHandler();
ui->loopHandler();
}
} // namespace OMOTE

View File

@ -1,24 +1,6 @@
// OMOTE firmware for ESP32
// 2023 Maximilian Kern
#include <lvgl.h>
#include "HardwareRevX.hpp"
#include "OmoteUI.hpp"
#include "omoteconfig.h"
std::shared_ptr<HardwareRevX> hal = nullptr;
void setup() {
hal = HardwareRevX::getInstance();
hal->init();
auto ui = OmoteUI::getInstance(hal);
ui->layout_UI();
lv_timer_handler(); // Run the LVGL UI once before the loop takes over
}
void loop() {
HardwareRevX::getInstance()->loopHandler();
OmoteUI::getInstance()->loopHandler();
}
#include "OmoteSetup.hpp"
void setup() { OMOTE::setup(); }
void loop() { OMOTE::loop(); }

View File

@ -1,17 +1,8 @@
#include "HardwareSimulator.hpp"
#include "omoteconfig.h"
#include "OmoteUI.hpp"
#include <memory>
#include "OmoteSetup.hpp"
int main(){
auto hwSim = std::make_shared<HardwareSimulator>();
hwSim->init();
auto ui = OmoteUI::getInstance(hwSim);
ui->layout_UI();
while (true){
ui->loopHandler();
lv_task_handler();
}
int main() {
OMOTE::setup();
while (true) {
OMOTE::loop();
}
}