Refine and implement hardware interface (#37)

This commit is contained in:
Matthew Colvin 2023-09-13 23:34:04 -05:00 committed by GitHub
parent 7a9ee138db
commit 6a78c4cfa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 3532 additions and 1627 deletions

View File

@ -1029,7 +1029,7 @@
(property "ki_keywords" "cap capacitor") (property "ki_keywords" "cap capacitor")
(path "/5d818a81-71ff-4f40-8854-04efda688b48") (path "/5d818a81-71ff-4f40-8854-04efda688b48")
(attr smd) (attr smd)
(fp_text reference "C13" (at -3.25 -1.68) (layer "F.SilkS") (fp_text reference "C13" (at -3.238895 0.068182) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 15439bba-04d4-4528-ad0e-855324eb39a5) (tstamp 15439bba-04d4-4528-ad0e-855324eb39a5)
) )
@ -6445,7 +6445,7 @@
(property "ki_keywords" "cap capacitor") (property "ki_keywords" "cap capacitor")
(path "/a888a00f-5496-4d09-af9f-455dba2d8767") (path "/a888a00f-5496-4d09-af9f-455dba2d8767")
(attr smd) (attr smd)
(fp_text reference "C14" (at 3.5 -1.85) (layer "F.SilkS") (fp_text reference "C14" (at 3.488895 -0.14998) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 5ecdc16d-4df0-4830-8429-f936a2864918) (tstamp 5ecdc16d-4df0-4830-8429-f936a2864918)
) )

Binary file not shown.

View File

