Compare commits

...

18 commits

Author SHA1 Message Date
Alexander Wellbrock 4a4622ae32
Merge d67323957c into 3a0d673df4 2024-08-05 21:30:25 +02:00
Jean-François Milants 3a0d673df4 Display the SPI flash JEDEC IDs in SystemInformation.
Some checks failed
CI / build-firmware (push) Successful in 5m33s
CI / build-simulator (push) Failing after 2s
CI / get-base-ref-size (push) Has been skipped
CI / Compare build size (push) Has been skipped
This is needed since a new memory chip will be used in future batches of PineTimes.
2024-08-05 20:34:41 +02:00
mark9064 53dc9dafe7 aod: simplify AOD disablement based on notification status 2024-08-05 20:32:43 +02:00
mark9064 a407902b06 aod: avoid spinning DisplayApp under high LVGL load 2024-08-05 20:32:43 +02:00
mark9064 3e8accde69 aod: run LVGL task handler until all work finished 2024-08-05 20:32:43 +02:00
mark9064 2bb611db8e aod: constant frequency idle frames 2024-08-05 20:32:43 +02:00
mark9064 ef88e8165c aod: porch control: 2Hz idle + 75Hz on 2024-08-05 20:32:43 +02:00
mark9064 da9ab4a7b4 aod: lower lcd voltage 2024-08-05 20:32:43 +02:00
John Crawford 0bcd7e0009 aod: lower voltage going to the display 2024-08-05 20:32:43 +02:00
mark9064 bf69e0dcc5 aod: fix flashlight brightness restore 2024-08-05 20:32:43 +02:00
mark9064 947c4f5067 aod: fix brightness getting stuck high 2024-08-05 20:32:43 +02:00
John Crawford 0960d67001 aod: lower refresh rate when always on 2024-08-05 20:32:43 +02:00
John Crawford 5385f7e275 aod: switch to 8 colors when always on 2024-08-05 20:32:43 +02:00
John Crawford e884b053d3 aod: disable while in notification sleep 2024-08-05 20:32:43 +02:00
John Crawford 85a2181b64 aod: integrate with display timeout 2024-08-05 20:32:43 +02:00
mark9064 3dca742b65 aod: PPI/RTC-based backlight brightness 2024-08-05 20:32:43 +02:00
KaffeinatedKat 20ac7e8df3 feat: always on display 2024-08-05 20:32:43 +02:00
w4tsn d67323957c
watchfaces: add LCARS
This adds an LCARS watchface, a UI known from the Star Trek series.

The following issues are still open and I might need a hand with these:

- the background image is around 170 kB. I suspect because of it's size
  (240x240) and the embedded text. This could be reduced by a lot I
  guess if converted to point-rendering in lvgl similar to Infineat
- there are alignment issues currently only solvable through calling the
  lv_obj_align function (again) after calling the local label_make
  function, which I added to reduce duplicate code
- coming from the settings screen the watchface loads quite slowly,
  probably because of the size of the background image
- needs rebase to latest development branch, currently tracks 1.14.0
2024-06-03 18:24:43 +02:00
33 changed files with 1426 additions and 45 deletions

View file

@ -423,6 +423,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/WatchFaceAnalog.cpp
displayapp/screens/WatchFaceDigital.cpp
displayapp/screens/WatchFaceInfineat.cpp
displayapp/screens/WatchFaceLCARS.cpp
displayapp/screens/WatchFaceTerminal.cpp
displayapp/screens/WatchFacePineTimeStyle.cpp
displayapp/screens/WatchFaceCasioStyleG7710.cpp

View file

