Skip to content

Commit

Permalink
Merge branch 'InfiniTimeOrg:main' into maze-watchface
Browse files Browse the repository at this point in the history
  • Loading branch information
Feksaaargh authored Oct 30, 2024
2 parents 2d863e1 + 57b6db8 commit 8aa81ab
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 106 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
[submodule "src/libs/littlefs"]
path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git
[submodule "src/libs/QCBOR"]
path = src/libs/QCBOR
url = https://github.com/laurencelundblade/QCBOR.git
[submodule "src/libs/arduinoFFT"]
path = src/libs/arduinoFFT
url = https://github.com/kosme/arduinoFFT.git
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
<div align="center">

![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
![Header Image](doc/logo/watchface_collage.png)

Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.
<br>

[![GitHub tag](https://img.shields.io/github/tag/InfiniTimeOrg/InfiniTime?include_prereleases=&sort=semver&color=blue)](https://github.com/InfiniTimeOrg/InfiniTime/releases)
[![GitHub License](https://img.shields.io/github/license/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniLink/blob/main/LICENSE)
[![Issues - InfiniTime](https://img.shields.io/github/issues/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/issues)
[![Pull Requests - InfiniTime](https://img.shields.io/github/issues-pr/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/pulls)
[![Downloads - InfiniTime](https://img.shields.io/github/downloads/InfiniTimeOrg/InfiniTime/total)](https://github.com/InfiniTimeOrg/InfiniTime)
[![Stars - InfiniTime](https://img.shields.io/github/stars/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/stargazers)
[![Forks - InfiniTime](https://img.shields.io/github/forks/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/network/members)

# InfiniTime

*Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.*

<br>

</div>

## New to InfiniTime?

Expand All @@ -22,8 +38,13 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS)
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
- [InfiniTimeExplorer](https://infinitimeexplorer.netlify.app) (Web)

<br>

***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
> *InfiniTimeExplorer is only compatible with web browsers that support Web BLE. Current fully supported browsers include Chrome and Microsoft Edge.*
>
> *We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
## Development

Expand Down
Binary file added doc/logo/watchface_collage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/ble/SimpleWeatherService.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

#include <cstdint>
#include <string>
#include <vector>
#include <array>
#include <memory>

#define min // workaround: nimble's min/max macros conflict with libstdc++
Expand Down
25 changes: 20 additions & 5 deletions src/components/datetime/DateTimeController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,29 @@
using namespace Pinetime::Controllers;

namespace {
char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
constexpr const char* const DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
constexpr const char* const DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
constexpr const char* const MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
constexpr const char* const MonthsStringLow[] =
{"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

constexpr int compileTimeAtoi(const char* str) {
int result = 0;
while (*str >= '0' && *str <= '9') {
result = result * 10 + *str - '0';
str++;
}
return result;
}
}

DateTime::DateTime(Controllers::Settings& settingsController) : settingsController {settingsController} {
mutex = xSemaphoreCreateMutex();
ASSERT(mutex != nullptr);
xSemaphoreGive(mutex);

// __DATE__ is a string of the format "MMM DD YYYY", so an offset of 7 gives the start of the year
SetTime(compileTimeAtoi(&__DATE__[7]), 1, 1, 0, 0, 0);
}

void DateTime::SetCurrentTime(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> t) {
Expand Down Expand Up @@ -46,7 +59,9 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour,
UpdateTime(previousSystickCounter, true);
xSemaphoreGive(mutex);

systemTask->PushMessage(System::Messages::OnNewTime);
if (systemTask != nullptr) {
systemTask->PushMessage(System::Messages::OnNewTime);
}
}

void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
Expand Down
95 changes: 54 additions & 41 deletions src/displayapp/DisplayApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,30 +157,38 @@ void DisplayApp::InitHw() {
}

TickType_t DisplayApp::CalculateSleepTime() {
// Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame
// Next frame time is frame count * refresh period (ms) * tick rate

auto RoundedDiv = [](uint32_t a, uint32_t b) {
return ((a + (b / 2)) / b);
};
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours (=overflow frame count / always on refresh period)
constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);

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);
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
// to increase the number of ticks (frames) before the overflow tick is reached
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);

// 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
// Frame count must always wraparound more often than the system tick count does
// 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);
// Using 64bit ints here to avoid overflow
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);

if (alwaysOnTickCount == overflowTick) {
alwaysOnTickCount = 0;
if (alwaysOnFrameCount == overflowFrameCount) {
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
}
if (elapsedTarget > ticksElapsed) {
return elapsedTarget - ticksElapsed;
if (targetRenderTick > ticksElapsed) {
return targetRenderTick - ticksElapsed;
} else {
return 0;
}
Expand Down Expand Up @@ -220,28 +228,27 @@ void DisplayApp::Refresh() {
TickType_t queueTimeout;
switch (state) {
case States::Idle:
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();
}
};
queueTimeout = portMAX_DELAY;
break;
case States::AOD:
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) {
alwaysOnFrameCount += 1;
queueTimeout = CalculateSleepTime();
}
}
} else {
queueTimeout = portMAX_DELAY;
}
break;
case States::Running:
Expand Down Expand Up @@ -284,6 +291,7 @@ void DisplayApp::Refresh() {
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
switch (msg) {
case Messages::GoToSleep:
case Messages::GoToAOD:
if (state != States::Running) {
break;
}
Expand All @@ -292,7 +300,7 @@ void DisplayApp::Refresh() {
vTaskDelay(100);
}
// Turn brightness down (or set to AlwaysOn mode)
if (settingsController.GetAlwaysOnDisplay()) {
if (msg == Messages::GoToAOD) {
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
} else {
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
Expand All @@ -305,17 +313,22 @@ void DisplayApp::Refresh() {
while (!lv_task_handler()) {
};
}
// Turn LCD display off (or set to low power for AlwaysOn mode)
if (settingsController.GetAlwaysOnDisplay()) {
// Clear any ongoing touch pressed events
// Without this LVGL gets stuck in the pressed state and will keep refreshing the
// display activity timer causing the screen to never sleep after timeout
lvgl.ClearTouchState();
if (msg == Messages::GoToAOD) {
lcd.LowPowerOn();
// Record idle entry time
alwaysOnTickCount = 0;
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
state = States::AOD;
} else {
lcd.Sleep();
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
}
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
Expand All @@ -324,7 +337,7 @@ void DisplayApp::Refresh() {
if (state == States::Running) {
break;
}
if (settingsController.GetAlwaysOnDisplay()) {
if (state == States::AOD) {
lcd.LowPowerOff();
} else {
lcd.Wakeup();
Expand Down Expand Up @@ -459,7 +472,7 @@ void DisplayApp::Refresh() {
}
}

if (touchHandler.IsTouching()) {
if (state == States::Running && touchHandler.IsTouching()) {
currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY());
}

Expand Down
4 changes: 2 additions & 2 deletions src/displayapp/DisplayApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ namespace Pinetime {
namespace Applications {
class DisplayApp {
public:
enum class States { Idle, Running };
enum class States { Idle, Running, AOD };
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };

DisplayApp(Drivers::St7789& lcd,
Expand Down Expand Up @@ -139,7 +139,7 @@ namespace Pinetime {
bool isDimmed = false;

TickType_t CalculateSleepTime();
TickType_t alwaysOnTickCount;
TickType_t alwaysOnFrameCount;
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
Expand Down
9 changes: 9 additions & 0 deletions src/displayapp/LittleVgl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,22 @@ void LittleVgl::SetNewTouchPoint(int16_t x, int16_t y, bool contact) {
}
}

// Cancel an ongoing tap
// Signifies that LVGL should not handle the current tap
void LittleVgl::CancelTap() {
if (tapped) {
isCancelled = true;
touchPoint = {-1, -1};
}
}

// Clear the current tapped state
// Signifies that touch input processing is suspended
void LittleVgl::ClearTouchState() {
touchPoint = {-1, -1};
tapped = false;
}

bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
ptr->point.x = touchPoint.x;
ptr->point.y = touchPoint.y;
Expand Down
1 change: 1 addition & 0 deletions src/displayapp/LittleVgl.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace Pinetime {
void SetFullRefresh(FullRefreshDirections direction);
void SetNewTouchPoint(int16_t x, int16_t y, bool contact);
void CancelTap();
void ClearTouchState();

bool GetFullRefresh() {
bool returnValue = fullRefresh;
Expand Down
1 change: 1 addition & 0 deletions src/displayapp/Messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Pinetime {
namespace Display {
enum class Messages : uint8_t {
GoToSleep,
GoToAOD,
GoToRunning,
UpdateBleConnection,
TouchEvent,
Expand Down
3 changes: 1 addition & 2 deletions src/drivers/St7789.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,8 @@ void St7789::IdleFrameRateOn() {
// 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
0x12, // Enable frame rate control for partial/idle mode, 4x frame divider
0x1e, // Idle mode frame rate
0x1e, // Partial mode frame rate (unused)
};
Expand Down
1 change: 0 additions & 1 deletion src/libs/QCBOR
Submodule QCBOR deleted from 56b17b
2 changes: 1 addition & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> NoI

void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
if (pin == Pinetime::PinMap::Cst816sIrq) {
systemTask.OnTouchEvent();
systemTask.PushMessage(Pinetime::System::Messages::OnTouchEvent);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/systemtask/Messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace Pinetime {
enum class Messages : uint8_t {
GoToSleep,
GoToRunning,
TouchWakeUp,
OnNewTime,
OnNewNotification,
OnNewCall,
Expand All @@ -17,6 +16,7 @@ namespace Pinetime {
HandleButtonEvent,
HandleButtonTimerEvent,
OnDisplayTaskSleeping,
OnDisplayTaskAOD,
EnableSleeping,
DisableSleeping,
OnNewDay,
Expand Down
Loading

0 comments on commit 8aa81ab

Please sign in to comment.