@ -1,6 +1,7 @@
{ {
"cmake.configureOnOpen": false, "cmake.configureOnOpen": false,
"files.associations": { "files.associations": {
"*.json": "jsonc",
"random": "cpp", "random": "cpp",
"array": "cpp", "array": "cpp",
"atomic": "cpp", "atomic": "cpp",
@ -50,7 +51,34 @@
"stdexcept": "cpp", "stdexcept": "cpp",
"streambuf": "cpp", "streambuf": "cpp",
"cinttypes": "cpp", "cinttypes": "cpp",
"typeinfo": "cpp" "typeinfo": "cpp",
"bit": "cpp",
"compare": "cpp",
"concepts": "cpp",
"numbers": "cpp",
"any": "cpp",
"hash_map": "cpp",
"strstream": "cpp",
"bitset": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"complex": "cpp",
"condition_variable": "cpp",
"forward_list": "cpp",
"list": "cpp",
"ratio": "cpp",
"format": "cpp",
"future": "cpp",
"mutex": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"stop_token": "cpp",
"thread": "cpp",
"cfenv": "cpp",
"typeindex": "cpp",
"valarray": "cpp",
"variant": "cpp"
}, },
"cmake.sourceDirectory": "${workspaceFolder}/.pio/libdeps/esp32/Adafruit BusIO", "cmake.sourceDirectory": "${workspaceFolder}/.pio/libdeps/esp32/Adafruit BusIO",
"editor.formatOnSave": false, "editor.formatOnSave": false,

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,40 @@
@startuml
' KEY
' --> : is a
' *-- : must have
' o-- : should have
namespace HAL{
interface BatteryInterface
interface WifiInterface
interface OtherHWInterface
abstract HardwareAbstract
HardwareAbstract o-- BatteryInterface
HardwareAbstract o-- WifiInterface
HardwareAbstract o-- OtherHWInterface
}
namespace Simulator{
class BatterySimulator
class WifiSimulator
BatterySimulator --> HAL.BatteryInterface
WifiSimulator --> HAL.WifiInterface
}
namespace ESP32{
class Battery
class WifiHandler
Battery --> HAL.BatteryInterface
WifiHandler --> HAL.WifiInterface
}
namespace UI {
class OmoteUI
OmoteUI *-- HAL.HardwareAbstract
}
@enduml

View File

@ -0,0 +1,5 @@
#include "HardwareAbstract.hpp"
HardwareAbstract::HardwareAbstract(){
}

View File

@ -0,0 +1,30 @@
// OMOTE Hardware Abstraction
// 2023 Matthew Colvin
#pragma once
#include "BatteryInterface.h"
#include "DisplayAbstract.h"
#include "wifiHandlerInterface.h"
#include "Notification.hpp"
#include <memory>
class HardwareAbstract {
public:
HardwareAbstract();
/// @brief Override in order to do setup of hardware devices post construction
virtual void init() = 0;
/// @brief Override to allow printing of a message for debugging
/// @param message - Debug message
virtual void debugPrint(const char* fmt, ...) = 0;
virtual std::shared_ptr<BatteryInterface> battery() = 0;
virtual std::shared_ptr<DisplayAbstract> display() = 0;
virtual std::shared_ptr<wifiHandlerInterface> wifi() = 0;
protected:
};

View File

@ -0,0 +1,9 @@
#pragma once
#include "Notification.hpp"
class BatteryInterface {
public:
BatteryInterface() = default;
virtual int getPercentage() = 0;
virtual bool isCharging() = 0;
};

View File

@ -0,0 +1,35 @@
#include "DisplayAbstract.h"
std::shared_ptr<DisplayAbstract> DisplayAbstract::mInstance = nullptr;
DisplayAbstract::DisplayAbstract(){
lv_init();
lv_disp_draw_buf_init(&mdraw_buf, mbufA, mbufB,
SCREEN_WIDTH * SCREEN_HEIGHT / 10);
// Initialize the display driver
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_WIDTH;
disp_drv.ver_res = SCREEN_HEIGHT;
disp_drv.flush_cb = &DisplayAbstract::flushDisplayImpl;
disp_drv.draw_buf = &mdraw_buf;
lv_disp_drv_register(&disp_drv);
// Initialize the touchscreen driver
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = &DisplayAbstract::screenInputImpl;
lv_indev_drv_register(&indev_drv);
}
void DisplayAbstract::flushDisplayImpl(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
mInstance->flushDisplay(disp, area, color_p);
}
void DisplayAbstract::screenInputImpl(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
mInstance->screenInput(indev_driver, data);
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <memory>
#include "lvgl.h"
class DisplayAbstract
{
public:
DisplayAbstract();
virtual void setBrightness(uint8_t brightness) = 0;
virtual uint8_t getBrightness() = 0;
virtual void turnOff() = 0;
protected:
// Set this with a getInstance method in the Child Class
static std::shared_ptr<DisplayAbstract> mInstance;
virtual void flushDisplay(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) = 0;
virtual void screenInput(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) = 0;
private:
// Used to satisfy LVGL APIs
static void flushDisplayImpl(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
static void screenInputImpl(lv_indev_drv_t *indev_driver, lv_indev_data_t *data);
// LVGL Screen Buffers
lv_disp_draw_buf_t mdraw_buf;
lv_color_t mbufA[SCREEN_WIDTH * SCREEN_HEIGHT / 10];
lv_color_t mbufB[SCREEN_WIDTH * SCREEN_HEIGHT / 10];
};

View File

@ -0,0 +1,17 @@
#pragma once
class UIInterface
{
public:
virtual void setup() = 0;
virtual void setup_ui() = 0;
virtual void wifi_scan_complete(unsigned int size) = 0;
virtual void clear_wifi_networks() = 0;
virtual void update_wifi(bool connected) = 0;
virtual void hide_keyboard() = 0;
virtual void show_keyboard() = 0;
virtual void update() = 0;
virtual void reset_settings_menu() = 0;
virtual void update_battery(int percentage, bool isCharging, bool isConnected) = 0;
virtual void turnOff() = 0;
};

View File

@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <memory>
#include <functional>
typedef struct {
std::string ssid;
int rssi;
} WifiInfo;
typedef struct {
bool isConnected;
std::string IP;
std::string ssid;
}wifiStatus;
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;
};

View File

@ -0,0 +1,9 @@
#pragma once
#include "SPSCQueueInterface.hpp"
template <typename T>
class MPMCQueueInterface: public SPSCQueueInterface<T>
{
public:
bool push(T obj, bool overwrite = false);
};

View File

@ -0,0 +1,29 @@
#pragma once
#include <vector>
#include <functional>
template <class... notifyData>
class Notification{
public:
typedef std::function<void(notifyData...)> HandlerTy;
Notification() = default;
void onNotify(HandlerTy aHandler);
void notify(notifyData... notifySendData);
private:
std::vector<HandlerTy> mFunctionHandlers;
};
template <class... handlerData>
void Notification<handlerData...>::onNotify(HandlerTy aHandler){
mFunctionHandlers.push_back(std::move(aHandler));
}
template <class... outboundData>
void Notification<outboundData...>::notify(outboundData... notifySendData){
for (auto handler : mFunctionHandlers){
handler(notifySendData...);
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <optional>
template <typename T>
class SPSCQueueInterface {
public:
virtual bool push(T obj) = 0;
virtual std::optional<T> pop() = 0;
virtual std::optional<T> peek() = 0;
virtual bool isFull() = 0;
virtual bool isEmpty() = 0;
};

View File

@ -1,5 +1,6 @@
#include "HardwareRevX.hpp" #include "HardwareRevX.hpp"
#include "driver/ledc.h" #include "display.hpp"
#include "wifihandler.hpp"
std::shared_ptr<HardwareRevX> HardwareRevX::mInstance = nullptr; std::shared_ptr<HardwareRevX> HardwareRevX::mInstance = nullptr;
@ -49,6 +50,10 @@ void HardwareRevX::initIO() {
gpio_deep_sleep_hold_dis(); gpio_deep_sleep_hold_dis();
} }
HardwareRevX::HardwareRevX():
HardwareAbstract(){
}
HardwareRevX::WakeReason getWakeReason() { HardwareRevX::WakeReason getWakeReason() {
// Find out wakeup cause // Find out wakeup cause
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1) { if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1) {
@ -64,110 +69,53 @@ HardwareRevX::WakeReason getWakeReason() {
void HardwareRevX::init() { void HardwareRevX::init() {
// Make sure ESP32 is running at full speed // Make sure ESP32 is running at full speed
setCpuFrequencyMhz(240); setCpuFrequencyMhz(240);
wakeup_reason = getWakeReason(); wakeup_reason = getWakeReason();
initIO(); initIO();
setupBacklight();
Serial.begin(115200); Serial.begin(115200);
mDisplay = Display::getInstance();
mBattery = std::make_shared<Battery>(ADC_BAT,CRG_STAT);
mWifiHandler = wifiHandler::getInstance();
restorePreferences(); restorePreferences();
slowDisplayWakeup();
setupTFT(); mDisplay->onTouch([this]([[maybe_unused]] auto touchPoint){ standbyTimer = SLEEP_TIMEOUT;});
setupTouchScreen();
initLVGL();
setupWifi();
setupIMU(); setupIMU();
setupIR(); setupIR();
debugPrint(std::string("Finished Hardware Setup in %d", millis())); debugPrint("Finished Hardware Setup in %d", millis());
} }
void HardwareRevX::debugPrint(std::string aDebugMessage) { void HardwareRevX::debugPrint(const char* fmt, ...)
Serial.print(aDebugMessage.c_str()); {
char result[100];
va_list arguments;
va_start(arguments, fmt);
vsnprintf(result, 100, fmt, arguments);
va_end (arguments);
Serial.print(result);
} }
void HardwareRevX::sendIR() {} std::shared_ptr<HardwareRevX> HardwareRevX::getInstance(){
if (!mInstance) {
void HardwareRevX::MQTTPublish(const char *topic, const char *payload) { mInstance = std::shared_ptr<HardwareRevX>(new HardwareRevX());
#ifdef ENABLE_WIFI
if (client.connected()) {
client.publish(topic, payload);
} else {
debugPrint("MQTT Client Not Connected When Attempting Publish.");
} }
#else return mInstance;
debugPrint("Attempting To Publish MQTT with wifi Disabled!");
#endif
} }
HardwareInterface::batteryStatus HardwareRevX::getBatteryPercentage() { std::shared_ptr<wifiHandlerInterface> HardwareRevX::wifi()
return battery; {
return mWifiHandler;
} }
void HardwareRevX::initLVGL() { std::shared_ptr<BatteryInterface> HardwareRevX::battery(){
lv_init(); return mBattery;
lv_disp_draw_buf_init(&mdraw_buf, mbufA, mbufB,
SCREEN_WIDTH * SCREEN_HEIGHT / 10);
// Initialize the display driver
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_WIDTH;
disp_drv.ver_res = SCREEN_HEIGHT;
disp_drv.flush_cb = &HardwareRevX::displayFlushImpl;
disp_drv.draw_buf = &mdraw_buf;
lv_disp_drv_register(&disp_drv);
// Initialize the touchscreen driver
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = &HardwareRevX::touchPadReadImpl;
lv_indev_drv_register(&indev_drv);
} }
void HardwareRevX::handleDisplayFlush(lv_disp_drv_t *disp, std::shared_ptr<DisplayAbstract> HardwareRevX::display(){
const lv_area_t *area, return mDisplay;
lv_color_t *color_p) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushPixelsDMA((uint16_t *)&color_p->full, w * h);
tft.endWrite();
lv_disp_flush_ready(disp);
}
void HardwareRevX::handleTouchPadRead(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;
standbyTimer = SLEEP_TIMEOUT;
}
if (!touched) {
data->state = LV_INDEV_STATE_REL;
} else {
data->state = LV_INDEV_STATE_PR;
// Set the coordinates
data->point.x = SCREEN_WIDTH - touchX;
data->point.y = SCREEN_HEIGHT - touchY;
// Serial.print( "touchpoint: x" );
// Serial.print( touchX );
// Serial.print( " y" );
// Serial.println( touchY );
// tft.drawFastHLine(0, screenHeight - touchY, screenWidth, TFT_RED);
// tft.drawFastVLine(screenWidth - touchX, 0, screenHeight, TFT_RED);
}
} }
void HardwareRevX::activityDetection() { void HardwareRevX::activityDetection() {
@ -197,7 +145,7 @@ void HardwareRevX::activityDetection() {
void HardwareRevX::enterSleep() { void HardwareRevX::enterSleep() {
// Save settings to internal flash memory // Save settings to internal flash memory
preferences.putBool("wkpByIMU", wakeupByIMUEnabled); preferences.putBool("wkpByIMU", wakeupByIMUEnabled);
preferences.putUChar("blBrightness", backlight_brightness); preferences.putUChar("blBrightness", mDisplay->getBrightness());
preferences.putUChar("currentDevice", currentDevice); preferences.putUChar("currentDevice", currentDevice);
if (!preferences.getBool("alreadySetUp")) if (!preferences.getBool("alreadySetUp"))
preferences.putBool("alreadySetUp", true); preferences.putBool("alreadySetUp", true);
@ -210,12 +158,6 @@ void HardwareRevX::enterSleep() {
IMU.readRegister(&intDataRead, IMU.readRegister(&intDataRead,
LIS3DH_INT1_SRC); // really clear interrupt LIS3DH_INT1_SRC); // really clear interrupt
#ifdef ENABLE_WIFI
// Power down modem
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
#endif
// Prepare IO states // Prepare IO states
digitalWrite(LCD_DC, LOW); // LCD control signals off digitalWrite(LCD_DC, LOW); // LCD control signals off
digitalWrite(LCD_CS, LOW); digitalWrite(LCD_CS, LOW);
@ -309,51 +251,16 @@ void HardwareRevX::configIMUInterrupts() {
IMU.writeRegister(LIS3DH_CTRL_REG3, dataToWrite); IMU.writeRegister(LIS3DH_CTRL_REG3, dataToWrite);
} }
void HardwareRevX::setupBacklight() {
// Configure the backlight PWM
// Manual setup because ledcSetup() briefly turns on the backlight
ledc_channel_config_t ledc_channel_left;
ledc_channel_left.gpio_num = (gpio_num_t)LCD_BL;
ledc_channel_left.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_channel_left.channel = LEDC_CHANNEL_5;
ledc_channel_left.intr_type = LEDC_INTR_DISABLE;
ledc_channel_left.timer_sel = LEDC_TIMER_1;
ledc_channel_left.flags.output_invert = 1; // Can't do this with ledcSetup()
ledc_channel_left.duty = 0;
ledc_timer_config_t ledc_timer;
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_timer.duty_resolution = LEDC_TIMER_8_BIT;
ledc_timer.timer_num = LEDC_TIMER_1;
ledc_timer.freq_hz = 640;
ledc_channel_config(&ledc_channel_left);
ledc_timer_config(&ledc_timer);
}
void HardwareRevX::restorePreferences() { void HardwareRevX::restorePreferences() {
// Restore settings from internal flash memory // Restore settings from internal flash memory
int backlight_brightness = 255;
preferences.begin("settings", false); preferences.begin("settings", false);
if (preferences.getBool("alreadySetUp")) { if (preferences.getBool("alreadySetUp")) {
wakeupByIMUEnabled = preferences.getBool("wkpByIMU"); wakeupByIMUEnabled = preferences.getBool("wkpByIMU");
backlight_brightness = preferences.getUChar("blBrightness"); backlight_brightness = preferences.getUChar("blBrightness");
currentDevice = preferences.getUChar("currentDevice"); currentDevice = preferences.getUChar("currentDevice");
} }
} mDisplay->setBrightness(backlight_brightness);
void HardwareRevX::setupTFT() {
// Setup TFT
tft.init();
tft.initDMA();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
tft.setSwapBytes(true);
}
void HardwareRevX::setupTouchScreen() {
// Configure i2c pins and set frequency to 400kHz
Wire.begin(SDA, SCL, 400000);
touch.begin(128); // Initialize touchscreen and set sensitivity threshold
} }
void HardwareRevX::setupIMU() { void HardwareRevX::setupIMU() {
@ -371,37 +278,6 @@ void HardwareRevX::setupIMU() {
IMU.readRegister(&intDataRead, LIS3DH_INT1_SRC); // clear interrupt IMU.readRegister(&intDataRead, LIS3DH_INT1_SRC); // clear interrupt
} }
void HardwareRevX::slowDisplayWakeup() {
// Slowly charge the VSW voltage to prevent a brownout
// Workaround for hardware rev 1!
for (int i = 0; i < 100; i++) {
digitalWrite(LCD_EN, HIGH); // LCD Logic off
delayMicroseconds(1);
digitalWrite(LCD_EN, LOW); // LCD Logic on
}
delay(100); // Wait for the LCD driver to power on
}
void HardwareRevX::handleWifiEvent(WiFiEvent_t event) {
#ifdef ENABLE_WIFI
// Serial.printf("[WiFi-event] event: %d\n", event);
if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
client.setServer(MQTT_SERVER, 1883); // MQTT initialization
client.connect("OMOTE"); // Connect using a client id
}
// Set status bar icon based on WiFi status
// TODO allow UI to register a Handler for these events
// if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP ||
// event == ARDUINO_EVENT_WIFI_STA_GOT_IP6) {
// lv_label_set_text(WifiLabel, LV_SYMBOL_WIFI);
// } else {
// lv_label_set_text(WifiLabel, "");
// }
#endif
}
void HardwareRevX::setupIR() { void HardwareRevX::setupIR() {
// Setup IR // Setup IR
IrSender.begin(); IrSender.begin();
@ -409,52 +285,11 @@ void HardwareRevX::setupIR() {
IrReceiver.enableIRIn(); // Start the receiver IrReceiver.enableIRIn(); // Start the receiver
} }
void HardwareRevX::setupWifi() { void HardwareRevX::startTasks() {}
#ifdef ENABLE_WIFI
// Setup WiFi
WiFi.setHostname("OMOTE"); // define hostname
WiFi.onEvent(wiFiEventImpl);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.setSleep(true);
#endif
}
void HardwareRevX::startTasks() {
if (xTaskCreate(&HardwareRevX::updateBatteryTask, "Battery Percent Update",
1024, nullptr, 5, &batteryUpdateTskHndl) != pdPASS) {
debugPrint("ERROR Could not Create Battery Update Task!");
}
}
void HardwareRevX::updateBatteryTask([[maybe_unused]] void *aData) {
while (true) {
mInstance->battery.voltage =
analogRead(ADC_BAT) * 2 * 3300 / 4095 + 350; // 350mV ADC offset
mInstance->battery.percentage =
constrain(map(mInstance->battery.voltage, 3700, 4200, 0, 100), 0, 100);
mInstance->battery.isCharging = !digitalRead(CRG_STAT);
// Check if battery is charging, fully charged or disconnected
vTaskDelay(1000 / portTICK_PERIOD_MS);
// Update battery at 1Hz
}
}
void HardwareRevX::loopHandler() { void HardwareRevX::loopHandler() {
standbyTimer < 2000 ? mDisplay->sleep() : mDisplay->wake();
// TODO Move the backlight handling into task that spawns when the backlight
// setting changes and then gets deleted when the setting is achieved.
// Update Backlight brightness
static int fadeInTimer = millis(); // fadeInTimer = time after setup
if (millis() <
fadeInTimer + backlight_brightness) { // Fade in the backlight brightness
ledcWrite(5, millis() - fadeInTimer);
} else { // Dim Backlight before entering standby
if (standbyTimer < 2000)
ledcWrite(5, 85); // Backlight dim
else
ledcWrite(5, backlight_brightness); // Backlight on
}
// TODO move to debug task // TODO move to debug task
// Blink debug LED at 1 Hz // Blink debug LED at 1 Hz
digitalWrite(USER_LED, millis() % 1000 > 500); digitalWrite(USER_LED, millis() % 1000 > 500);
@ -470,30 +305,6 @@ void HardwareRevX::loopHandler() {
IMUTaskTimer = millis(); IMUTaskTimer = millis();
} }
// TODO Convert to free RTOS task
// TODO Create batter change notification for UI
// if (battery_ischarging || (!battery_ischarging && battery_voltage >
// 4350)) {
// lv_label_set_text(objBattPercentage, "");
// lv_label_set_text(objBattIcon, LV_SYMBOL_USB);
// } else {
// // Update status bar battery indicator
// // lv_label_set_text_fmt(objBattPercentage, "%d%%",
// battery_percentage); if (battery_percentage > 95)
// lv_label_set_text(objBattIcon, LV_SYMBOL_BATTERY_FULL);
// else if (battery_percentage > 75)
// lv_label_set_text(objBattIcon, LV_SYMBOL_BATTERY_3);
// else if (battery_percentage > 50)
// lv_label_set_text(objBattIcon, LV_SYMBOL_BATTERY_2);
// else if (battery_percentage > 25)
// lv_label_set_text(objBattIcon, LV_SYMBOL_BATTERY_1);
// else
// lv_label_set_text(objBattIcon, LV_SYMBOL_BATTERY_EMPTY);
// }
// }
// Keypad Handling // Keypad Handling
customKeypad.getKey(); // Populate key list customKeypad.getKey(); // Populate key list
for (int i = 0; i < LIST_MAX; for (int i = 0; i < LIST_MAX;

View File

@ -1,11 +1,9 @@
#pragma once #pragma once
#include "SparkFunLIS3DH.h" #include "SparkFunLIS3DH.h"
#include "HardwareInterface.h" #include "HardwareAbstract.hpp"
#include <WiFi.h>
#include "Wire.h"
#include "lvgl.h" #include "lvgl.h"
#include <Adafruit_FT6206.h> #include "battery.hpp"
#include <IRrecv.h> #include <IRrecv.h>
#include <IRremoteESP8266.h> #include <IRremoteESP8266.h>
#include <IRsend.h> #include <IRsend.h>
@ -13,90 +11,56 @@
#include <Keypad.h> // modified for inverted logic #include <Keypad.h> // modified for inverted logic
#include <Preferences.h> #include <Preferences.h>
#include <PubSubClient.h> #include <PubSubClient.h>
#include <TFT_eSPI.h> // Hardware-specific library
#include <functional> #include <functional>
#include <memory> #include <memory>
#include "wifihandler.hpp"
#include "omoteconfig.h" #include "omoteconfig.h"
#include "BatteryInterface.h"
#include "wifiHandlerInterface.h"
#include "display.hpp"
class HardwareRevX : public HardwareInterface { class HardwareRevX : public HardwareAbstract {
public: public:
enum class WakeReason { RESET, IMU, KEYPAD }; enum class WakeReason { RESET, IMU, KEYPAD };
static std::shared_ptr<HardwareRevX> getInstance() { static std::shared_ptr<HardwareRevX> getInstance();
if (!mInstance) {
mInstance = std::make_shared<HardwareRevX>();
}
return mInstance;
}
static std::weak_ptr<HardwareRevX> getRefrence() { return getInstance(); } static std::weak_ptr<HardwareRevX> getRefrence() { return getInstance(); }
HardwareRevX() : HardwareInterface(){}; // HardwareAbstract
// HardwareInterface
virtual void init() override; virtual void init() override;
virtual void sendIR() override; virtual void debugPrint(const char* fmt, ...) override;
virtual void MQTTPublish(const char *topic, const char *payload) override;
virtual batteryStatus getBatteryPercentage() override;
virtual void debugPrint(std::string aDebugMessage) override;
virtual std::shared_ptr<BatteryInterface> battery() override;
virtual std::shared_ptr<DisplayAbstract> display() override;
virtual std::shared_ptr<wifiHandlerInterface> wifi() override;
/// @brief To be ran in loop out in main
// TODO move to a freertos task
void loopHandler(); void loopHandler();
protected: protected:
// Init Functions to setup hardware // Init Functions to setup hardware
void initIO(); void initIO();
void setupBacklight();
void restorePreferences(); void restorePreferences();
void slowDisplayWakeup(); void slowDisplayWakeup();
void setupTFT();
void setupTouchScreen();
void initLVGL();
void setupIMU(); void setupIMU();
void setupIR(); void setupIR();
void setupWifi();
void activityDetection(); void activityDetection();
void enterSleep(); void enterSleep();
void configIMUInterrupts(); void configIMUInterrupts();
// UI/UX Handlers
void handleDisplayFlush(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p);
void handleTouchPadRead(lv_indev_drv_t *indev_driver, lv_indev_data_t *data);
void handleWifiEvent(WiFiEvent_t event);
// Tasks // Tasks
void startTasks(); void startTasks();
static void updateBatteryTask([[maybe_unused]] void *aData);
TaskHandle_t batteryUpdateTskHndl = nullptr;
private: private:
// Static Wrappers Needed to Satisfy C APIs HardwareRevX();
static void wiFiEventImpl(WiFiEvent_t event) {
mInstance->handleWifiEvent(event);
}
static void displayFlushImpl(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p) {
mInstance->handleDisplayFlush(disp, area, color_p);
}
static void touchPadReadImpl(lv_indev_drv_t *indev_driver,
lv_indev_data_t *data) {
mInstance->handleTouchPadRead(indev_driver, data);
}
#ifdef ENABLE_WIFI
WiFiClient espClient;
PubSubClient client = PubSubClient(espClient);
#endif
Adafruit_FT6206 touch = Adafruit_FT6206();
TS_Point touchPoint;
TS_Point oldPoint;
TFT_eSPI tft = TFT_eSPI();
std::shared_ptr<Battery> mBattery;
std::shared_ptr<Display> mDisplay;
std::shared_ptr<wifiHandler> mWifiHandler;
// IMU Motion Detection // IMU Motion Detection
LIS3DH IMU = LIS3DH(I2C_MODE, 0x19); // Default constructor is I2C, addr 0x19. LIS3DH IMU = LIS3DH(I2C_MODE, 0x19); // Default constructor is I2C, addr 0x19.
int standbyTimer = SLEEP_TIMEOUT; int standbyTimer = SLEEP_TIMEOUT;
@ -105,7 +69,6 @@ private:
Preferences preferences; Preferences preferences;
bool wakeupByIMUEnabled = true; bool wakeupByIMUEnabled = true;
int backlight_brightness = 255;
byte currentDevice = 1; // Current Device to control (allows switching byte currentDevice = 1; // Current Device to control (allows switching
// mappings between devices) // mappings between devices)
@ -113,14 +76,6 @@ private:
IRsend IrSender = IRsend(IR_LED, true); IRsend IrSender = IRsend(IR_LED, true);
IRrecv IrReceiver = IRrecv(IR_RX); IRrecv IrReceiver = IRrecv(IR_RX);
HardwareInterface::batteryStatus battery;
// LVGL Screen Buffers
lv_disp_draw_buf_t mdraw_buf;
lv_color_t mbufA[SCREEN_WIDTH * SCREEN_HEIGHT / 10];
lv_color_t mbufB[SCREEN_WIDTH * SCREEN_HEIGHT / 10];
lv_color_t color_primary = lv_color_hex(0x303030); // gray
// Keypad declarations // Keypad declarations
static const byte ROWS = 5; // four rows static const byte ROWS = 5; // four rows

View File

@ -0,0 +1,33 @@
#include "battery.hpp"
#include <Arduino.h>
Battery::Battery(int adc_pin, int charging_pin): BatteryInterface(),
mAdcPin(adc_pin),
mChargingPin(charging_pin)
{
mAdcPin = adc_pin;
mChargingPin = charging_pin;
// Power Pin Definition
pinMode(mChargingPin, INPUT_PULLUP);
pinMode(mAdcPin, INPUT);
}
int Battery::getPercentage()
{
return constrain(map(this->getVoltage(), 3700, 4200, 0, 100), 0, 100);
}
bool Battery::isCharging()
{
return !digitalRead(mChargingPin);
}
bool Battery::isConnected()
{
return ((!isCharging()) && (getVoltage() < 4350));
}
int Battery::getVoltage()
{
return analogRead(mAdcPin)*2*3300/4095 + 350;
}

View File

@ -0,0 +1,58 @@
#pragma once
#include "BatteryInterface.h"
#include "DisplayAbstract.h"
class Battery: public BatteryInterface {
public:
/**
* @brief Get the Percentage of the battery
*
* @return int Percentage of the battery
*/
virtual int getPercentage() override;
/**
* @brief Function to determine if the battery is charging or not
*
* @return true Battery is currently charging
* @return false Battery is currently not charging
*/
virtual bool isCharging() override;
/**
* @brief Function to determine if the battery is connected
*
* @return true Battery is connected
* @return false Battery is not connected
*/
bool isConnected();
Battery(int adc_pin, int charging_pin);
// Not sure why this is needed but shared_ptr seems to really
// need it possibly a compiler template handling limitation
// none the less we really should not use it.
Battery() = default;
private:
/**
* @brief Function to get the current voltage of the battery
*
* @return int Voltage of the battery in mV
*/
int getVoltage();
/**
* @brief Variable to store which pin should be used for ADC
*
*/
int mAdcPin;
/**
* @brief Variable to store which pin is used to indicate if the battery is currently charging or not
*
*/
int mChargingPin;
};

View File

@ -0,0 +1,194 @@
#include "display.hpp"
#include "omoteconfig.h"
#include "Wire.h"
#include "driver/ledc.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);
}
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
// 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);
}
void Display::setupBacklight() {
// Configure the backlight PWM
// Manual setup because ledcSetup() briefly turns on the backlight
ledc_channel_config_t ledc_channel_left;
ledc_channel_left.gpio_num = (gpio_num_t)mBacklightPin;
ledc_channel_left.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_channel_left.channel = LEDC_CHANNEL_5;
ledc_channel_left.intr_type = LEDC_INTR_DISABLE;
ledc_channel_left.timer_sel = LEDC_TIMER_1;
ledc_channel_left.flags.output_invert = 1; // Can't do this with ledcSetup()
ledc_channel_left.duty = 0;
ledc_channel_left.hpoint = 0;
ledc_timer_config_t ledc_timer;
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_timer.duty_resolution = LEDC_TIMER_8_BIT;
ledc_timer.timer_num = LEDC_TIMER_1;
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
ledc_timer.freq_hz = 640;
ledc_channel_config(&ledc_channel_left);
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();
tft.initDMA();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
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::setBrightness(uint8_t brightness)
{
mAwakeBrightness = brightness;
Serial.print("Set Brightness:");
Serial.println(mAwakeBrightness);
startFade();
}
uint8_t Display::getBrightness(){
return mAwakeBrightness;
}
void Display::setCurrentBrightness(uint8_t brightness){
mBrightness = brightness;
auto duty = static_cast<int>(mBrightness);
ledcWrite(LCD_BACKLIGHT_LEDC_CHANNEL, duty);
// Serial.print("Current 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::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);
}
if (!touched) {
data->state = LV_INDEV_STATE_REL;
} else {
data->state = LV_INDEV_STATE_PR;
// Set the coordinates
data->point.x = SCREEN_WIDTH - touchX;
data->point.y = SCREEN_HEIGHT - touchY;
// Serial.print( "touchpoint: x" );
// Serial.print( touchX );
// Serial.print( " y" );
// Serial.println( touchY );
// tft.drawFastHLine(0, screenHeight - touchY, screenWidth, TFT_RED);
// tft.drawFastVLine(screenWidth - touchX, 0, screenHeight, TFT_RED);
}
}
void Display::fadeImpl(void* ){
bool fadeDone = false;
while(!fadeDone){
fadeDone = getInstance()->fade();
vTaskDelay(3 / portTICK_PERIOD_MS); // 3 miliseconds between steps
// 0 - 255 will take about .75 seconds to fade up.
}
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){
setCurrentBrightness(mBrightness - 1);
auto setPoint = isAsleep ? 0 : mAwakeBrightness;
return mBrightness == setPoint;
}else{
setCurrentBrightness(mBrightness + 1);
return mBrightness == mAwakeBrightness;
}
}
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);
}
xSemaphoreGive(mFadeTaskMutex);
}
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);
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushPixelsDMA((uint16_t *)&color_p->full, w * h);
tft.endWrite();
lv_disp_flush_ready(disp);
}

View File

@ -0,0 +1,73 @@
#pragma once
#include "DisplayAbstract.h"
#include "HardwareAbstract.hpp"
#include "Notification.hpp"
#include <Adafruit_FT6206.h>
#include <memory>
#include <TFT_eSPI.h>
#include "driver/ledc.h"
/*LEDC Channel to use for the LCD backlight*/
#define LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_5
#define LCD_BACKLIGHT_LEDC_FREQUENCY 640
#define LCD_BACKLIGHT_LEDC_BIT_RESOLUTION 8
#define DEFAULT_BACKLIGHT_BRIGHTNESS 128
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);
inline void wake() {if(isAsleep) {isAsleep = false; startFade();}}
inline void sleep() {if(!isAsleep){isAsleep = true; startFade();}}
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();
/// @brief Set the actual display brightness right now
/// @param brightness
void setCurrentBrightness(uint8_t brightness);
private:
Display(int backlight_pin, int enable_pin);
void setupTFT();
void setupTouchScreen();
void setupBacklight();
int mEnablePin;
int mBacklightPin;
TFT_eSPI tft;
Adafruit_FT6206 touch;
TS_Point touchPoint;
TS_Point oldPoint;
Notification<TS_Point> mTouchEvent;
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,70 @@
#include "freeRTOSMPMCQueue.hpp"
template <typename T>
freeRTOSMPMCQueue<T>::freeRTOSMPMCQueue(uint32_t size)
{
this->queue = xQueueCreate(size, sizeof(T));
}
template <typename T>
freeRTOSMPMCQueue<T>::~freeRTOSMPMCQueue()
{
vQueueDelete(this->queue);
}
template <typename T>
bool freeRTOSMPMCQueue<T>::push(T obj)
{
return xQueueSendToBack(this->queue, &obj, 0) == pdTRUE;
}
template <typename T>
bool freeRTOSMPMCQueue<T>::push(T obj, bool overwrite)
{
if (overwrite == true)
{
xQueueOverwrite(this->queue, obj);
return true;
}
else
{
return this->push(obj);
}
}
template <typename T>
std::optional<T> freeRTOSMPMCQueue<T>::pop()
{
T retval;
if (xQueueReceive(this->queue, &retval, 0) == pdTRUE)
{
return retval;
}
return std::nullopt;
}
template <typename T>
std::optional<T> freeRTOSMPMCQueue<T>::peek()
{
T retval;
if (xQueuePeek(this->queue, &retval, 0) == pdTRUE)
{
return retval;
}
return std::nullopt;
}
template <typename T>
bool freeRTOSMPMCQueue<T>::isFull()
{
return (xQueueIsQueueFullFromISR(this->queue) == pdTRUE);
}
template <typename T>
bool freeRTOSMPMCQueue<T>::isEmpty()
{
return (xQueueIsQueueEmptyFromISR(this->queue) == pdTRUE);
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "MPMCQueueInterface.hpp"
#include "Arduino.h"
template <typename T>
class freeRTOSMPMCQueue: public MPMCQueueInterface<T>
{
public:
freeRTOSMPMCQueue(uint32_t size);
~freeRTOSMPMCQueue();
bool push (T obj);
bool push (T obj, bool overwrite);
std::optional<T> pop();
std::optional<T> peek();
bool isFull();
bool isEmpty();
private:
QueueHandle_t queue;
};

View File

@ -0,0 +1,230 @@
#include "wifihandler.hpp"
#include <Arduino.h>
#include <Preferences.h>
#include "HardwareAbstract.hpp"
#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());
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();
}
#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();
}
#endif
this->connect_attempt = false;
}
void wifiHandler::scan()
{
Serial.println("scan called");
/* If the */
WiFi.status();
if (WiFi.isConnected() != true)
{
WiFi.disconnect();
}
WiFi.scanNetworks(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::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

@ -0,0 +1,99 @@
#pragma once
#include "wifiHandlerInterface.h"
#include "Notification.hpp"
#include "memory.h"
#include <WiFi.h>
#define STRING_SIZE 50
class wifiHandler: public wifiHandlerInterface {
public:
wifiHandler();
static std::shared_ptr<wifiHandler> getInstance();
/**
* @brief Function to initialize the wifi handler
*
*/
void begin();
/**
* @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);
/**
* @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);
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;
};

View File

@ -1,70 +1,48 @@
#include "HardwareSimulator.hpp" #include "HardwareSimulator.hpp"
#include <unistd.h>
#include "SDL2/SDL.h"
#include "display/monitor.h"
#include "indev/mouse.h"
#include "indev/mousewheel.h"
#include "indev/keyboard.h"
#include "sdl/sdl.h"
/** #include "SDLDisplay.hpp"
* A task to measure the elapsed time for LittlevGL #include <sstream>
* @param data unused
* @return never return HardwareSimulator::HardwareSimulator(): HardwareAbstract(),
*/ mTickThread([](){
static int tick_thread(void * data) 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>())
{ {
(void)data; mHardwareStatusTitleUpdate = std::thread([this] {
int dataToShow = 0;
while(1) { while (true)
SDL_Delay(5); /*Sleep for 5 millisecond*/ {
lv_tick_inc(5); /*Tell LittelvGL that 5 milliseconds were elapsed*/ std::stringstream title;
} switch (dataToShow){
case 0:
return 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;
}
void HardwareSimulator::init() { std::shared_ptr<DisplayAbstract> HardwareSimulator::display(){
lv_init(); return mDisplay;
// Workaround for sdl2 `-m32` crash }
// https://bugs.launchpad.net/ubuntu/+source/libsdl2/+bug/1775067/comments/7 std::shared_ptr<wifiHandlerInterface> HardwareSimulator::wifi(){
#ifndef WIN32 return mWifiHandler;
setenv("DBUS_FATAL_WARNINGS", "0", 1);
#endif
/* Add a display
* Use the 'monitor' driver which creates window on PC's monitor to simulate a display*/
static lv_disp_draw_buf_t disp_buf;
static lv_color_t buf[SDL_HOR_RES * 10]; /*Declare a buffer for 10 lines*/
lv_disp_draw_buf_init(&disp_buf, buf, NULL, SDL_HOR_RES * 10); /*Initialize the display buffer*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
disp_drv.flush_cb = sdl_display_flush; /*Used when `LV_VDB_SIZE != 0` in lv_conf.h (buffered drawing)*/
disp_drv.draw_buf = &disp_buf;
disp_drv.hor_res = SDL_HOR_RES;
disp_drv.ver_res = SDL_VER_RES;
//disp_drv.disp_fill = monitor_fill; /*Used when `LV_VDB_SIZE == 0` in lv_conf.h (unbuffered drawing)*/
//disp_drv.disp_map = monitor_map; /*Used when `LV_VDB_SIZE == 0` in lv_conf.h (unbuffered drawing)*/
lv_disp_drv_register(&disp_drv);
/* Add the mouse as input device
* Use the 'mouse' driver which reads the PC's mouse*/
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv); /*Basic initialization*/
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = sdl_mouse_read; /*This function will be called periodically (by the library) to get the mouse position and state*/
lv_indev_drv_register(&indev_drv);
sdl_init();
/* Tick init.
* You have to call 'lv_tick_inc()' in periodically to inform LittelvGL about how much time were elapsed
* Create an SDL thread to do this*/
SDL_CreateThread(tick_thread, "tick", NULL);
} }

View File

@ -1,29 +1,34 @@
#pragma once #pragma once
#include "HardwareInterface.h" #include "HardwareAbstract.hpp"
#include <iostream>
#include <string>
class HardwareSimulator : public HardwareInterface { #include "batterySimulator.hpp"
#include "SDLDisplay.hpp"
#include "wifiHandlerSim.hpp"
#include <thread>
class HardwareSimulator : public HardwareAbstract {
public: public:
HardwareSimulator() = default; HardwareSimulator();
virtual void init() override {};
virtual void debugPrint(std::string message) override { virtual void debugPrint(const char* fmt, ...) override {
std::cout << message; va_list arguments;
va_start(arguments, fmt);
vprintf(fmt, arguments);
va_end(arguments);
} }
virtual void sendIR() override {} virtual std::shared_ptr<BatteryInterface> battery() override;
virtual std::shared_ptr<DisplayAbstract> display() override;
virtual std::shared_ptr<wifiHandlerInterface> wifi() override;
virtual void MQTTPublish(const char *topic, const char *payload) override{ private:
std::thread mTickThread;
std::thread mHardwareStatusTitleUpdate;
}; std::shared_ptr<BatterySimulator> mBattery;
std::shared_ptr<SDLDisplay> mDisplay;
virtual void init() override; std::shared_ptr<wifiHandlerSim> mWifiHandler;
virtual batteryStatus getBatteryPercentage() {
batteryStatus fakeStatus;
fakeStatus.isCharging = false;
fakeStatus.percentage = 100;
fakeStatus.voltage = 4200;
return fakeStatus;
}
}; };

View File

@ -0,0 +1,39 @@
#include "SDLDisplay.hpp"
#include "sdl/sdl.h"
#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);
}
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);
}
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...
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#include "SDL2/SDL.h"
#include "DisplayAbstract.h"
class SDLDisplay : public DisplayAbstract{
public:
static std::shared_ptr<SDLDisplay> getInstance();
virtual void setBrightness(uint8_t brightness) override;
virtual uint8_t getBrightness() override;
virtual void turnOff() override;
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;
private:
SDLDisplay();
uint8_t mBrightness;
SDL_Window* mSimWindow;
};

View File

@ -0,0 +1,77 @@
#include "SimulatorMPMCQueue.hpp"
template <typename T>
bool SimulatorMPMCQueue<T>::push(T obj)
{
return this->push(obj, false);
}
template <typename T>
bool SimulatorMPMCQueue<T>::push(T obj, bool overwrite)
{
bool retval = false;
if (this->mtx.try_lock())
{
if (this->isFull() && overwrite)
{
/* If we should overwrite already written data and the buffer is full, we increment the rd_index as well.
This has to be done in the mutex so we do not overwrite data which is already being read*/
this->rd_index = this->incrementIndex(this->rd_index);
}
/* If the buffer is full, we can not write to the buffer. If overwrite is set to true, this check will never
fail as we move the rd_index if the buffer would otherwise be full*/
if (!this->isFull())
{
retval = true;
this->data[this->wr_index] = obj;
this->wr_index = this->incrementIndex(this->wr_index);
}
this->mtx.unlock();
}
return retval;
}
template <typename T>
std::optional<T> SimulatorMPMCQueue<T>::pop()
{
T retval;
if (this->mtx.try_lock()){
if (this->isEmpty())
{
return std::nullopt;
}
retval = this->data[this->rd_index];
this->rd_index = this->incrementIndex(this->rd_index);
this->mtx.unlock();
return retval;
}
return std::nullopt;
}
template <typename T>
std::optional<T> SimulatorMPMCQueue<T>::peek()
{
T retval;
if (this->mtx.try_lock())
{
if (this->isEmpty())
{
return std::nullopt;
}
retval = this->data[this->rd_index];
this->mtx.unlock();
return retval;
}
return std::nullopt;
}
template <typename T>
uint32_t SimulatorMPMCQueue<T>::incrementIndex(uint32_t index)
{
return (index + 1) % this->size;
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "MPMCQueueInterface.hpp"
#include "SimulatorSPSCQueue.hpp"
#include <mutex>
template <typename T>
class SimulatorMPMCQueue: public SimulatorSPSCQueue<T>, public MPMCQueueInterface<T>
{
public:
SimulatorMPMCQueue(uint32_t size): SimulatorSPSCQueue<T>(size){};
bool push (T obj);
bool push (T obj, bool overwrite);
std::optional<T> pop();
std::optional<T> peek();
bool isFull();
bool isEmpty();
private:
T* data;
uint32_t size;
uint32_t rd_index;
uint32_t wr_index;
uint32_t incrementIndex(uint32_t index);
std::mutex mtx;
};

View File

@ -0,0 +1,68 @@
#include "SimulatorSPSCQueue.hpp"
template <typename T>
SimulatorSPSCQueue<T>::SimulatorSPSCQueue(uint32_t size)
{
this->size = size;
this->data = new T[](this->size + 1);
this->rd_index = 0;
this->wr_index = 0;
}
template <typename T>
SimulatorSPSCQueue<T>::~SimulatorSPSCQueue()
{
free(this->data);
}
template <typename T>
bool SimulatorSPSCQueue<T>::isFull()
{
return ((this->wr_index + 1) % this->size) == this->rd_index;
}
template <typename T>
bool SimulatorSPSCQueue<T>::isEmpty()
{
return this->rd_index == this->wr_index;
}
template <typename T>
bool SimulatorSPSCQueue<T>::push(T obj)
{
bool retval = false;
if (!this->isFull())
{
retval = true;
this->data[this->wr_index] = obj;
this->wr_index = this->incrementIndex(this->wr_index);
}
return retval;
}
template <typename T>
std::optional<T> SimulatorSPSCQueue<T>::pop()
{
std::optional<T> retval;
retval = this->peek();
this->rd_index = this->incrementIndex(this->rd_index);
return retval;
}
template <typename T>
std::optional<T> SimulatorSPSCQueue<T>::peek()
{
if (this->isEmpty())
{
return std::nullopt;
}
return this->data[this->rd_index];
}
template <typename T>
uint32_t SimulatorSPSCQueue<T>::incrementIndex(uint32_t index)
{
return (index + 1) % this->size;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "SPSCQueueInterface.hpp"
#include <cstdint>
template <typename T>
class SimulatorSPSCQueue: public SPSCQueueInterface<T>
{
public:
SimulatorSPSCQueue(uint32_t size);
~SimulatorSPSCQueue();
bool push (T obj);
std::optional<T> pop();
std::optional<T> peek();
bool isFull();
bool isEmpty();
private:
T* data;
uint32_t size;
uint32_t rd_index;
uint32_t wr_index;
uint32_t incrementIndex(uint32_t index);
};

View File

@ -0,0 +1,23 @@
#include "BatteryInterface.h"
#include <chrono>
#include <cmath>
class BatterySimulator: public BatteryInterface{
public:
BatterySimulator() :
mCreationTime(std::chrono::high_resolution_clock::now())
{};
~BatterySimulator(){}
virtual int getPercentage() override {
auto now = std::chrono::high_resolution_clock::now();
auto batteryRunTime = std::chrono::duration_cast<std::chrono::seconds>(now - mCreationTime);
constexpr auto minToBatteryZero = 3;
auto fakeBattPercentage = 100 - ((batteryRunTime / std::chrono::duration<float,std::ratio<60LL>>(minToBatteryZero)) * 100);
return std::floor(fakeBattPercentage < 100 ? fakeBattPercentage : 0);
}
virtual bool isCharging() override { return false; }
private:
std::chrono::_V2::system_clock::time_point mCreationTime;
};

View File

@ -1,6 +1,3 @@
#pragma once #pragma once
#define IS_SIMULATOR true #define IS_SIMULATOR true
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 360

View File

@ -0,0 +1,68 @@
#include "wifiHandlerSim.hpp"
std::shared_ptr<wifiHandlerSim> mInstance;
std::shared_ptr<wifiHandlerSim> wifiHandlerSim::getInstance()
{
if(mInstance)
{
return mInstance;
}
mInstance = std::make_shared<wifiHandlerSim>(wifiHandlerSim());
return mInstance;
};
wifiHandlerSim::wifiHandlerSim(){
}
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);
}
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
}
};
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);
}
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

@ -0,0 +1,35 @@
#pragma once
#include "wifiHandlerInterface.h"
#include "Notification.hpp"
#include <memory>
class wifiHandlerSim: public wifiHandlerInterface {
public:
wifiHandlerSim();
static std::shared_ptr<wifiHandlerSim> getInstance();
/**
* @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);
/**
* @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;
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,8 @@ std::shared_ptr<OmoteUI> OmoteUI::mInstance = nullptr;
// #if defined(IS_SIMULATOR) && (IS_SIMULATOR == true) // #if defined(IS_SIMULATOR) && (IS_SIMULATOR == true)
// #endif // #endif
OmoteUI::OmoteUI(std::shared_ptr<HardwareAbstract> aHardware) : mHardware(aHardware){}
// Set the page indicator scroll position relative to the tabview scroll // Set the page indicator scroll position relative to the tabview scroll
// position // position
void OmoteUI::store_scroll_value_event_cb(lv_event_t *e) { void OmoteUI::store_scroll_value_event_cb(lv_event_t *e) {
@ -27,13 +29,13 @@ void OmoteUI::tabview_device_event_cb(lv_event_t *e) {
// Slider Event handler // Slider Event handler
void OmoteUI::bl_slider_event_cb(lv_event_t *e) { void OmoteUI::bl_slider_event_cb(lv_event_t *e) {
lv_obj_t *slider = lv_event_get_target(e); lv_obj_t *slider = lv_event_get_target(e);
backlight_brightness = std::clamp(lv_slider_get_value(slider), 60, 255); auto newBrightness = std::clamp(lv_slider_get_value(slider), 60, 255);
mHardware->display()->setBrightness(newBrightness);
} }
// Apple Key Event handler // Apple Key Event handler
void OmoteUI::appleKey_event_cb(lv_event_t *e) { void OmoteUI::appleKey_event_cb(lv_event_t *e) {
// Send IR command based on the event user data // Send IR command based on the event user data
mHardware->sendIR();
//mHardware->debugPrint(std::to_string(50 + (int)e->user_data)); //mHardware->debugPrint(std::to_string(50 + (int)e->user_data));
} }
@ -91,39 +93,146 @@ void OmoteUI::loopHandler(){
lv_timer_handler(); lv_timer_handler();
} }
void OmoteUI::create_status_bar(){
// Create a status bar
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);
lv_obj_set_style_radius(statusbar, 0, LV_PART_MAIN);
lv_obj_align(statusbar, LV_ALIGN_TOP_MID, 0, 0);
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);
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);
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);
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)
{
// Add content to the settings tab
// 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);
// Add a label, then a box for the display settings
this->settingsMenu = lv_menu_create(parent);
lv_obj_set_width(this->settingsMenu, 210);
/* 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_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));
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_label_set_text(menuLabel, "Battery");
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);
lv_menu_set_page(this->settingsMenu, this->settingsMainPage);
}
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;
}
}
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::reset_settings_menu()
{
lv_menu_set_page(this->settingsMenu, this->settingsMainPage);
}
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);
}
void OmoteUI::layout_UI() { void OmoteUI::layout_UI() {
// --- LVGL UI Configuration --- // Set the background color
// Set the background color
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN); 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 // Setup a scrollable tabview for devices and settings
lv_obj_t *tabview; lv_obj_t* tabview;
tabview = tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 0); // Hide tab labels by setting their height to 0
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_style_bg_color(tabview, lv_color_black(), LV_PART_MAIN);
lv_obj_set_size(tabview, SCREEN_WIDTH, lv_obj_set_size(tabview, SCREEN_WIDTH, 270); // 270 = screenHeight(320) - panel(30) - statusbar(20)
270); // 270 = screenHeight(320) - panel(30) - statusbar(20)
lv_obj_align(tabview, LV_ALIGN_TOP_MID, 0, 20); lv_obj_align(tabview, LV_ALIGN_TOP_MID, 0, 20);
// Add 4 tabs (names are irrelevant since the labels are hidden) // Add 4 tabs (names are irrelevant since the labels are hidden)
lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "Settings"); lv_obj_t* tab1 = lv_tabview_add_tab(tabview, "Settings");
lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "Technisat"); lv_obj_t* tab2 = lv_tabview_add_tab(tabview, "Technisat");
lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "Apple TV"); 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* tab4 = lv_tabview_add_tab(tabview, "Smart Home");
// Configure number button grid // Configure number button grid
static lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), 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
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
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 // Create a container with grid for tab2
lv_obj_set_style_pad_all(tab2, 0, LV_PART_MAIN); 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_shadow_width(cont, 0, LV_PART_MAIN);
lv_obj_set_style_bg_color(cont, lv_color_black(), 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); lv_obj_set_style_border_width(cont, 0, LV_PART_MAIN);
@ -134,61 +243,50 @@ void OmoteUI::layout_UI() {
lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 0); lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_radius(cont, 0, LV_PART_MAIN); lv_obj_set_style_radius(cont, 0, LV_PART_MAIN);
lv_obj_t *buttonLabel; lv_obj_t* buttonLabel;
lv_obj_t *obj; lv_obj_t* obj;
// Iterate through grid buttons and configure them // Iterate through grid buttons configure them
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
uint8_t col = i % 3; uint8_t col = i % 3;
uint8_t row = i / 3; uint8_t row = i / 3;
// Create the button object // Create the button object
if ((row == 3) && ((col == 0) || (col == 2))) if ((row == 3) && ((col == 0) || (col == 2))) continue; // Do not create a complete fourth row, only a 0 button
continue; // Do not create a complete fourth row, only a 0 button
obj = lv_btn_create(cont); obj = lv_btn_create(cont);
lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, col, 1, lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, col, 1, LV_GRID_ALIGN_STRETCH, row, 1);
LV_GRID_ALIGN_STRETCH, row, 1); lv_obj_set_style_bg_color(obj, this->color_primary, LV_PART_MAIN);
lv_obj_set_style_bg_color(obj, color_primary, LV_PART_MAIN);
lv_obj_set_style_radius(obj, 14, LV_PART_MAIN); lv_obj_set_style_radius(obj, 14, LV_PART_MAIN);
lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); // Clicking a button causes lv_obj_set_style_shadow_color(obj, lv_color_hex(0x404040), LV_PART_MAIN);
// 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 // Create Labels for each button
buttonLabel = lv_label_create(obj); buttonLabel = lv_label_create(obj);
if (i < 9) { if(i < 9){
lv_label_set_text_fmt(buttonLabel, std::to_string(i + 1).c_str(), col, lv_label_set_text_fmt(buttonLabel, std::to_string(i+1).c_str(), col, row);
row); lv_obj_set_user_data(obj, (void*)i); // Add user data so we can identify which button caused the container event
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, else{
LV_PART_MAIN); 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_center(buttonLabel); lv_obj_center(buttonLabel);
} }
// Create a shared event for all button inside container // Create a shared event for all button inside container
lv_obj_add_event_cb( lv_obj_add_event_cb(cont, [] (lv_event_t* e) {mInstance->virtualKeypad_event_cb(e);}, LV_EVENT_CLICKED, NULL);
cont, [](lv_event_t *e) { mInstance->virtualKeypad_event_cb(e); },
LV_EVENT_CLICKED, NULL);
// Add content to the Apple TV tab (3) // Add content to the Apple TV tab (3)
// Add a nice apple tv logo // Add a nice apple tv logo
lv_obj_t* appleImg = imgs.addAppleTVIcon(tab3); lv_obj_t* appleImg = imgs.addAppleTVIcon(tab3);
lv_obj_align(appleImg, LV_ALIGN_CENTER, 0, -60);
// create two buttons and add their icons accordingly // 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_align(button, LV_ALIGN_BOTTOM_LEFT, 10, 0);
lv_obj_set_size(button, 60, 60); lv_obj_set_size(button, 60, 60);
lv_obj_set_style_radius(button, 30, LV_PART_MAIN); lv_obj_set_style_radius(button, 30, LV_PART_MAIN);
lv_obj_set_style_bg_color(button, color_primary, LV_PART_MAIN); lv_obj_set_style_bg_color(button, color_primary, LV_PART_MAIN);
lv_obj_add_event_cb( lv_obj_add_event_cb(button, [] (lv_event_t* e) {mInstance->appleKey_event_cb(e);}, LV_EVENT_CLICKED, (void*)1);
button, [](lv_event_t *e) { mInstance->appleKey_event_cb(e); },
LV_EVENT_CLICKED, (void *)1);
appleImg = imgs.addAppleBackIcon(button); appleImg = imgs.addAppleDisplayImage(button);
lv_obj_align(appleImg, LV_ALIGN_CENTER, -3, 0); lv_obj_align(appleImg, LV_ALIGN_CENTER, -3, 0);
lv_obj_set_style_img_recolor(appleImg, lv_color_white(), LV_PART_MAIN); lv_obj_set_style_img_recolor(appleImg, lv_color_white(), LV_PART_MAIN);
lv_obj_set_style_img_recolor_opa(appleImg, LV_OPA_COVER, LV_PART_MAIN); lv_obj_set_style_img_recolor_opa(appleImg, LV_OPA_COVER, LV_PART_MAIN);
@ -199,9 +297,7 @@ void OmoteUI::layout_UI() {
lv_obj_set_size(button, 60, 60); lv_obj_set_size(button, 60, 60);
lv_obj_set_style_radius(button, 30, LV_PART_MAIN); lv_obj_set_style_radius(button, 30, LV_PART_MAIN);
lv_obj_set_style_bg_color(button, color_primary, LV_PART_MAIN); lv_obj_set_style_bg_color(button, color_primary, LV_PART_MAIN);
lv_obj_add_event_cb( lv_obj_add_event_cb(button, [] (lv_event_t* e) {mInstance->appleKey_event_cb(e);}, LV_EVENT_CLICKED, (void*)2);
button, [](lv_event_t *e) { mInstance->appleKey_event_cb(e); },
LV_EVENT_CLICKED, (void *)2);
appleImg = imgs.addAppleDisplayImage(button); appleImg = imgs.addAppleDisplayImage(button);
lv_obj_align(appleImg, LV_ALIGN_CENTER, 0, 0); lv_obj_align(appleImg, LV_ALIGN_CENTER, 0, 0);
@ -209,157 +305,49 @@ void OmoteUI::layout_UI() {
lv_obj_set_style_img_recolor_opa(appleImg, LV_OPA_COVER, LV_PART_MAIN); lv_obj_set_style_img_recolor_opa(appleImg, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_align(appleImg, LV_ALIGN_CENTER, 0, 0); lv_obj_align(appleImg, LV_ALIGN_CENTER, 0, 0);
// Add content to the settings tab this->setup_settings(tab1);
// With a flex layout, setting groups/boxes will position themselves
// automatically
lv_obj_set_layout(tab1, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(tab1, LV_FLEX_FLOW_COLUMN);
lv_obj_set_scrollbar_mode(tab1, LV_SCROLLBAR_MODE_ACTIVE);
// Add a label, then a box for the display settings
lv_obj_t *menuLabel = lv_label_create(tab1);
lv_label_set_text(menuLabel, "Display");
lv_obj_t *menuBox = lv_obj_create(tab1);
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_align(brightnessIcon, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_t *slider = lv_slider_create(menuBox);
lv_slider_set_range(slider, 60, 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, backlight_brightness, 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, NULL);
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_set_size(wakeToggle, 40, 22);
lv_obj_align(wakeToggle, LV_ALIGN_TOP_RIGHT, 0, 29);
lv_obj_set_style_bg_color(wakeToggle, lv_color_lighten(color_primary, 50),
LV_PART_MAIN);
lv_obj_add_event_cb(
wakeToggle,
[](lv_event_t *e) { mInstance->WakeEnableSetting_event_cb(e); },
LV_EVENT_VALUE_CHANGED, NULL);
if (wakeupByIMUEnabled)
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_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_pad_top(drop, 1, LV_PART_MAIN);
lv_obj_set_style_bg_color(drop, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(drop, 0, 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_darken(color_primary, 40),
LV_PART_MAIN);
// Add another label, then a settings box for WiFi
menuLabel = lv_label_create(tab1);
lv_label_set_text(menuLabel, "Wi-Fi");
menuBox = lv_obj_create(tab1);
lv_obj_set_size(menuBox, lv_pct(100), 80);
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(menuBox, 0, LV_PART_MAIN);
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Network");
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, LV_SYMBOL_RIGHT);
lv_obj_align(menuLabel, LV_ALIGN_TOP_RIGHT, 0, 0);
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Password");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 0, 32);
menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, LV_SYMBOL_RIGHT);
lv_obj_align(menuLabel, LV_ALIGN_TOP_RIGHT, 0, 32);
// Another setting for the battery
menuLabel = lv_label_create(tab1);
lv_label_set_text(menuLabel, "Battery");
menuBox = lv_obj_create(tab1);
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);
// Add content to the smart home tab (4) // Add content to the smart home tab (4)
lv_obj_set_layout(tab4, LV_LAYOUT_FLEX); lv_obj_set_layout(tab4, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(tab4, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(tab4, LV_FLEX_FLOW_COLUMN);
lv_obj_set_scrollbar_mode(tab4, LV_SCROLLBAR_MODE_ACTIVE); lv_obj_set_scrollbar_mode(tab4, LV_SCROLLBAR_MODE_ACTIVE);
// Add a label, then a box for the light controls // Add a label, then a box for the light controls
menuLabel = lv_label_create(tab4); lv_obj_t* menuLabel = lv_label_create(tab4);
lv_label_set_text(menuLabel, "Living Room"); lv_label_set_text(menuLabel, "Living Room");
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_size(menuBox, lv_pct(100), 79);
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN); 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_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); lv_obj_align(bulbIcon, LV_ALIGN_TOP_LEFT, 0, 0);
menuLabel = lv_label_create(menuBox); menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Floor Lamp"); lv_label_set_text(menuLabel, "Floor Lamp");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 22, 3); 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_set_size(lightToggleA, 40, 22);
lv_obj_align(lightToggleA, LV_ALIGN_TOP_RIGHT, 0, 0); lv_obj_align(lightToggleA, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_style_bg_color(lightToggleA, lv_color_lighten(color_primary, 50), lv_obj_set_style_bg_color(lightToggleA, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
LV_PART_MAIN);
lv_obj_set_style_bg_color(lightToggleA, color_primary, LV_PART_INDICATOR); lv_obj_set_style_bg_color(lightToggleA, color_primary, LV_PART_INDICATOR);
lv_obj_add_event_cb( lv_obj_add_event_cb(lightToggleA, [] (lv_event_t* e) {mInstance->smartHomeToggle_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)1);
lightToggleA,
[](lv_event_t *e) { mInstance->smartHomeToggle_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)1);
slider = lv_slider_create(menuBox); lv_obj_t *slider = lv_slider_create(menuBox);
lv_slider_set_range(slider, 60, 255); lv_slider_set_range(slider, 0, 100);
lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30), lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30), LV_PART_INDICATOR);
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_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_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_color(slider, lv_color_white(), LV_PART_KNOB);
lv_obj_set_style_bg_opa(slider, 255, LV_PART_MAIN); 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_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
LV_PART_MAIN);
lv_slider_set_value(slider, 255, LV_ANIM_OFF); lv_slider_set_value(slider, 255, LV_ANIM_OFF);
lv_obj_set_size(slider, lv_pct(90), 10); lv_obj_set_size(slider, lv_pct(90), 10);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 37); lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 37);
lv_obj_add_event_cb( lv_obj_add_event_cb(slider, [] (lv_event_t* e) {mInstance->smartHomeSlider_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)1);
slider, [](lv_event_t *e) { mInstance->smartHomeSlider_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)1);
// Add another menu box for a second appliance // Add another this->settingsMenu box for a second appliance
menuBox = lv_obj_create(tab4); menuBox = lv_obj_create(tab4);
lv_obj_set_size(menuBox, lv_pct(100), 79); 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_bg_color(menuBox, color_primary, LV_PART_MAIN);
@ -371,35 +359,26 @@ void OmoteUI::layout_UI() {
menuLabel = lv_label_create(menuBox); menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Ceiling Light"); lv_label_set_text(menuLabel, "Ceiling Light");
lv_obj_align(menuLabel, LV_ALIGN_TOP_LEFT, 22, 3); 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_set_size(lightToggleB, 40, 22);
lv_obj_align(lightToggleB, LV_ALIGN_TOP_RIGHT, 0, 0); lv_obj_align(lightToggleB, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_style_bg_color(lightToggleB, lv_color_lighten(color_primary, 50), lv_obj_set_style_bg_color(lightToggleB, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
LV_PART_MAIN);
lv_obj_set_style_bg_color(lightToggleB, color_primary, LV_PART_INDICATOR); lv_obj_set_style_bg_color(lightToggleB, color_primary, LV_PART_INDICATOR);
lv_obj_add_event_cb( lv_obj_add_event_cb(lightToggleB, [] (lv_event_t* e) {mInstance->smartHomeToggle_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)2);
lightToggleB,
[](lv_event_t *e) { mInstance->smartHomeToggle_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)2);
slider = lv_slider_create(menuBox); slider = lv_slider_create(menuBox);
lv_slider_set_range(slider, 60, 255); lv_slider_set_range(slider, 0, 100);
lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30), lv_obj_set_style_bg_color(slider, lv_color_lighten(lv_color_black(), 30), LV_PART_INDICATOR);
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_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_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_color(slider, lv_color_white(), LV_PART_KNOB);
lv_obj_set_style_bg_opa(slider, 255, LV_PART_MAIN); 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_obj_set_style_bg_color(slider, lv_color_lighten(color_primary, 50), LV_PART_MAIN);
LV_PART_MAIN);
lv_slider_set_value(slider, 255, LV_ANIM_OFF); lv_slider_set_value(slider, 255, LV_ANIM_OFF);
lv_obj_set_size(slider, lv_pct(90), 10); lv_obj_set_size(slider, lv_pct(90), 10);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 37); lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 37);
lv_obj_add_event_cb( lv_obj_add_event_cb(slider, [] (lv_event_t* e) {mInstance->smartHomeSlider_event_cb(e);}, LV_EVENT_VALUE_CHANGED, (void*)2);
slider, [](lv_event_t *e) { mInstance->smartHomeSlider_event_cb(e); },
LV_EVENT_VALUE_CHANGED, (void *)2);
// Add another room (empty for now) // Add another room (empty for now)
menuLabel = lv_label_create(tab4); menuLabel = lv_label_create(tab4);
@ -410,19 +389,20 @@ void OmoteUI::layout_UI() {
lv_obj_set_style_bg_color(menuBox, color_primary, LV_PART_MAIN); 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_set_style_border_width(menuBox, 0, LV_PART_MAIN);
// Set current page according to the current Device // Set current page according to the current Device
lv_tabview_set_act(tabview, currentDevice, LV_ANIM_OFF); lv_tabview_set_act(tabview, 0, LV_ANIM_OFF);
// Create a page indicator // Create a page indicator
panel = lv_obj_create(lv_scr_act()); panel = lv_obj_create(lv_scr_act());
lv_obj_clear_flag( lv_obj_clear_flag(panel, LV_OBJ_FLAG_CLICKABLE); // This indicator will not be clickable
panel, LV_OBJ_FLAG_CLICKABLE); // this indicator will not be clickable
lv_obj_set_size(panel, SCREEN_WIDTH, 30); lv_obj_set_size(panel, SCREEN_WIDTH, 30);
lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_ROW); lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_ROW);
lv_obj_align(panel, LV_ALIGN_BOTTOM_MID, 0, 0); lv_obj_align(panel, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_scrollbar_mode(panel, LV_SCROLLBAR_MODE_OFF); lv_obj_set_scrollbar_mode(panel, LV_SCROLLBAR_MODE_OFF);
// This small hidden button enables the page indicator to scroll further // 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_size(btn, 50, lv_pct(100));
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN); lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
lv_obj_set_style_opa(btn, LV_OPA_TRANSP, LV_PART_MAIN); lv_obj_set_style_opa(btn, LV_OPA_TRANSP, LV_PART_MAIN);
@ -430,7 +410,7 @@ void OmoteUI::layout_UI() {
btn = lv_btn_create(panel); btn = lv_btn_create(panel);
lv_obj_clear_flag(btn, LV_OBJ_FLAG_CLICKABLE); lv_obj_clear_flag(btn, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_size(btn, 150, lv_pct(100)); 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_label_set_text_fmt(label, "Settings");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN); lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
@ -467,15 +447,10 @@ void OmoteUI::layout_UI() {
lv_obj_set_size(btn, 50, lv_pct(100)); lv_obj_set_size(btn, 50, lv_pct(100));
lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN); lv_obj_set_style_shadow_width(btn, 0, LV_PART_MAIN);
lv_obj_set_style_opa(btn, LV_OPA_TRANSP, 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 // Make the indicator scroll together with the tabs by creating a scroll event
lv_obj_add_event_cb( 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_tabview_get_content(tabview), lv_obj_add_event_cb(tabview, [] (lv_event_t* e) {mInstance->tabview_device_event_cb(e);}, LV_EVENT_VALUE_CHANGED, NULL);
[](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 // Initialize scroll position for the indicator
lv_event_send(lv_tabview_get_content(tabview), LV_EVENT_SCROLL, NULL); lv_event_send(lv_tabview_get_content(tabview), LV_EVENT_SCROLL, NULL);
@ -488,35 +463,13 @@ void OmoteUI::layout_UI() {
lv_obj_add_style(panel, &style_btn, 0); lv_obj_add_style(panel, &style_btn, 0);
// Make the indicator fade out at the sides using gradient bitmaps // 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_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_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_align(img2, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
lv_obj_set_size(img2, 30, 30); lv_obj_set_size(img2, 30, 30);
// Create a status bar this->create_status_bar();
lv_obj_t *statusbar = lv_btn_create(lv_scr_act()); this->mHardware->wifi()->begin();
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);
lv_obj_set_style_radius(statusbar, 0, LV_PART_MAIN);
lv_obj_align(statusbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t *WifiLabel = lv_label_create(statusbar);
lv_label_set_text(WifiLabel, LV_SYMBOL_WIFI);
lv_obj_align(WifiLabel, LV_ALIGN_LEFT_MID, -8, 0);
lv_obj_set_style_text_font(WifiLabel, &lv_font_montserrat_14, LV_PART_MAIN);
lv_obj_t *objBattPercentage = lv_label_create(statusbar);
lv_label_set_text(objBattPercentage, "");
lv_obj_align(objBattPercentage, LV_ALIGN_RIGHT_MID, -16, 0);
lv_obj_set_style_text_font(objBattPercentage, &lv_font_montserrat_14,
LV_PART_MAIN);
lv_obj_t *objBattIcon = lv_label_create(statusbar);
lv_label_set_text(objBattIcon, LV_SYMBOL_BATTERY_EMPTY);
lv_obj_align(objBattIcon, LV_ALIGN_RIGHT_MID, 8, 0);
lv_obj_set_style_text_font(objBattIcon, &lv_font_montserrat_14, LV_PART_MAIN);
} }

View File

@ -2,25 +2,24 @@
// 2023 Matthew Colvin // 2023 Matthew Colvin
#pragma once #pragma once
#include "HardwareInterface.h" #include "HardwareAbstract.hpp"
#include "Images.hpp" #include "Images.hpp"
#include "lvgl.h" #include "lvgl.h"
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <stdio.h> #include <stdio.h>
#include <string> #include <string>
#include "poller.hpp"
/// @brief Singleton to allow UI code to live separately from the Initialization /// @brief Singleton to allow UI code to live separately from the Initialization
/// of resources. /// of resources.
class OmoteUI { class OmoteUI {
public: public:
OmoteUI(std::shared_ptr<HardwareInterface> aHardware) OmoteUI(std::shared_ptr<HardwareAbstract> aHardware);
: mHardware(aHardware){};
static std::weak_ptr<OmoteUI> getRefrence() { return getInstance(); }; static std::weak_ptr<OmoteUI> getRefrence() { return getInstance(); };
static std::shared_ptr<OmoteUI> static std::shared_ptr<OmoteUI>
getInstance(std::shared_ptr<HardwareInterface> aHardware = nullptr) { getInstance(std::shared_ptr<HardwareAbstract> aHardware = nullptr) {
if (mInstance) { if (mInstance) {
return mInstance; return mInstance;
} else if (aHardware) { } else if (aHardware) {
@ -46,23 +45,194 @@ public:
void smartHomeSlider_event_cb(lv_event_t *e); void smartHomeSlider_event_cb(lv_event_t *e);
// Virtual Keypad Event handler // Virtual Keypad Event handler
void virtualKeypad_event_cb(lv_event_t *e); void virtualKeypad_event_cb(lv_event_t *e);
void wifi_settings_cb(lv_event_t* event);
void connect_btn_cb(lv_event_t* event);
void password_field_event_cb(lv_event_t* e);
// Use LVGL to layout the ui and register the callbacks // Use LVGL to layout the ui and register the callbacks
void layout_UI(); void layout_UI();
void ta_kb_event_cb(lv_event_t* e);
void wifi_scan_done(std::shared_ptr<std::vector<WifiInfo>> info);
void loopHandler(); 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.
*
*/
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
* needed if the keyboard should be shown due to some script or other trigger.
*
*/
void show_keyboard();
private: private:
static std::shared_ptr<OmoteUI> mInstance; static std::shared_ptr<OmoteUI> mInstance;
std::shared_ptr<HardwareInterface> mHardware; std::shared_ptr<HardwareAbstract> mHardware;
std::unique_ptr<poller> batteryPoller;
void reset_settings_menu();
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 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
*/
void setup_settings(lv_obj_t* parent);
/**
* @brief LVGL Menu for settings pages as needed.
*
*/
lv_obj_t* settingsMenu;
/**
* @brief Main page of the settings menu
*
*/
lv_obj_t* settingsMainPage;
/**
* @brief Battery percentage label
*
*/
lv_obj_t* objBattPercentage;
/**
* @brief Battery icon object in the status bar
*
*/
lv_obj_t* objBattIcon;
void create_status_bar();
lv_obj_t *panel = nullptr; lv_obj_t *panel = nullptr;
Images imgs = Images(); Images imgs = Images();
uint_fast8_t currentDevice = 4; uint_fast8_t currentDevice = 4;
int backlight_brightness = 255;
lv_color_t color_primary = lv_color_hex(0x303030); // gray lv_color_t color_primary = lv_color_hex(0x303030); // gray
bool wakeupByIMUEnabled = true; bool wakeupByIMUEnabled = true;
inline static const uint_fast8_t virtualKeyMapTechnisat[10] = { inline static const uint_fast8_t virtualKeyMapTechnisat[10] = {
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0}; 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0};
/************************************** WIFI Settings Menu *******************************************************/
/**
* @brief Container within the wifi selection page
*/
lv_obj_t* wifi_setting_cont;
/**
* @brief Wifi settings entry point on the settings tab
*
*/
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.
*
*/
lv_obj_t* wifi_password_label;
/**
* @brief Menu Subpage for the wifi password
*/
lv_obj_t* wifi_password_page;
/**
* @brief Menu Subpage for wifi selection
*/
lv_obj_t* wifi_selection_page;
/**
* @brief Wifi Label shown in the top status bar
*/
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
*/
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
* @return lv_obj_t* Menu sub page object pointer
*/
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
* @return lv_obj_t* menu sub page object pointer
*/
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
*/
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
*/
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
*/
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);
}; };

View File

@ -0,0 +1,56 @@
#include "OmoteUI.hpp"
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_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_align(brightnessIcon, LV_ALIGN_TOP_LEFT, 0, 0);
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_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);
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_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(wakeupByIMUEnabled) 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_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_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_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);
}

View File

@ -0,0 +1,26 @@
#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

@ -0,0 +1,23 @@
#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

@ -0,0 +1,312 @@
#include "OmoteUI.hpp"
#define WIFI_SUBPAGE_SIZE 3
static char* ssid;
lv_obj_t* OmoteUI::create_wifi_selection_page(lv_obj_t* menu)
{
/* Create sub page for wifi*/
lv_obj_t* subpage = lv_menu_page_create(menu, NULL);
this->wifi_setting_cont = lv_menu_cont_create(subpage);
lv_obj_set_layout(this->wifi_setting_cont, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(this->wifi_setting_cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_scrollbar_mode(this->wifi_setting_cont, LV_SCROLLBAR_MODE_ACTIVE);
lv_obj_t* menuLabel = lv_label_create(this->wifi_setting_cont);
lv_label_set_text(menuLabel, "Searching for wifi networks");
return subpage;
}
/**
* @brief Callback function for the show password checkbox. Checking the box will show the password while unchecked the
* password will be shown as dots.
*
* @param e Pointer to event object for the event where this callback is called
*/
static void show_password_cb(lv_event_t* e)
{
lv_obj_t* password_field = (lv_obj_t*) e->user_data;
if (lv_obj_has_state(e->target, LV_STATE_CHECKED)){
lv_textarea_set_password_mode(password_field, false);
}
else{
lv_textarea_set_password_mode(password_field, true);
}
}
/**
* @brief Textarea callback function for the password field. In case the enter key is pressed in the text area, the
* function will try to connect to the network with the provided password.
*
* @param e Pointer to event object for the event where this callback is called
*/
void OmoteUI::password_field_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);
const char* password = lv_textarea_get_text(ta);
switch(code){
case LV_EVENT_READY:
this->mHardware->wifi()->connect(std::make_shared<std::string>(std::string(ssid)), std::make_shared<std::string>(std::string(password)));
lv_obj_clear_state(ta, LV_STATE_FOCUSED);
this->hide_keyboard();
this->reset_settings_menu();
/* Fall through on purpose. Pressing enter should disable the keyboard as well*/
default:
break;
}
}
/**
* @brief Callback which is triggered when clicking the connect button. It triggers the wifi connection.
*
* @param event Pointer to event object for the event where this callback is called
*/
void OmoteUI::connect_btn_cb(lv_event_t* event)
{
lv_obj_t* ta = (lv_obj_t*) event->user_data;
const char* password = lv_textarea_get_text(ta);
this->mHardware->wifi()->connect(std::make_shared<std::string>(std::string(ssid)), std::make_shared<std::string>(std::string(password)));
//Trigger wifi connection here
//wifihandler.connect(ssid, password);
lv_obj_clear_state(ta, LV_STATE_FOCUSED);
this->hide_keyboard();
this->reset_settings_menu();
}
void OmoteUI::create_wifi_main_page(lv_obj_t* parent)
{
lv_obj_t* menuLabel = lv_label_create(parent);
lv_label_set_text(menuLabel, "Wi-Fi");
this->wifiOverview = lv_obj_create(parent);
lv_obj_set_size(this->wifiOverview, lv_pct(100), 80);
lv_obj_set_style_bg_color(this->wifiOverview, color_primary, LV_PART_MAIN);
lv_obj_set_style_border_width(this->wifiOverview, 0, LV_PART_MAIN);
menuLabel = lv_label_create(this->wifiOverview);
lv_obj_t* arrow = lv_label_create(this->wifiOverview);
lv_label_set_text(arrow, LV_SYMBOL_RIGHT);
lv_obj_align(arrow, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_t* ip_label = lv_label_create(this->wifiOverview);
lv_label_set_text(ip_label, "IP:");
lv_obj_align(ip_label, LV_ALIGN_BOTTOM_LEFT, 0, 0);
lv_obj_t* ip = lv_label_create(this->wifiOverview);
lv_obj_align(ip, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
lv_label_set_text(menuLabel, "Disconnected");
lv_label_set_text(ip, "-");
lv_menu_set_load_page_event(this->settingsMenu, this->wifiOverview, this->wifi_selection_page);
lv_obj_add_event_cb(this->wifiOverview, [] (lv_event_t* e) {mInstance->wifi_settings_cb(e);}, LV_EVENT_CLICKED, this->wifi_setting_cont);
}
void OmoteUI::wifi_scan_done(std::shared_ptr<std::vector<WifiInfo>> info)
{
unsigned int size = info->size();
this->no_subpages = (size + WIFI_SUBPAGE_SIZE - 1)/WIFI_SUBPAGE_SIZE;
this->no_wifi_networks = size;
this->found_wifi_networks = info;
if (size == 0)
{
lv_obj_t* menuBox = lv_obj_create(this->wifi_setting_cont);
lv_obj_set_size(menuBox, lv_pct(100), 45);
lv_obj_set_scrollbar_mode(menuBox, LV_SCROLLBAR_MODE_OFF);
lv_obj_t* menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "no networks found");
}
else
{
this->update_wifi_selection_subpage(0);
}
}
void OmoteUI::next_wifi_selection_subpage(lv_event_t* e)
{
int subpage = (uintptr_t) lv_event_get_user_data(e);
this->update_wifi_selection_subpage(subpage);
}
/**
* @brief Callback function in case a wifi is selected. This callback function will change the label of the wifi password
* sub page to the selected wifi network.
*
* @param e Pointer to event object for the event where this callback is called
*/
static void wifi_selected_cb(lv_event_t* e)
{
lv_obj_t* label = lv_obj_get_child(e->target, 0);
lv_label_set_text((lv_obj_t*) e->user_data, lv_label_get_text(label));
ssid = lv_label_get_text(label);
}
void OmoteUI::update_wifi_selection_subpage(int page)
{
if (page < this->no_subpages)
{
lv_obj_clean(this->wifi_setting_cont);
lv_obj_t* pageLabel = lv_label_create(this->wifi_setting_cont);
lv_label_set_text_fmt(pageLabel, "Page %d/%d", page + 1, this->no_subpages);
if (page > 0)
{
lv_obj_t* menuBox = lv_obj_create(this->wifi_setting_cont);
lv_obj_set_size(menuBox, lv_pct(100), 45);
lv_obj_set_scrollbar_mode(menuBox, LV_SCROLLBAR_MODE_OFF);
lv_obj_t* menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Previous");
lv_obj_align(menuLabel, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_add_event_cb(menuBox, [](lv_event_t* e) {mInstance->next_wifi_selection_subpage(e);},LV_EVENT_CLICKED, (void*)(page - 1));
lv_obj_t* arrow = lv_label_create(menuBox);
lv_label_set_text(arrow, LV_SYMBOL_LEFT);
lv_obj_align(arrow, LV_ALIGN_TOP_LEFT, 0, 0);
}
for (int i = 0; i < WIFI_SUBPAGE_SIZE && (page*WIFI_SUBPAGE_SIZE + i) < this->no_wifi_networks; i++)
{
lv_obj_t* menuBox = lv_obj_create(this->wifi_setting_cont);
lv_obj_set_size(menuBox, lv_pct(100), 45);
lv_obj_set_scrollbar_mode(menuBox, LV_SCROLLBAR_MODE_OFF);
lv_obj_add_flag(menuBox, LV_OBJ_FLAG_EVENT_BUBBLE);
lv_obj_t* menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, this->found_wifi_networks->at(page*WIFI_SUBPAGE_SIZE + i).ssid.c_str());
lv_obj_t* wifi_image;
int RSSI = this->found_wifi_networks->at(page*WIFI_SUBPAGE_SIZE + i).rssi;
if (RSSI > -50)
{
wifi_image = imgs.addWifiHighSignal(menuBox);
}
else if (RSSI > -60)
{
wifi_image = imgs.addWifiMidSignal(menuBox);
}
else if (RSSI > -70)
{
wifi_image = imgs.addWifiLowSignal(menuBox);
}
else
{
wifi_image = imgs.addWifiLowSignal(menuBox);
}
lv_obj_align(wifi_image, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_menu_set_load_page_event(this->settingsMenu, menuBox, this->wifi_password_page);
lv_obj_add_event_cb(menuBox, wifi_selected_cb, LV_EVENT_CLICKED, this->wifi_password_label);
}
if ((page + 1) < this->no_subpages)
{
lv_obj_t* menuBox = lv_obj_create(this->wifi_setting_cont);
lv_obj_set_size(menuBox, lv_pct(100), 45);
lv_obj_set_scrollbar_mode(menuBox, LV_SCROLLBAR_MODE_OFF);
lv_obj_t* menuLabel = lv_label_create(menuBox);
lv_label_set_text(menuLabel, "Next");
lv_obj_add_event_cb(menuBox, [](lv_event_t* e) {mInstance->next_wifi_selection_subpage(e);}, LV_EVENT_CLICKED, (void*)(page + 1));
lv_obj_t* arrow = lv_label_create(menuBox);
lv_label_set_text(arrow, LV_SYMBOL_RIGHT);
lv_obj_align(arrow, LV_ALIGN_TOP_RIGHT, 0, 0);
}
lv_obj_scroll_to_y(this->wifi_setting_cont, 0, LV_ANIM_OFF);
}
}
void OmoteUI::create_wifi_settings(lv_obj_t* menu, lv_obj_t* parent)
{
this->wifi_selection_page = this->create_wifi_selection_page(menu);
this->wifi_password_page = this->create_wifi_password_page(this->settingsMenu);
this->create_wifi_main_page(parent);
this->mHardware->wifi()->onScanDone([] (std::shared_ptr<std::vector<WifiInfo>> info) {mInstance->wifi_scan_done(info);});
this->mHardware->wifi()->onStatusUpdate([] (std::shared_ptr<wifiStatus> status) {mInstance->wifi_status(status);});
}
void OmoteUI::wifi_status(std::shared_ptr<wifiStatus> status)
{
this->mHardware->debugPrint("connected %d\n", status->isConnected);
this->mHardware->debugPrint("IP %s\n", status->IP);
this->mHardware->debugPrint("SSID %s\n", status->ssid);
lv_obj_t* ip_label = lv_obj_get_child(this->wifiOverview, 3);
lv_obj_t* ssid_label = lv_obj_get_child(this->wifiOverview, 0);
if (status->isConnected)
{
lv_label_set_text(this->WifiLabel, LV_SYMBOL_WIFI);
lv_label_set_text(ssid_label, status->ssid.c_str());
lv_label_set_text(ip_label, status->IP.c_str());
}
else
{
lv_label_set_text(this->WifiLabel, "");
lv_label_set_text(ssid_label, "Disconnected");
lv_label_set_text(ip_label, "-");
}
}
lv_obj_t* OmoteUI::create_wifi_password_page(lv_obj_t* menu)
{
lv_obj_t* ret_val = lv_menu_page_create(menu, NULL);
lv_obj_t* cont = lv_menu_cont_create(ret_val);
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);
this->wifi_password_label = lv_label_create(cont);
lv_label_set_text(this->wifi_password_label, "Password");
lv_obj_t* password_input = lv_textarea_create(cont);
lv_obj_set_width(password_input, lv_pct(100));
lv_textarea_set_password_mode(password_input, true);
lv_textarea_set_one_line(password_input, true);
lv_textarea_set_placeholder_text(password_input, "Password");
lv_obj_add_event_cb(password_input, [] (lv_event_t* e) {mInstance->password_field_event_cb(e);} , LV_EVENT_READY, NULL );
this->attach_keyboard(password_input);
lv_obj_t* show_password = lv_checkbox_create(cont);
lv_checkbox_set_text(show_password, "Show password");
lv_obj_add_event_cb(show_password, show_password_cb, LV_EVENT_VALUE_CHANGED, password_input);
lv_obj_t* connect_button = lv_btn_create(cont);
lv_obj_t* label = lv_label_create(connect_button);
lv_label_set_text(label, "Connect");
lv_obj_add_event_cb(connect_button,[] (lv_event_t* e) { mInstance->connect_btn_cb(e);}, LV_EVENT_CLICKED, password_input);
return ret_val;
}
/**
* @brief Callback which is triggered when the wifi settings are opened (the wifi settings are pressed in the settings
* main page). This function will trigger the asynchronous scan for wifi networks and update the label of the wifi
* selection page to indicate that wifi networks are being searched for. The wifi event callback function then has to
* call the API function to fill the page with the found networks.
*
* @param event Pointer to event object for the event where this callback is called
*/
void OmoteUI::wifi_settings_cb(lv_event_t* event)
{
lv_obj_t* cont = (lv_obj_t*) lv_event_get_user_data(event);
lv_obj_clean(cont);
lv_obj_t* label = lv_label_create(cont);
lv_label_set_text(label, "Searching for wifi networks");
mHardware->debugPrint("Wifi settings cb called\n");
mHardware->wifi()->scan();
//This will trigger an asynchronouse network scan
// We need to trigger wifi search via HAL
//wifihandler.scan();
}

View File

@ -40,13 +40,17 @@ build_flags =
; ------------- Includes ------------------------------------ ; ------------- Includes ------------------------------------
-I OmoteUI -I OmoteUI
-I HAL -I HAL
-I HAL/Interface -I HAL/HardwareModules
lib_deps = lib_deps =
lvgl/lvgl@^8.3.9 ;lvgl/lvgl@^8.3.9
lvgl=https://github.com/lvgl/lvgl/archive/refs/tags/v8.3.9.zip
lib_archive = false lib_archive = false
build_src_filter = build_src_filter =
+<../OmoteUI/*> +<../OmoteUI/*>
+<../HAL/HardwareAbstract.cpp>
+<../HAL/HardwareModules/*.cpp>
@ -62,6 +66,7 @@ lib_deps =
${env.lib_deps} ${env.lib_deps}
sparkfun/SparkFun LIS3DH Arduino Library@^1.0.3 sparkfun/SparkFun LIS3DH Arduino Library@^1.0.3
crankyoldgit/IRremoteESP8266@^2.8.4 crankyoldgit/IRremoteESP8266@^2.8.4
adafruit/Adafruit BusIO @ 1.9.6
adafruit/Adafruit FT6206 Library@^1.0.6 adafruit/Adafruit FT6206 Library@^1.0.6
bodmer/TFT_eSPI@^2.5.23 bodmer/TFT_eSPI@^2.5.23
knolleary/PubSubClient@^2.8 knolleary/PubSubClient@^2.8
@ -95,6 +100,9 @@ build_flags =
; ------------- Includes -------------------------------------------- ; ------------- Includes --------------------------------------------
-I HAL/Targets/ESP32 -I HAL/Targets/ESP32
-I HAL/Targets/ESP32/battery
-I HAL/Targets/ESP32/display
-I HAL/Targets/ESP32/wifiHandler
build_unflags = build_unflags =
-std=gnu++11 -std=gnu++11
@ -116,6 +124,7 @@ build_flags =
-D LV_LVGL_H_INCLUDE_SIMPLE -D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_DRV_NO_CONF -D LV_DRV_NO_CONF
-D USE_SDL -D USE_SDL
-D SDL_MAIN_HANDLED
-D SDL_HOR_RES=SCREEN_WIDTH -D SDL_HOR_RES=SCREEN_WIDTH
-D SDL_VER_RES=SCREEN_HEIGHT -D SDL_VER_RES=SCREEN_HEIGHT
-D SDL_ZOOM=1 -D SDL_ZOOM=1
@ -123,6 +132,7 @@ build_flags =
; -------------- Sim Includes --------------------------- ; -------------- Sim Includes ---------------------------
-I HAL/Targets/Simulator -I HAL/Targets/Simulator
-I HAL/Targets/Simulator/wifiHandlerSim
debug_build_flags = debug_build_flags =
-g ;Allow debugging in vscode -g ;Allow debugging in vscode
@ -135,6 +145,6 @@ lib_deps =
build_src_filter = build_src_filter =
+<simMain.cpp> +<simMain.cpp>
+<../HAL/Targets/Simulator/*> +<../HAL/Targets/Simulator/*>
+<../OmoteUI/*> ${env.build_src_filter}
; Force compile LVGL demo, remove when working on your own project ; Force compile LVGL demo, remove when working on your own project

View File

@ -89,6 +89,8 @@ Distributed under the GPL v3 License. See [LICENSE](https://github.com/CoretechR
## Contact ## Contact
Maximilian Kern - [kernm.de](kernm.de) Maximilian Kern - [kernm.de](https://kernm.de)
Project Link: [https://hackaday.io/project/191752-omote-diy-universal-remote](https://hackaday.io/project/191752-omote-diy-universal-remote) Omote Discord: [https://discord.gg/5PnYFAsKsG](https://discord.gg/5PnYFAsKsG)
Project Page on Hackaday.io: [https://hackaday.io/project/191752-omote-diy-universal-remote](https://hackaday.io/project/191752-omote-diy-universal-remote)