@ -2,38 +2,138 @@
#include <hal/nrf_gpio.h>
#include "displayapp/screens/Symbols.h"
#include "drivers/PinMap.h"
#include <libraries/delay/nrf_delay.h>
using namespace Pinetime::Controllers;
namespace {
// reinterpret_cast is not constexpr so this is the best we can do
static NRF_RTC_Type* const RTC = reinterpret_cast<NRF_RTC_Type*>(NRF_RTC2_BASE);
}
void BrightnessController::Init() {
nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
nrf_gpio_cfg_output(PinMap::LcdBacklightHigh);
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
static_assert(timerFrequency == 32768, "Change the prescaler below");
RTC->PRESCALER = 0;
// CC1 switches the backlight on (pin transitions from high to low) and resets the counter to 0
RTC->CC[1] = timerPeriod;
// Enable compare events for CC0,CC1
RTC->EVTEN = 0b0000'0000'0000'0011'0000'0000'0000'0000;
// Disable all interrupts
RTC->INTENCLR = 0b0000'0000'0000'1111'0000'0000'0000'0011;
Set(level);
}
void BrightnessController::ApplyBrightness(uint16_t rawBrightness) {
// The classic off, low, medium, high brightnesses are at {0, timerPeriod, timerPeriod*2, timerPeriod*3}
// These brightness levels do not use PWM: they only set/clear the corresponding pins
// Any brightness level between the above levels is achieved with efficient RTC based PWM on the next pin up
// E.g 2.5*timerPeriod corresponds to medium brightness with 50% PWM on the high pin
// Note: Raw brightness does not necessarily correspond to a linear perceived brightness
uint8_t pin;
if (rawBrightness > 2 * timerPeriod) {
rawBrightness -= 2 * timerPeriod;
pin = PinMap::LcdBacklightHigh;
} else if (rawBrightness > timerPeriod) {
rawBrightness -= timerPeriod;
pin = PinMap::LcdBacklightMedium;
} else {
pin = PinMap::LcdBacklightLow;
}
if (rawBrightness == timerPeriod || rawBrightness == 0) {
if (lastPin != UNSET) {
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
nrf_gpio_cfg_output(lastPin);
}
lastPin = UNSET;
if (rawBrightness == 0) {
nrf_gpio_pin_set(pin);
} else {
nrf_gpio_pin_clear(pin);
}
} else {
// If the pin on which we are doing PWM is changing
// Disable old PWM channel (if exists) and set up new one
if (lastPin != pin) {
if (lastPin != UNSET) {
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
nrf_gpio_cfg_output(lastPin);
}
nrfx_gpiote_out_config_t gpioteCfg = {.action = NRF_GPIOTE_POLARITY_TOGGLE,
.init_state = NRF_GPIOTE_INITIAL_VALUE_LOW,
.task_pin = true};
APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg));
nrfx_gpiote_out_task_enable(pin);
nrf_ppi_channel_endpoint_setup(ppiBacklightOff,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[0]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[1]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast<uint32_t>(&RTC->TASKS_CLEAR));
nrf_ppi_channel_enable(ppiBacklightOff);
nrf_ppi_channel_enable(ppiBacklightOn);
} else {
// If the pin used for PWM isn't changing, we only need to set the pin state to the initial value (low)
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
// Due to errata 20,179 and the intricacies of RTC timing, keep it simple: override the pin state
nrfx_gpiote_out_task_force(pin, false);
}
// CC0 switches the backlight off (pin transitions from low to high)
RTC->CC[0] = rawBrightness;
RTC->TASKS_CLEAR = 1;
RTC->TASKS_START = 1;
lastPin = pin;
}
switch (pin) {
case PinMap::LcdBacklightHigh:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
break;
case PinMap::LcdBacklightMedium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break;
case PinMap::LcdBacklightLow:
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
}
}
void BrightnessController::Set(BrightnessController::Levels level) {
this->level = level;
switch (level) {
default:
case Levels::High:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
ApplyBrightness(3 * timerPeriod);
break;
case Levels::Medium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
ApplyBrightness(2 * timerPeriod);
break;
case Levels::Low:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
ApplyBrightness(timerPeriod);
break;
case Levels::AlwaysOn:
ApplyBrightness(timerPeriod / 10);
break;
case Levels::Off:
nrf_gpio_pin_set(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
ApplyBrightness(0);
break;
}
}

View file

@ -2,11 +2,14 @@
#include <cstdint>
#include "nrf_ppi.h"
#include "nrfx_gpiote.h"
namespace Pinetime {
namespace Controllers {
class BrightnessController {
public:
enum class Levels { Off, Low, Medium, High };
enum class Levels { Off, AlwaysOn, Low, Medium, High };
void Init();
void Set(Levels level);
@ -20,6 +23,25 @@ namespace Pinetime {
private:
Levels level = Levels::High;
static constexpr uint8_t UNSET = UINT8_MAX;
uint8_t lastPin = UNSET;
// Maximum time (μs) it takes for the RTC to fully stop
static constexpr uint8_t rtcStopTime = 46;
// Frequency of timer used for PWM (Hz)
static constexpr uint16_t timerFrequency = 32768;
// Backlight PWM frequency (Hz)
static constexpr uint16_t pwmFreq = 1000;
// Wraparound point in timer ticks
// Defines the number of brightness levels between each pin
static constexpr uint16_t timerPeriod = timerFrequency / pwmFreq;
// Warning: nimble reserves some PPIs
// https://github.com/InfiniTimeOrg/InfiniTime/blob/034d83fe6baf1ab3875a34f8cee387e24410a824/src/libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c#L53
// SpiMaster uses PPI 0 for an erratum workaround
// Channel 1, 2 should be free to use
static constexpr nrf_ppi_channel_t ppiBacklightOn = NRF_PPI_CHANNEL1;
static constexpr nrf_ppi_channel_t ppiBacklightOff = NRF_PPI_CHANNEL2;
void ApplyBrightness(uint16_t val);
};
}
}

View file

@ -214,6 +214,21 @@ namespace Pinetime {
return settings.screenTimeOut;
};
bool GetAlwaysOnDisplay() const {
return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep;
};
void SetAlwaysOnDisplaySetting(bool state) {
if (state != settings.alwaysOnDisplay) {
settingsChanged = true;
}
settings.alwaysOnDisplay = state;
}
bool GetAlwaysOnDisplaySetting() const {
return settings.alwaysOnDisplay;
}
void SetShakeThreshold(uint16_t thresh) {
if (settings.shakeWakeThreshold != thresh) {
settings.shakeWakeThreshold = thresh;
@ -286,13 +301,15 @@ namespace Pinetime {
private:
Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0007;
static constexpr uint32_t settingsVersion = 0x0008;
struct SettingsData {
uint32_t version = settingsVersion;
uint32_t stepsGoal = 10000;
uint32_t screenTimeOut = 15000;
bool alwaysOnDisplay = false;
ClockType clockType = ClockType::H24;
WeatherFormat weatherFormat = WeatherFormat::Metric;
Notification notificationStatus = Notification::On;

View file

@ -81,7 +81,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem)
Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: lcd {lcd},
touchPanel {touchPanel},
batteryController {batteryController},
@ -97,6 +98,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
brightnessController {brightnessController},
touchHandler {touchHandler},
filesystem {filesystem},
spiNorFlash {spiNorFlash},
lvgl {lcd, filesystem},
timer(this, TimerCallback),
controllers {batteryController,
@ -154,6 +156,36 @@ void DisplayApp::InitHw() {
lcd.Init();
}
TickType_t DisplayApp::CalculateSleepTime() {
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
// Divide both the numerator and denominator by 8 to increase the number of ticks (frames) before the overflow tick is reached
TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8);
// ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours
constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
// Assumptions
// Tick rate is multiple of 8
// Needed for division trick above
static_assert(configTICK_RATE_HZ % 8 == 0);
// Local tick count must always wraparound before the system tick count does
// As a static assert we can use 64 bit ints and therefore dodge overflows
// Always on overflow time (ms) < system tick overflow time (ms)
static_assert((uint64_t) overflowTick * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
if (alwaysOnTickCount == overflowTick) {
alwaysOnTickCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
}
if (elapsedTarget > ticksElapsed) {
return elapsedTarget - ticksElapsed;
} else {
return 0;
}
}
void DisplayApp::Refresh() {
auto LoadPreviousScreen = [this]() {
FullRefreshDirections returnDirection;
@ -203,7 +235,29 @@ void DisplayApp::Refresh() {
TickType_t queueTimeout;
switch (state) {
case States::Idle:
queueTimeout = portMAX_DELAY;
if (settingsController.GetAlwaysOnDisplay()) {
if (!currentScreen->IsRunning()) {
LoadPreviousScreen();
}
// Check we've slept long enough
// Might not be true if the loop received an event
// If not true, then wait that amount of time
queueTimeout = CalculateSleepTime();
if (queueTimeout == 0) {
// Only advance the tick count when LVGL is done
// Otherwise keep running the task handler while it still has things to draw
// Note: under high graphics load, LVGL will always have more work to do
if (lv_task_handler() > 0) {
// Drop frames that we've missed if drawing/event handling took way longer than expected
while (queueTimeout == 0) {
alwaysOnTickCount += 1;
queueTimeout = CalculateSleepTime();
}
};
}
} else {
queueTimeout = portMAX_DELAY;
}
break;
case States::Running:
if (!currentScreen->IsRunning()) {
@ -234,20 +288,34 @@ void DisplayApp::Refresh() {
case Messages::DimScreen:
DimScreen();
break;
case Messages::RestoreBrightness:
RestoreBrightness();
break;
case Messages::GoToSleep:
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
brightnessController.Lower();
vTaskDelay(100);
}
lcd.Sleep();
// Don't actually turn off the display for AlwaysOn mode
if (settingsController.GetAlwaysOnDisplay()) {
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
lcd.LowPowerOn();
// Record idle entry time
alwaysOnTickCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
} else {
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
lcd.Sleep();
}
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
break;
case Messages::GoToRunning:
lcd.Wakeup();
if (settingsController.GetAlwaysOnDisplay()) {
lcd.LowPowerOff();
} else {
lcd.Wakeup();
}
lv_disp_trig_activity(nullptr);
ApplyBrightness();
state = States::Running;
@ -535,7 +603,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
bleController,
watchdog,
motionController,
touchPanel);
touchPanel,
spiNorFlash);
break;
case Apps::FlashLight:
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);

View file

@ -66,7 +66,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem);
Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start(System::BootErrors error);
void PushMessage(Display::Messages msg);
@ -96,6 +97,7 @@ namespace Pinetime {
Pinetime::Controllers::BrightnessController& brightnessController;
Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::FS& filesystem;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Controllers::FirmwareValidator validator;
Pinetime::Components::LittleVgl lvgl;
@ -135,6 +137,13 @@ namespace Pinetime {
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
bool isDimmed = false;
TickType_t CalculateSleepTime();
TickType_t alwaysOnTickCount;
TickType_t alwaysOnStartTime;
// If this is to be changed, make sure the actual always on refresh rate is changed
// by configuring the LCD refresh timings
static constexpr uint32_t alwaysOnRefreshPeriod = 500;
};
}
}

View file

@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& /*alarmController*/,
Pinetime::Controllers::BrightnessController& /*brightnessController*/,
Pinetime::Controllers::TouchHandler& /*touchHandler*/,
Pinetime::Controllers::FS& /*filesystem*/)
Pinetime::Controllers::FS& /*filesystem*/,
Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/)
: lcd {lcd}, bleController {bleController} {
}

View file

@ -18,6 +18,7 @@ namespace Pinetime {
class St7789;
class Cst816S;
class Watchdog;
class SpiNorFlash;
}
namespace Controllers {
@ -59,7 +60,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem);
Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start();
void Start(Pinetime::System::BootErrors) {

View file

@ -18,7 +18,7 @@ namespace Pinetime {
TimerDone,
BleFirmwareUpdateStarted,
DimScreen,
RestoreBrightness,
NotifyDeviceActivity,
ShowPairingKey,
AlarmTriggered,
Chime,

View file

@ -12,6 +12,7 @@
#include "displayapp/screens/WatchFaceAnalog.h"
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFaceLCARS.h"
#include "displayapp/screens/WatchFacePineTimeStyle.h"
#include "displayapp/screens/WatchFaceTerminal.h"

View file

@ -51,6 +51,7 @@ namespace Pinetime {
PineTimeStyle,
Terminal,
Infineat,
LCARS,
CasioStyleG7710,
};

View file

@ -26,6 +26,7 @@ else()
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::LCARS")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710")
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
endif()

View file

@ -17,6 +17,7 @@ namespace {
FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController)
: systemTask {systemTask}, brightnessController {brightnessController} {
previousBrightnessLevel = brightnessController.Level();
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
flashLight = lv_label_create(lv_scr_act(), nullptr);
@ -52,6 +53,7 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
FlashLight::~FlashLight() {
lv_obj_clean(lv_scr_act());
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
brightnessController.Set(previousBrightnessLevel);
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
}

View file

@ -27,6 +27,7 @@ namespace Pinetime {
Controllers::BrightnessController& brightnessController;
Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High;
Controllers::BrightnessController::Levels previousBrightnessLevel;
lv_obj_t* flashLight;
lv_obj_t* backgroundAction;

View file

@ -38,7 +38,8 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
const Pinetime::Controllers::Ble& bleController,
const Pinetime::Drivers::Watchdog& watchdog,
Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel)
const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: app {app},
dateTimeController {dateTimeController},
batteryController {batteryController},
@ -47,6 +48,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
watchdog {watchdog},
motionController {motionController},
touchPanel {touchPanel},
spiNorFlash {spiNorFlash},
screens {app,
0,
{[this]() -> std::unique_ptr<Screen> {
@ -186,10 +188,12 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label, true);
const auto& bleAddr = bleController.Address();
auto spiFlashId = spiNorFlash.GetIdentification();
lv_label_set_text_fmt(label,
"#808080 BLE MAC#\n"
" %02x:%02x:%02x:%02x:%02x:%02x"
" %02x:%02x:%02x:%02x:%02x:%02x\n"
"\n"
"#808080 SPI Flash# %02x-%02x-%02x\n"
"\n"
"#808080 Memory heap#\n"
" #808080 Free# %d\n"
@ -202,6 +206,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
bleAddr[2],
bleAddr[1],
bleAddr[0],
spiFlashId.manufacturer,
spiFlashId.type,
spiFlashId.density,
xPortGetFreeHeapSize(),
xPortGetMinimumEverFreeHeapSize(),
mallocFailedCount,

View file

@ -29,7 +29,8 @@ namespace Pinetime {
const Pinetime::Controllers::Ble& bleController,
const Pinetime::Drivers::Watchdog& watchdog,
Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel);
const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash);
~SystemInfo() override;
bool OnTouchEvent(TouchEvents event) override;
@ -42,6 +43,7 @@ namespace Pinetime {
const Pinetime::Drivers::Watchdog& watchdog;
Pinetime::Controllers::MotionController& motionController;
const Pinetime::Drivers::Cst816S& touchPanel;
const Pinetime::Drivers::SpiNorFlash& spiNorFlash;
ScreenList<5> screens;

View file

@ -0,0 +1,345 @@
#include "displayapp/screens/WatchFaceLCARS.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/BleIcon.h"
#include "components/settings/Settings.h"
#include "components/battery/BatteryController.h"
#include "components/heartrate/HeartRateController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/motion/MotionController.h"
using namespace Pinetime::Applications::Screens;
namespace {
void set_label_text_from_uint(lv_obj_t* label, uint8_t number) {
std::string number_as_string = std::to_string(number);
char* number_as_char = new char[number_as_string.length() +1];
std::strcpy(number_as_char, number_as_string.c_str());
lv_label_set_text_static(label, number_as_char);
}
void set_label_color(lv_obj_t* label, lv_color_t color) {
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
}
lv_obj_t* label_make(lv_obj_t* container, uint8_t position_x, uint8_t position_y, lv_color_t color, uint8_t align, const char* text) {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
lv_label_set_text_static(label, text);
lv_obj_align(label, container, align, position_x, position_y);
return label;
}
lv_obj_t* label_make_with_font(lv_obj_t* container, uint8_t position_x, uint8_t position_y, lv_color_t color, lv_font_t* font, uint8_t align, const char* text) {
lv_obj_t* label = label_make(container, position_x, position_y, color, align, text);
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
lv_obj_realign(label);
return label;
}
lv_obj_t* label_container_make(lv_obj_t* parent, uint8_t position_x, uint8_t position_y, uint8_t size_x, uint8_t size_y, uint8_t align) {
lv_obj_t* container = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(container, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_size(container, size_x, size_y);
lv_obj_align(container, parent, align, position_x, position_y);
return container;
}
}
WatchFaceLCARS::WatchFaceLCARS(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController,
Controllers::HeartRateController& heartRateController,
Controllers::FS& filesystem)
: currentDateTime {{}},
currentNanoSeconds {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificationManager {notificationManager},
settingsController {settingsController},
motionController {motionController},
heartRateController {heartRateController} {
// Fonts
lfs_file f = {};
if (filesystem.FileOpen(&f, "/fonts/antonio_78.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_antonio_78 = lv_font_load("F:/fonts/antonio_78.bin");
}
if (filesystem.FileOpen(&f, "/fonts/antonio_33.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_antonio_33 = lv_font_load("F:/fonts/antonio_33.bin");
}
if (filesystem.FileOpen(&f, "/fonts/antonio_21.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_antonio_21 = lv_font_load("F:/fonts/antonio_21.bin");
}
if (filesystem.FileOpen(&f, "/fonts/antonio_13.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_antonio_13 = lv_font_load("F:/fonts/antonio_13.bin");
}
// Background
background = lv_img_create(lv_scr_act(), nullptr);
lv_img_set_src(background, "F:/images/LCARS.bin");
lv_obj_set_pos(background, 0, 0);
// System
system_container = label_container_make(lv_scr_act(), 0, 5, 170, 20, LV_ALIGN_IN_TOP_RIGHT);
lv_obj_align(system_container, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 5); // TODO: Find out why this align is necessary
labelBattery = label_make_with_font(system_container, 0, 0, grayColor, font_antonio_21, LV_ALIGN_IN_TOP_RIGHT, "0 %");
bleIcon = label_make(labelBattery, -5, 0, orangeColor, LV_ALIGN_OUT_LEFT_MID, Symbols::bluetooth);
lv_obj_align(bleIcon, labelBattery, LV_ALIGN_OUT_LEFT_MID, -5, 0); // TODO: Find out why this align is necessary
// Date
dateContainer = label_container_make(lv_scr_act(), 65, 50, 175, 20, LV_ALIGN_IN_TOP_LEFT);
labelDate = label_make_with_font(dateContainer, 0, 0, orangeColor, font_antonio_21, LV_ALIGN_IN_LEFT_MID, "12024 MON 01 MAR");
labelTimeAmPm = label_make_with_font(dateContainer, 0, 0, orangeColor, font_antonio_21, LV_ALIGN_IN_RIGHT_MID, "");
// Seconds Labels
label_seconds_container = label_container_make(lv_scr_act(), 65, 150, 90, 15, LV_ALIGN_IN_TOP_LEFT);
label_tens_container = label_container_make(label_seconds_container, 0, 0, 90, 15, LV_ALIGN_IN_TOP_LEFT);
for (int i = 0; i < 6; ++i) {
label_tens[i] = label_make_with_font(label_tens_container, 10 * i, 0, grayColor, font_antonio_13, LV_ALIGN_IN_LEFT_MID, "");
set_label_text_from_uint(label_tens[i], i);
}
label_ones_container = label_container_make(label_seconds_container, 0, 15, 90, 15, LV_ALIGN_IN_TOP_LEFT);
for (int i = 0; i < 10; ++i) {
label_ones[i] = label_make_with_font(label_ones_container, 10 * i, 0, grayColor, font_antonio_13, LV_ALIGN_IN_LEFT_MID, "");
set_label_text_from_uint(label_ones[i], i);
}
// Time
timeContainer = label_container_make(lv_scr_act(), 65, 76, 170, 60, LV_ALIGN_IN_TOP_LEFT);
labelTime = label_make_with_font(timeContainer, 0, 0, orangeColor, font_antonio_78, LV_ALIGN_IN_TOP_LEFT, "00:00");
// WK
label_week = label_make_with_font(lv_scr_act(), -5, 150, orangeColor, font_antonio_33, LV_ALIGN_IN_TOP_RIGHT, "WK00");
lv_obj_align(label_week, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 150); // TODO: Find out why this align is necessary
// Sensors
sensors_container = label_container_make(lv_scr_act(), 0, 0, 130, 50, LV_ALIGN_IN_BOTTOM_RIGHT);
lv_obj_align(sensors_container, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0); // TODO: Find out why this align is necessary
stepValue = label_make_with_font(sensors_container, -5, 0, orangeColor, font_antonio_21, LV_ALIGN_IN_BOTTOM_RIGHT, "0");
lv_obj_align(stepValue, sensors_container, LV_ALIGN_IN_BOTTOM_RIGHT, -5, 0); // TODO: Find out why this align is necessary
stepIcon = label_make(stepValue, -5, 0, orangeColor, LV_ALIGN_OUT_LEFT_MID, Symbols::shoe);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0); // TODO: Find out why this align is necessary
heartbeatValue = label_make_with_font(sensors_container, -5, -25, orangeColor, font_antonio_21, LV_ALIGN_IN_BOTTOM_RIGHT, "0");
lv_obj_align(heartbeatValue, sensors_container, LV_ALIGN_IN_BOTTOM_RIGHT, -5, -25); // TODO: Find out why this align is necessary
heartbeatIcon = label_make(stepValue, -25, 0, orangeColor, LV_ALIGN_OUT_LEFT_MID, "");
lv_obj_align(heartbeatIcon, heartbeatValue, LV_ALIGN_IN_BOTTOM_LEFT, -25, 0); // TODO: Find out why this align is necessary
// Tasks
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceLCARS::~WatchFaceLCARS() {
// Tasks
lv_task_del(taskRefresh);
// Fonts
if (font_antonio_78 != nullptr) {
lv_font_free(font_antonio_78);
}
if (font_antonio_33 != nullptr) {
lv_font_free(font_antonio_33);
}
if (font_antonio_21 != nullptr) {
lv_font_free(font_antonio_21);
}
if (font_antonio_13 != nullptr) {
lv_font_free(font_antonio_13);
}
// Objects
lv_obj_clean(lv_scr_act());
}
void WatchFaceLCARS::Refresh() {
currentNanoSeconds = std::chrono::time_point_cast<std::chrono::nanoseconds>(dateTimeController.CurrentDateTime());
if (currentNanoSeconds.IsUpdated()) {
UpdateSeconds();
UpdateStepCount();
UpdateBatteryPercent();
UpdateBLE();
UpdateHeartRate();
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(currentNanoSeconds.Get());
if (currentDateTime.IsUpdated()) {
UpdateTime();
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
UpdateStardate();
UpdateWK();
}
}
}
}
void WatchFaceLCARS::UpdateHeartRate() {
heartbeat = heartRateController.HeartRate();
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
if (heartbeat.Get() > 120) {
set_label_color(heartbeatValue, redColor);
set_label_color(heartbeatIcon, redColor);
} else {
set_label_color(heartbeatValue, orangeColor);
set_label_color(heartbeatIcon, orangeColor);
}
if (heartbeatRunning.Get()) {
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
} else {
lv_label_set_text_static(heartbeatValue, "");
lv_label_set_text_static(heartbeatIcon, "");
}
lv_obj_realign(heartbeatValue);
lv_obj_realign(heartbeatIcon);
}
}
void WatchFaceLCARS::UpdateTime() {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
char ampmChar[3] = "AM";
if (hour == 0) {
hour = 12;
} else if (hour == 12) {
ampmChar[0] = 'P';
} else if (hour > 12) {
hour = hour - 12;
ampmChar[0] = 'P';
}
lv_label_set_text(labelTimeAmPm, ampmChar);
weekNumberFormat = "%V";
} else {
weekNumberFormat = "%U";
}
lv_label_set_text_fmt(labelTime, "%2d:%02d", hour, minute);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_obj_realign(labelTimeAmPm);
lv_obj_realign(labelTime);
}
}
void WatchFaceLCARS::UpdateSeconds() {
uint8_t second = dateTimeController.Seconds();
if (last_second != second) {
last_second = second;
ResetSecondsDigits();
set_label_color(label_tens[second / 10], orangeColor);
set_label_color(label_ones[second % 10], orangeColor);
}
}
void WatchFaceLCARS::UpdateWK() {
time_t ttTime = std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(currentDate.Get()));
tm* tmTime = std::localtime(&ttTime);
char buffer[8];
strftime(buffer, 8, weekNumberFormat, tmTime);
uint8_t weekNumber = atoi(buffer);
lv_label_set_text_fmt(label_week, "WK%02d", weekNumber);
lv_obj_realign(label_week);
}
void WatchFaceLCARS::UpdateStardate() {
lv_label_set_text_fmt(
labelDate,
"1%d %s %d %s",
dateTimeController.Year(),
dateTimeController.DayOfWeekShortToString(),
dateTimeController.Day(),
dateTimeController.MonthShortToString()
);
lv_obj_realign(labelDate);
}
void WatchFaceLCARS::UpdateBatteryPercent() {
batteryPercentRemaining = batteryController.PercentRemaining();
isCharging = batteryController.IsCharging();
if (batteryController.IsCharging() ) { // Charging battery animation
chargingBatteryPercent += 1;
if (chargingBatteryPercent > 100) {
chargingBatteryPercent = batteryPercentRemaining.Get();
}
if (chargingBatteryPercent <= 100 && ((chargingBatteryPercent % 47) == 0)) {
SetBatteryLevel(batteryPercentRemaining.Get(), orangeColor);
}
if ((chargingBatteryPercent % 97) == 0) {
SetBatteryLevel(batteryPercentRemaining.Get(), grayColor);
}
} else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) {
chargingBatteryPercent = batteryPercentRemaining.Get();
if (chargingBatteryPercent <= 20) {
SetBatteryLevel(chargingBatteryPercent, redColor);
} else if (chargingBatteryPercent > 99) {
SetBatteryLevel(chargingBatteryPercent, orangeColor);
} else {
SetBatteryLevel(chargingBatteryPercent, grayColor);
}
}
}
void WatchFaceLCARS::UpdateBLE() {
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated()) {
lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get()));
lv_obj_realign(bleIcon);
}
}
void WatchFaceLCARS::UpdateStepCount() {
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_realign(stepValue);
lv_obj_realign(stepIcon);
}
}
void WatchFaceLCARS::ResetSecondsDigits() {
for (int i = 0; i < 6; ++i) {
set_label_color(label_tens[i], grayColor);
}
for (int i = 0; i < 10; ++i) {
set_label_color(label_ones[i], grayColor);
}
}
void WatchFaceLCARS::SetBatteryLevel(uint8_t batteryPercent, const lv_color_t& color) {
lv_label_set_text_fmt(labelBattery, "%lu%%", batteryPercent);
set_label_color(labelBattery, color);
lv_obj_realign(labelBattery);
lv_obj_realign(bleIcon);
}
bool WatchFaceLCARS::IsAvailable(Pinetime::Controllers::FS& filesystem) {
lfs_file file = {};
if (filesystem.FileOpen(&file, "/fonts/antonio_78.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/antonio_33.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/antonio_21.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/antonio_13.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
return true;
}

View file

@ -0,0 +1,138 @@
#pragma once
#include <lvgl/lvgl.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include <displayapp/Controllers.h>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceLCARS : public Screen {
public:
WatchFaceLCARS(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController,
Controllers::HeartRateController& heartRateController,
Controllers::FS& fs);
~WatchFaceLCARS() override;
void Refresh() override;
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
private:
uint32_t savedTick = 0;
uint8_t chargingBatteryPercent = 101; // not a mistake ;)
uint8_t last_second = 60;
const char* weekNumberFormat = "%V";
static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99);
static constexpr lv_color_t orangeColor = LV_COLOR_MAKE(0xFF, 0x99, 0x33);
static constexpr lv_color_t redColor = LV_COLOR_MAKE(0xDD, 0x44, 0x44);
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
Utility::DirtyValue<bool> isCharging {};
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<bool> bleRadioEnabled {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentNanoSeconds {};
Utility::DirtyValue<uint32_t> stepCount {};
Utility::DirtyValue<uint8_t> heartbeat {};
Utility::DirtyValue<bool> heartbeatRunning {};
Utility::DirtyValue<bool> notificationState {};
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
lv_obj_t* background;
lv_obj_t* timeContainer;
lv_obj_t* labelTime;
lv_obj_t* labelTimeAmPm;
lv_obj_t* label_week;
lv_obj_t* label_seconds_container;
lv_obj_t* label_tens_container;
lv_obj_t* label_ones_container;
lv_obj_t* label_tens[6];
lv_obj_t* label_ones[10];
lv_obj_t* dateContainer;
lv_obj_t* labelDate;
lv_obj_t* labelBattery;
lv_obj_t* system_container;
lv_obj_t* sensors_container;
lv_obj_t* bleIcon;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* heartbeatValue;
lv_obj_t* heartbeatIcon;
lv_obj_t* notificationIcon;
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::MotionController& motionController;
Controllers::HeartRateController& heartRateController;
void SetBatteryLevel(uint8_t batteryPercent, const lv_color_t& color);
void ResetSecondsDigits();
void UpdateStepCount();
void UpdateBLE();
void UpdateBatteryPercent();
void UpdateStardate();
void UpdateWK();
void UpdateSeconds();
void UpdateTime();
void UpdateHeartRate();
lv_task_t* taskRefresh;
lv_font_t* font_antonio_78 = nullptr;
lv_font_t* font_antonio_33 = nullptr;
lv_font_t* font_antonio_21 = nullptr;
lv_font_t* font_antonio_13 = nullptr;
};
}
template <>
struct WatchFaceTraits<WatchFace::LCARS> {
static constexpr WatchFace watchFace = WatchFace::LCARS;
static constexpr const char* name = "LCARS face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceLCARS(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.motionController,
controllers.heartRateController,
controllers.filesystem);
};
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
return Screens::WatchFaceLCARS::IsAvailable(filesystem);
}
};
}
}

View file

@ -9,10 +9,17 @@
using namespace Pinetime::Applications::Screens;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
void TimeoutEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<SettingDisplay*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
void AlwaysOnEventHandler(lv_obj_t* obj, lv_event_t event) {
if (event == LV_EVENT_VALUE_CHANGED) {
auto* screen = static_cast<SettingDisplay*>(obj->user_data);
screen->ToggleAlwaysOn();
}
}
}
constexpr std::array<uint16_t, 6> SettingDisplay::options;
@ -49,13 +56,20 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime
snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000);
lv_checkbox_set_text(cbOption[i], buffer);
cbOption[i]->user_data = this;
lv_obj_set_event_cb(cbOption[i], event_handler);
lv_obj_set_event_cb(cbOption[i], TimeoutEventHandler);
SetRadioButtonStyle(cbOption[i]);
if (settingsController.GetScreenTimeOut() == options[i]) {
lv_checkbox_set_checked(cbOption[i], true);
}
}
alwaysOnCheckbox = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text(alwaysOnCheckbox, "Always On");
lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting());
lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT);
alwaysOnCheckbox->user_data = this;
lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler);
}
SettingDisplay::~SettingDisplay() {
@ -63,6 +77,11 @@ SettingDisplay::~SettingDisplay() {
settingsController.SaveSettings();
}
void SettingDisplay::ToggleAlwaysOn() {
settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting());
lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting());
}
void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
for (unsigned int i = 0; i < options.size(); i++) {

View file

@ -18,6 +18,7 @@ namespace Pinetime {
~SettingDisplay() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
void ToggleAlwaysOn();
private:
DisplayApp* app;
@ -25,6 +26,7 @@ namespace Pinetime {
Controllers::Settings& settingsController;
lv_obj_t* cbOption[options.size()];
lv_obj_t* alwaysOnCheckbox;
};
}
}

View file

@ -10,6 +10,7 @@
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/CheckboxList.h"
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFaceLCARS.h"
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
namespace Pinetime {

View file

@ -10,7 +10,7 @@ SpiNorFlash::SpiNorFlash(Spi& spi) : spi {spi} {
}
void SpiNorFlash::Init() {
device_id = ReadIdentificaion();
device_id = ReadIdentification();
NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d",
device_id.manufacturer,
device_id.type,
@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() {
uint8_t cmd[cmdSize] = {static_cast<uint8_t>(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03};
uint8_t id = 0;
spi.Read(reinterpret_cast<uint8_t*>(&cmd), cmdSize, &id, 1);
auto devId = device_id = ReadIdentificaion();
auto devId = device_id = ReadIdentification();
if (devId.type != device_id.type) {
NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed");
} else {
@ -41,7 +41,7 @@ void SpiNorFlash::Wakeup() {
NRF_LOG_INFO("[SpiNorFlash] Wakeup")
}
SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() {
SpiNorFlash::Identification SpiNorFlash::ReadIdentification() {
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
Identification identification;
spi.Read(&cmd, 1, reinterpret_cast<uint8_t*>(&identification), sizeof(Identification));
@ -145,3 +145,7 @@ void SpiNorFlash::Write(uint32_t address, const uint8_t* buffer, size_t size) {
len -= toWrite;
}
}
SpiNorFlash::Identification SpiNorFlash::GetIdentification() const {
return device_id;
}

View file

@ -20,7 +20,6 @@ namespace Pinetime {
uint8_t density = 0;
};
Identification ReadIdentificaion();
uint8_t ReadStatusRegister();
bool WriteInProgress();
bool WriteEnabled();
@ -33,6 +32,8 @@ namespace Pinetime {
bool ProgramFailed();
bool EraseFailed();
Identification GetIdentification() const;
void Init();
void Uninit();
@ -40,6 +41,8 @@ namespace Pinetime {
void Wakeup();
private:
Identification ReadIdentification();
enum class Commands : uint8_t {
PageProgram = 0x02,
Read = 0x03,

View file

@ -16,6 +16,7 @@ void St7789::Init() {
nrf_gpio_pin_set(pinReset);
HardwareReset();
SoftwareReset();
Command2Enable();
SleepOut();
PixelFormat();
MemoryDataAccessControl();
@ -24,8 +25,13 @@ void St7789::Init() {
#ifndef DRIVER_DISPLAY_MIRROR
DisplayInversionOn();
#endif
PorchSet();
FrameRateNormalSet();
IdleFrameRateOff();
NormalModeOn();
SetVdv();
PowerControl();
GateControl();
DisplayOn();
}
@ -63,6 +69,17 @@ void St7789::SoftwareReset() {
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::Command2Enable() {
WriteCommand(static_cast<uint8_t>(Commands::Command2Enable));
constexpr uint8_t args[] = {
0x5a, // Constant
0x69, // Constant
0x02, // Constant
0x01, // Enable
};
WriteData(args, sizeof(args));
}
void St7789::SleepOut() {
if (!sleepIn) {
return;
@ -127,10 +144,79 @@ void St7789::NormalModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
}
void St7789::IdleModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOn));
}
void St7789::IdleModeOff() {
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOff));
}
void St7789::PorchSet() {
WriteCommand(static_cast<uint8_t>(Commands::Porch));
constexpr uint8_t args[] = {
0x02, // Normal mode front porch
0x03, // Normal mode back porch
0x01, // Porch control enable
0xed, // Idle mode front:back porch
0xed, // Partial mode front:back porch (partial mode unused but set anyway)
};
WriteData(args, sizeof(args));
}
void St7789::FrameRateNormalSet() {
WriteCommand(static_cast<uint8_t>(Commands::FrameRateNormal));
// Note that the datasheet table is imprecise - see formula below table
WriteData(0x0a);
}
void St7789::IdleFrameRateOn() {
WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
// According to the datasheet, these controls should apply only to partial/idle mode
// However they appear to apply to normal mode, so we have to enable/disable
// every time we enter/exit always on
// In testing this divider appears to actually be 16x?
constexpr uint8_t args[] = {
0x13, // Enable frame rate control for partial/idle mode, 8x frame divider
0x1e, // Idle mode frame rate
0x1e, // Partial mode frame rate (unused)
};
WriteData(args, sizeof(args));
}
void St7789::IdleFrameRateOff() {
WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
constexpr uint8_t args[] = {
0x00, // Disable frame rate control and divider
0x0a, // Idle mode frame rate (normal)
0x0a, // Partial mode frame rate (normal, unused)
};
WriteData(args, sizeof(args));
}
void St7789::DisplayOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOn));
}
void St7789::PowerControl() {
WriteCommand(static_cast<uint8_t>(Commands::PowerControl1));
constexpr uint8_t args[] = {
0xa4, // Constant
0x00, // Lowest possible voltages
};
WriteData(args, sizeof(args));
WriteCommand(static_cast<uint8_t>(Commands::PowerControl2));
// Lowest possible boost circuit clocks
WriteData(0xb3);
}
void St7789::GateControl() {
WriteCommand(static_cast<uint8_t>(Commands::GateControl));
// Lowest possible VGL/VGH
WriteData(0x00);
}
void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet));
uint8_t colArgs[] = {
@ -198,6 +284,18 @@ void St7789::HardwareReset() {
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::LowPowerOn() {
IdleModeOn();
IdleFrameRateOn();
NRF_LOG_INFO("[LCD] Low power mode");
}
void St7789::LowPowerOff() {
IdleModeOff();
IdleFrameRateOff();
NRF_LOG_INFO("[LCD] Normal power mode");
}
void St7789::Sleep() {
SleepIn();
nrf_gpio_cfg_default(pinDataCommand);

View file

@ -24,6 +24,8 @@ namespace Pinetime {
void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
void LowPowerOn();
void LowPowerOff();
void Sleep();
void Wakeup();
@ -37,6 +39,7 @@ namespace Pinetime {
void HardwareReset();
void SoftwareReset();
void Command2Enable();
void SleepOut();
void EnsureSleepOutPostDelay();
void SleepIn();
@ -45,8 +48,16 @@ namespace Pinetime {
void DisplayInversionOn();
void NormalModeOn();
void WriteToRam(const uint8_t* data, size_t size);
void IdleModeOn();
void IdleModeOff();
void FrameRateNormalSet();
void IdleFrameRateOff();
void IdleFrameRateOn();
void DisplayOn();
void DisplayOff();
void PowerControl();
void GateControl();
void PorchSet();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void SetVdv();
@ -68,8 +79,17 @@ namespace Pinetime {
MemoryDataAccessControl = 0x36,
VerticalScrollDefinition = 0x33,
VerticalScrollStartAddress = 0x37,
IdleModeOff = 0x38,
IdleModeOn = 0x39,
PixelFormat = 0x3a,
FrameRateIdle = 0xb3,
FrameRateNormal = 0xc6,
VdvSet = 0xc4,
Command2Enable = 0xdf,
PowerControl1 = 0xd0,
PowerControl2 = 0xe8,
GateControl = 0xb7,
Porch = 0xb2,
};
void WriteData(uint8_t data);
void WriteData(const uint8_t* data, size_t size);

View file

@ -123,7 +123,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
alarmController,
brightnessController,
touchHandler,
fs);
fs,
spiNorFlash);
Pinetime::System::SystemTask systemTask(spi,
spiNorFlash,

View file

@ -58,5 +58,53 @@
"size": 115,
"format": "bin",
"target_path": "/fonts/"
},
"antonio_78" : {
"sources": [
{
"file": "fonts/antonio.ttf",
"symbols": "0123456789:"
}
],
"bpp": 1,
"size": 78,
"format": "bin",
"target_path": "/fonts/"
},
"antonio_33" : {
"sources": [
{
"file": "fonts/antonio.ttf",
"symbols": "0123456789WK"
}
],
"bpp": 1,
"size": 33,
"format": "bin",
"target_path": "/fonts/"
},
"antonio_21" : {
"sources": [
{
"file": "fonts/antonio.ttf",
"symbols": "0123456789:APMONTUEWDHFRISATJBYLGKVZ% "
}
],
"bpp": 1,
"size": 21,
"format": "bin",
"target_path": "/fonts/"
},
"antonio_13" : {
"sources": [
{
"file": "fonts/antonio.ttf",
"symbols": "0123456789 "
}
],
"bpp": 1,
"size": 13,
"format": "bin",
"target_path": "/fonts/"
}
}

View file

@ -0,0 +1,48 @@
SIL Open Font License v1.10
This license can also be found at this permalink: https://www.fontsquirrel.com/license/antonio
Copyright (c) 2011-12, vernon adams (vern@newtypography.co.uk), with Reserved Font Names Antonio
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
—————————————————————————————-
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
—————————————————————————————-
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View file

@ -19,5 +19,12 @@
"output_format": "bin",
"binary_format": "ARGB8565_RBSWAP",
"target_path": "/images/"
},
"LCARS" : {
"sources": "images/LCARS.png",
"color_format": "CF_TRUE_COLOR_ALPHA",
"output_format": "bin",
"binary_format": "ARGB8565_RBSWAP",
"target_path": "/images/"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="240"
height="240"
viewBox="0 0 63.5 63.5"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="Star Trek Watchface.svg"
inkscape:export-filename="Star Trek Watchface With Text.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="2.5976568"
inkscape:cx="116.06614"
inkscape:cy="119.91576"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="20"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="layer2"
showguides="false"
inkscape:export-bgcolor="#00000000" />
<defs
id="defs1">
<rect
x="134.72286"
y="116.75272"
width="32.321084"
height="8.0905084"
id="rect15" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:none">
<text
xml:space="preserve"
style="font-size:22.5455px;fill:#ff9933;stroke-width:0.234849"
x="13.493794"
y="43.231659"
id="text1"
transform="scale(1.1266093,0.88761916)"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.5455px;font-family:Antonio;-inkscape-font-specification:Antonio;stroke-width:0.234849"
x="13.493794"
y="43.231659">10:30</tspan></text>
<text
xml:space="preserve"
style="font-size:3.52778px;fill:#ff9933;stroke-width:0.264583"
x="16.538929"
y="47.272141"
id="text1-5"><tspan
sodipodi:role="line"
id="tspan1-3"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#999999;fill-opacity:1;stroke-width:0.264583"
x="16.538929"
y="47.272141"><tspan
style="fill:#ff9933"
id="tspan4">0</tspan> 1 2 3 4 5 6 7 8 9</tspan></text>
<text
xml:space="preserve"
style="font-size:3.52777px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#999999;fill-opacity:1;stroke-width:0.264583"
x="16.538931"
y="43.27816"
id="text2"><tspan
sodipodi:role="line"
id="tspan2"
style="stroke-width:0.264583"
x="16.538931"
y="43.27816">0 1 2 3 4 <tspan
style="fill:#ff9933"
id="tspan25">5</tspan></tspan></text>
<text
xml:space="preserve"
style="font-size:9.87777px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff8866;stroke-width:0.264583"
x="35.793755"
y="32.993759"
id="text5"><tspan
sodipodi:role="line"
id="tspan5"
style="fill:#ff8866;stroke-width:0.264583"
x="35.793755"
y="32.993759" /></text>
<text
xml:space="preserve"
style="font-size:4.93889px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="49.639694"
y="56.440994"
id="text6"><tspan
sodipodi:role="line"
id="tspan6"
style="font-size:4.93889px;stroke-width:0.264583"
x="49.639694"
y="56.440994">78 BPM</tspan></text>
<text
xml:space="preserve"
style="font-size:4.93889px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="43.046471"
y="62.015366"
id="text7"><tspan
sodipodi:role="line"
id="tspan7"
style="font-size:4.93889px;stroke-width:0.264583"
x="43.046471"
y="62.015366">1260 Steps</tspan></text>
<text
xml:space="preserve"
style="font-size:8.67718px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.361549"
x="43.338665"
y="47.602779"
id="text8"><tspan
sodipodi:role="line"
style="font-size:8.67718px;stroke-width:0.361549"
x="43.338665"
y="47.602779"
id="tspan23">KW 14</tspan></text>
<text
xml:space="preserve"
style="font-size:4.93889px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="51.093872"
y="5.874855"
id="text9"><tspan
sodipodi:role="line"
id="tspan9"
style="font-size:4.93889px;stroke-width:0.264583"
x="51.093872"
y="5.874855">100%</tspan></text>
<text
xml:space="preserve"
style="font-size:4.23333px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="43.939583"
y="5.8218007"
id="text9-6"><tspan
sodipodi:role="line"
id="tspan9-1"
style="font-size:4.93889px;stroke-width:0.264583"
x="43.939583"
y="5.8218007">BT</tspan><tspan
sodipodi:role="line"
style="font-size:4.23333px;stroke-width:0.264583"
x="43.939583"
y="11.277539"
id="tspan16" /></text>
<text
xml:space="preserve"
style="font-size:4.23333px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="34.5933"
y="5.8676205"
id="text9-6-1"><tspan
sodipodi:role="line"
id="tspan9-1-9"
style="font-size:4.93889px;stroke-width:0.264583"
x="34.5933"
y="5.8676205">GB</tspan><tspan
sodipodi:role="line"
style="font-size:4.23333px;stroke-width:0.264583"
x="34.5933"
y="11.323359"
id="tspan16-4" /></text>
<text
xml:space="preserve"
style="font-size:5.64444px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="30.224888"
y="18.570627"
id="text10"><tspan
sodipodi:role="line"
id="tspan10"
style="font-size:5.64444px;stroke-width:0.264583"
x="30.224888"
y="18.570627">SUN</tspan></text>
<text
xml:space="preserve"
style="font-size:5.64444px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="39.012764"
y="18.576555"
id="text11"><tspan
sodipodi:role="line"
id="tspan11"
style="font-size:5.64444px;stroke-width:0.264583"
x="39.012764"
y="18.576555">31.</tspan></text>
<text
xml:space="preserve"
style="font-size:5.64444px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="45.800869"
y="18.537088"
id="text12"><tspan
sodipodi:role="line"
id="tspan12"
style="font-size:5.64444px;stroke-width:0.264583"
x="45.800869"
y="18.537088">MAR</tspan></text>
<text
xml:space="preserve"
style="font-size:5.64444px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#ff9933;stroke-width:0.264583"
x="16.408016"
y="18.678455"
id="text13"><tspan
sodipodi:role="line"
id="tspan13"
style="font-size:5.64444px;stroke-width:0.264583"
x="16.408016"
y="18.678455">12024</tspan></text>
<text
xml:space="preserve"
transform="matrix(0.26458333,0,0,0.26458333,0,-3.7041668)"
id="text15"
style="font-size:8px;font-family:Antonio;-inkscape-font-specification:Antonio;white-space:pre;shape-inside:url(#rect15);display:inline;fill:#000000" />
<text
xml:space="preserve"
style="font-size:5.64444px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#88ccff;stroke-width:0.264583"
x="56.456512"
y="18.633963"
id="text24"><tspan
sodipodi:role="line"
id="tspan24"
style="font-size:5.64444px;stroke-width:0.264583"
x="56.456512"
y="18.633963">PM</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Background">
<rect
style="fill:#cc99cc;fill-opacity:1;stroke-width:0.0813156"
id="rect4"
width="13.291104"
height="1.2121059"
x="0"
y="-2.7755576e-17" />
<rect
style="fill:#9944ff;fill-opacity:1;stroke-width:0.288846"
id="rect4-9"
width="13.291104"
height="15.294165"
x="0"
y="48.25753" />
<rect
style="fill:#88ccff;fill-opacity:1;stroke-width:0.178393"
id="rect4-9-0"
width="13.291104"
height="5.8337417"
x="14.027023"
y="57.717953" />
<rect
style="fill:#dd4444;fill-opacity:1;stroke-width:0.389993"
id="rect4-6"
width="13.291104"
height="27.880901"
x="-1.110223e-16"
y="19.954521" />
<path
id="rect4-0"
style="fill:#99ccff;fill-opacity:1;stroke-width:0.189548"
d="m 0,1.577442 v 1.4836962 c 0,1.5852254 0.5175042,3.3176435 1.5952589,4.4801326 1.13959,1.2291864 2.920045,2.0398118 4.59622,2.0398118 H 40.438358 V 7.9718788 H 16.96018 c -1.835253,0 -3.669003,-1.7568371 -3.669003,-3.1220025 V 1.577442 Z"
sodipodi:nodetypes="csascccccc" />
<path
id="rect4-0-5"
style="fill:#dd4444;fill-opacity:1;stroke-width:0.189548"
d="m 0,19.565147 v -3.071198 c 0,-1.585226 0.517504,-3.317645 1.5952588,-4.480135 1.1395901,-1.229187 2.920045,-2.0398113 4.59622,-2.0398113 H 40.438357 V 11.583206 H 16.960179 c -1.835253,0 -3.669003,1.756838 -3.669003,3.122004 v 4.859937 z"
sodipodi:nodetypes="csascccccc" />
<path
id="rect4-0-5-0"
style="fill:#3366cc;fill-opacity:1;stroke-width:0.189548"
d="m 14.027023,57.317007 v -2.012865 c 0,-1.585226 0.517504,-3.317645 1.595259,-4.480135 1.13959,-1.229187 2.920045,-2.039813 4.59622,-2.039813 H 62.63702 v 1.609205 H 30.987202 c -1.835253,0 -3.669003,1.756838 -3.669003,3.122004 v 3.801604 z"
sodipodi:nodetypes="csascccccc" />
<rect
style="fill:#cc99cc;fill-opacity:1;stroke-width:0.0830008"
id="rect5"
width="16.936972"
height="1.61"
x="43.691734"
y="7.9710908" />
<rect
style="fill:#cc99cc;fill-opacity:1;stroke-width:0.0829989"
id="rect5-6"
width="16.936235"
height="1.61"
x="43.693829"
y="9.9740114" />
<rect
style="fill:#ff9933;fill-opacity:1;stroke-width:0.0317888"
id="rect5-0"
width="2.4843886"
height="1.61"
x="40.840656"
y="7.9710908" />
<rect
style="fill:#ffcc99;fill-opacity:1;stroke-width:0.0317888"
id="rect5-0-2"
width="2.4843886"
height="1.61"
x="40.840656"
y="9.9740114" />
<rect
style="fill:#dd4444;fill-opacity:1;stroke-width:0.0317888"
id="rect5-0-1"
width="2.4843886"
height="1.61"
x="61.01561"
y="7.9710908" />
<rect
style="fill:#ffcc99;fill-opacity:1;stroke-width:0.0317888"
id="rect5-0-1-7"
width="2.4843886"
height="1.61"
x="61.01561"
y="9.9740114" />
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Text"
style="display:inline">
<text
xml:space="preserve"
style="font-size:3.175px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#000000;stroke-width:0.264583"
x="3.208236"
y="18.643311"
id="text14"><tspan
sodipodi:role="line"
id="tspan14"
style="font-size:3.175px;fill:#000000;stroke-width:0.264583"
x="3.208236"
y="18.643311">Stardate</tspan></text>
<text
xml:space="preserve"
style="font-size:3.175px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#000000;stroke-width:0.264583"
x="7.6141686"
y="46.876671"
id="text14-3"><tspan
sodipodi:role="line"
id="tspan14-7"
style="font-size:3.175px;fill:#000000;stroke-width:0.264583"
x="7.6141686"
y="46.876671">Time</tspan></text>
<text
xml:space="preserve"
style="font-size:3.175px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#000000;stroke-width:0.264583"
x="3.8299036"
y="52.036678"
id="text14-3-5"><tspan
sodipodi:role="line"
id="tspan14-7-0"
style="font-size:3.175px;fill:#000000;stroke-width:0.264583"
x="3.8299036"
y="52.036678">Sensors</tspan></text>
<text
xml:space="preserve"
style="font-size:3.175px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#000000;stroke-width:0.264583"
x="20.41197"
y="56.458221"
id="text14-3-5-5"><tspan
sodipodi:role="line"
id="tspan14-7-0-4"
style="font-size:3.175px;fill:#000000;stroke-width:0.264583"
x="20.41197"
y="56.458221">Vitals</tspan></text>
<text
xml:space="preserve"
style="font-size:3.175px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#000000;stroke-width:0.264583"
x="14.892928"
y="61.273037"
id="text14-3-5-5-6"><tspan
sodipodi:role="line"
id="tspan14-7-0-4-9"
style="font-size:3.175px;fill:#000000;stroke-width:0.264583"
x="14.892928"
y="61.273037">Movement</tspan></text>
<text
xml:space="preserve"
style="font-size:3.175px;font-family:Antonio;-inkscape-font-specification:Antonio;fill:#000000;stroke-width:0.264583"
x="4.7538781"
y="4.969224"
id="text14-2"><tspan
sodipodi:role="line"
id="tspan14-8"
style="font-size:3.175px;fill:#000000;stroke-width:0.264583"
x="4.7538781"
y="4.969224">System</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -102,7 +102,9 @@ void SystemTask::Work() {
watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
watchdog.Start();
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason()));
APP_GPIOTE_INIT(2);
if (!nrfx_gpiote_is_init()) {
nrfx_gpiote_init();
}
spi.Init();
spiNorFlash.Init();
@ -192,13 +194,16 @@ void SystemTask::Work() {
if (!bleController.IsFirmwareUpdating()) {
doNotGoToSleep = false;
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
break;
case Messages::DisableSleeping:
doNotGoToSleep = true;
break;
case Messages::GoToRunning:
spi.Wakeup();
// SPI doesn't go to sleep for always on mode
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Wakeup();
}
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
@ -240,7 +245,7 @@ void SystemTask::Work() {
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break;
case Messages::OnNewTime:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
alarmController.ScheduleAlarm();
@ -251,7 +256,7 @@ void SystemTask::Work() {
if (state == SystemTaskState::Sleeping) {
GoToRunning();
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
}
@ -263,7 +268,7 @@ void SystemTask::Work() {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
break;
case Messages::BleConnected:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
isBleDiscoveryTimerRunning = true;
bleDiscoveryTimer = 5;
break;
@ -323,7 +328,11 @@ void SystemTask::Work() {
// if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions.
spiNorFlash.Sleep();
}
spi.Sleep();
// Must keep SPI awake when still updating the display for always on
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Sleep();
}
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
@ -457,7 +466,7 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
return;
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
using Actions = Controllers::ButtonActions;