Merge branch 'main' into wb/wadokei-merge

This commit is contained in:
2024-12-05 16:41:43 -08:00
93 changed files with 3513 additions and 791 deletions
+17 -13
View File
@@ -371,8 +371,6 @@ list(APPEND SOURCE_FILES
displayapp/DisplayApp.cpp
displayapp/screens/Screen.cpp
displayapp/screens/Tile.cpp
displayapp/screens/InfiniPaint.cpp
displayapp/screens/Paddle.cpp
displayapp/screens/StopWatch.cpp
displayapp/screens/BatteryIcon.cpp
displayapp/screens/BleIcon.cpp
@@ -381,13 +379,9 @@ list(APPEND SOURCE_FILES
displayapp/screens/Label.cpp
displayapp/screens/FirmwareUpdate.cpp
displayapp/screens/Music.cpp
displayapp/screens/Navigation.cpp
displayapp/screens/Metronome.cpp
displayapp/screens/Motion.cpp
displayapp/screens/FirmwareValidation.cpp
displayapp/screens/ApplicationList.cpp
displayapp/screens/Notifications.cpp
displayapp/screens/Twos.cpp
displayapp/screens/HeartRate.cpp
displayapp/screens/FlashLight.cpp
displayapp/screens/List.cpp
@@ -395,10 +389,12 @@ list(APPEND SOURCE_FILES
displayapp/screens/BatteryInfo.cpp
displayapp/screens/Steps.cpp
displayapp/screens/Timer.cpp
displayapp/screens/Dice.cpp
displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp
displayapp/screens/Alarm.cpp
displayapp/screens/Styles.cpp
displayapp/screens/WeatherSymbols.cpp
displayapp/Colors.cpp
displayapp/widgets/Counter.cpp
displayapp/widgets/PageIndicator.cpp
@@ -421,6 +417,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/settings/SettingChimes.cpp
displayapp/screens/settings/SettingShakeThreshold.cpp
displayapp/screens/settings/SettingBluetooth.cpp
displayapp/screens/settings/SettingLocation.cpp
## Watch faces
displayapp/screens/WatchFaceAnalog.cpp
@@ -429,6 +426,8 @@ list(APPEND SOURCE_FILES
#displayapp/screens/WatchFaceTerminal.cpp
displayapp/screens/WatchFacePineTimeStyle.cpp
#displayapp/screens/WatchFaceCasioStyleG7710.cpp
#displayapp/screens/WatchFaceFuzzy.cpp
#displayapp/screens/WatchFaceSundial.cpp
##
@@ -546,7 +545,6 @@ list(APPEND RECOVERY_SOURCE_FILES
systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp
drivers/TwiMaster.cpp
components/gfx/Gfx.cpp
components/rle/RleDecoder.cpp
components/heartrate/HeartRateController.cpp
heartratetask/HeartRateTask.cpp
@@ -576,7 +574,6 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
components/rle/RleDecoder.cpp
components/gfx/Gfx.cpp
drivers/St7789.cpp
components/brightness/BrightnessController.cpp
@@ -595,9 +592,7 @@ set(INCLUDE_FILES
displayapp/TouchEvents.h
displayapp/screens/Screen.h
displayapp/screens/Tile.h
displayapp/screens/InfiniPaint.h
displayapp/screens/StopWatch.h
displayapp/screens/Paddle.h
displayapp/screens/BatteryIcon.h
displayapp/screens/BleIcon.h
displayapp/screens/NotificationIcon.h
@@ -611,10 +606,7 @@ set(INCLUDE_FILES
displayapp/Apps.h
displayapp/screens/Notifications.h
displayapp/screens/HeartRate.h
displayapp/screens/Metronome.h
displayapp/screens/Motion.h
displayapp/screens/Timer.h
displayapp/screens/Alarm.h
displayapp/Colors.h
displayapp/widgets/Counter.h
displayapp/widgets/PageIndicator.h
@@ -915,6 +907,18 @@ target_compile_options(sunset PRIVATE
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
# SUNSET_SRC
add_library(sunset STATIC ${SUNSET_SRC})
target_include_directories(sunset SYSTEM PUBLIC . ../)
target_include_directories(sunset SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(sunset PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
# Build autonomous binary (without support for bootloader)
set(EXECUTABLE_NAME "pinetime-app")
set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
+3 -1
View File
@@ -62,7 +62,8 @@
#define configTICK_RATE_HZ 1024
#define configMAX_PRIORITIES (3)
#define configMINIMAL_STACK_SIZE (120)
#define configTOTAL_HEAP_SIZE (1024 * 40)
// how much heap can one smartwatch need, michael, 40kb?
#define configTOTAL_HEAP_SIZE (1024 * 39)
#define configMAX_TASK_NAME_LEN (4)
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
@@ -75,6 +76,7 @@
#define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 1
#define configUSE_TASK_NOTIFICATIONS 0
/* Hook function related definitions. */
#define configUSE_IDLE_HOOK 0
+2
View File
@@ -357,6 +357,8 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp
this->totalSize = totalSize;
this->expectedCrc = expectedCrc;
this->ready = true;
totalWriteIndex = 0;
bufferWriteIndex = 0;
}
void DfuService::DfuImage::Append(uint8_t* data, size_t size) {
+1 -1
View File
@@ -2,9 +2,9 @@
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
#include <host/ble_gap.h>
#include <atomic>
#undef max
#undef min
#include <atomic>
namespace Pinetime {
namespace Controllers {
+4
View File
@@ -120,3 +120,7 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
else if (attributeHandle == motionValuesHandle)
motionValuesNoficationEnabled = false;
}
bool MotionService::IsMotionNotificationSubscribed() const {
return motionValuesNoficationEnabled;
}
+1
View File
@@ -21,6 +21,7 @@ namespace Pinetime {
void SubscribeNotification(uint16_t attributeHandle);
void UnsubscribeNotification(uint16_t attributeHandle);
bool IsMotionNotificationSubscribed() const;
private:
NimbleController& nimble;
@@ -158,3 +158,16 @@ bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
std::strcmp(this->location.data(), other.location.data()) == 0;
}
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature;
}
bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {
for (int i = 0; i < this->nbDays; i++) {
if (this->days[i] != other.days[i]) {
return false;
}
}
return this->timestamp == other.timestamp && this->nbDays == other.nbDays;
}
@@ -96,9 +96,13 @@ namespace Pinetime {
int16_t minTemperature;
int16_t maxTemperature;
Icons iconId;
bool operator==(const Day& other) const;
};
std::array<Day, MaxNbForecastDays> days;
bool operator==(const Forecast& other) const;
};
std::optional<CurrentWeather> Current() const;
@@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
return MonthsStringLow[static_cast<uint8_t>(month)];
}
const char* DateTime::DayOfWeekShortToStringLow() const {
return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
const char* DateTime::DayOfWeekShortToStringLow(Days day) {
return DaysStringShortLow[static_cast<uint8_t>(day)];
}
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
@@ -129,7 +129,7 @@ std::string DateTime::FormattedTime() {
auto hour = Hours();
auto minute = Minutes();
// Return time as a string in 12- or 24-hour format
char buff[9];
char buff[11];
if (settingsController.GetClockType() == ClockType::H12) {
uint8_t hour12;
const char* amPmStr;
+1 -1
View File
@@ -122,7 +122,7 @@ namespace Pinetime {
const char* MonthShortToString() const;
const char* DayOfWeekShortToString() const;
static const char* MonthShortToStringLow(Months month);
const char* DayOfWeekShortToStringLow() const;
static const char* DayOfWeekShortToStringLow(Days day);
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
return currentDateTime;
-196
View File
@@ -1,196 +0,0 @@
#include "components/gfx/Gfx.h"
#include "drivers/St7789.h"
using namespace Pinetime::Components;
Gfx::Gfx(Pinetime::Drivers::St7789& lcd) : lcd {lcd} {
}
void Gfx::Init() {
}
void Gfx::ClearScreen() {
SetBackgroundColor(0x0000);
state.remainingIterations = 240 + 1;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(0, 0, width, height, reinterpret_cast<const uint8_t*>(buffer), width * 2);
WaitTransferFinished();
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
SetBackgroundColor(color);
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(buffer), width * 2);
WaitTransferFinished();
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = 0x00;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(b), width * 2);
WaitTransferFinished();
}
void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap) {
if (y > (height - p_font->height)) {
// Not enough space to write even single char.
return;
}
uint8_t current_x = x;
uint8_t current_y = y;
for (size_t i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n') {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
DrawChar(p_font, (uint8_t) text[i], &current_x, current_y, color);
}
uint8_t char_idx = text[i] - p_font->startChar;
uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
if (current_x > (width - char_width)) {
if (wrap) {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
break;
}
if (y > (height - p_font->height)) {
break;
}
}
}
}
void Gfx::DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color) {
uint8_t char_idx = c - font->startChar;
uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
uint16_t bg = 0x0000;
if (c == ' ') {
*x += font->height / 2;
return;
}
// Build first line
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
buffer[(j * 8) + k] = color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
state.remainingIterations = font->height + 0;
state.currentIteration = 0;
state.busy = true;
state.action = Action::DrawChar;
state.font = const_cast<FONT_INFO*>(font);
state.character = c;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(*x, y, bytes_in_line * 8, font->height, reinterpret_cast<const uint8_t*>(&buffer), bytes_in_line * 8 * 2);
WaitTransferFinished();
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
}
void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
lcd.DrawPixel(x, y, color);
}
void Gfx::Sleep() {
lcd.Sleep();
}
void Gfx::Wakeup() {
lcd.Wakeup();
}
void Gfx::SetBackgroundColor(uint16_t color) {
for (int i = 0; i < width; i++) {
buffer[i] = color;
}
}
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
if (!state.busy)
return false;
state.remainingIterations = state.remainingIterations - 1;
if (state.remainingIterations == 0) {
state.busy = false;
NotifyEndOfTransfer(state.taskToNotify);
return false;
}
if (state.action == Action::FillRectangle) {
*data = reinterpret_cast<uint8_t*>(buffer);
size = width * 2;
} else if (state.action == Action::DrawChar) {
uint16_t bg = 0x0000;
uint8_t char_idx = state.character - state.font->startChar;
uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration + 1) * bytes_in_line) + j]) {
buffer[(j * 8) + k] = state.color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
*data = reinterpret_cast<uint8_t*>(buffer);
size = bytes_in_line * 8 * 2;
}
state.currentIteration = state.currentIteration + 1;
return true;
}
void Gfx::NotifyEndOfTransfer(TaskHandle_t task) {
if (task != nullptr) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
void Gfx::WaitTransferFinished() const {
ulTaskNotifyTake(pdTRUE, 500);
}
void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
}
void Gfx::SetScrollStartLine(uint16_t line) {
lcd.VerticalScrollStartAddress(line);
}
-62
View File
@@ -1,62 +0,0 @@
#pragma once
#include <FreeRTOS.h>
#include <nrf_font.h>
#include <task.h>
#include <cstddef>
#include <cstdint>
#include "drivers/BufferProvider.h"
namespace Pinetime {
namespace Drivers {
class St7789;
}
namespace Components {
class Gfx : public Pinetime::Drivers::BufferProvider {
public:
explicit Gfx(Drivers::St7789& lcd);
void Init();
void ClearScreen();
void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap);
void DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void SetScrollStartLine(uint16_t line);
void Sleep();
void Wakeup();
bool GetNextBuffer(uint8_t** buffer, size_t& size) override;
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
private:
static constexpr uint8_t width = 240;
static constexpr uint8_t height = 240;
enum class Action { None, FillRectangle, DrawChar };
struct State {
State() : busy {false}, action {Action::None}, remainingIterations {0}, currentIteration {0} {
}
volatile bool busy;
volatile Action action;
volatile uint16_t remainingIterations;
volatile uint16_t currentIteration;
volatile FONT_INFO* font;
volatile uint16_t color;
volatile uint8_t character;
volatile TaskHandle_t taskToNotify = nullptr;
};
volatile State state;
uint16_t buffer[width]; // 1 line buffer
Drivers::St7789& lcd;
void SetBackgroundColor(uint16_t color);
void WaitTransferFinished() const;
void NotifyEndOfTransfer(TaskHandle_t task);
};
}
}
+18 -6
View File
@@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
service->OnNewStepCountValue(nbSteps);
}
if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) {
if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) {
service->OnNewMotionValues(x, y, z);
}
lastTime = time;
time = xTaskGetTickCount();
lastX = this->x;
this->x = x;
xHistory++;
xHistory[0] = x;
yHistory++;
yHistory[0] = y;
zHistory++;
@@ -67,20 +67,26 @@ MotionController::AccelStats MotionController::GetAccelStats() const {
AccelStats stats;
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
stats.xMean += xHistory[histSize - i];
stats.yMean += yHistory[histSize - i];
stats.zMean += zHistory[histSize - i];
stats.prevXMean += xHistory[1 + i];
stats.prevYMean += yHistory[1 + i];
stats.prevZMean += zHistory[1 + i];
}
stats.xMean /= AccelStats::numHistory;
stats.yMean /= AccelStats::numHistory;
stats.zMean /= AccelStats::numHistory;
stats.prevXMean /= AccelStats::numHistory;
stats.prevYMean /= AccelStats::numHistory;
stats.prevZMean /= AccelStats::numHistory;
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
stats.xVariance += (xHistory[histSize - i] - stats.xMean) * (xHistory[histSize - i] - stats.xMean);
stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
}
stats.xVariance /= AccelStats::numHistory;
stats.yVariance /= AccelStats::numHistory;
stats.zVariance /= AccelStats::numHistory;
@@ -93,7 +99,7 @@ bool MotionController::ShouldRaiseWake() const {
constexpr int16_t yThresh = -64;
constexpr int16_t rollDegreesThresh = -45;
if (x < -xThresh || x > xThresh) {
if (std::abs(stats.xMean) > xThresh) {
return false;
}
@@ -107,8 +113,9 @@ bool MotionController::ShouldRaiseWake() const {
bool MotionController::ShouldShakeWake(uint16_t thresh) {
/* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
int32_t speed =
std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 +
(xHistory[0] - xHistory[histSize - 1]) / 4) *
100 / (time - lastTime);
// (.2 * speed) + ((1 - .2) * accumulatedSpeed);
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
@@ -116,6 +123,11 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) {
}
bool MotionController::ShouldLowerSleep() const {
if ((stats.xMean > 887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) > 30) ||
(stats.xMean < -887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) < -30)) {
return true;
}
if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
return false;
}
+9 -3
View File
@@ -21,7 +21,7 @@ namespace Pinetime {
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
int16_t X() const {
return x;
return xHistory[0];
}
int16_t Y() const {
@@ -62,6 +62,10 @@ namespace Pinetime {
this->service = service;
}
Pinetime::Controllers::MotionService* GetService() const {
return service;
}
private:
uint32_t nbSteps = 0;
uint32_t currentTripSteps = 0;
@@ -72,11 +76,14 @@ namespace Pinetime {
struct AccelStats {
static constexpr uint8_t numHistory = 2;
int16_t xMean = 0;
int16_t yMean = 0;
int16_t zMean = 0;
int16_t prevXMean = 0;
int16_t prevYMean = 0;
int16_t prevZMean = 0;
uint32_t xVariance = 0;
uint32_t yVariance = 0;
uint32_t zVariance = 0;
};
@@ -85,9 +92,8 @@ namespace Pinetime {
AccelStats stats = {};
int16_t lastX = 0;
int16_t x = 0;
static constexpr uint8_t histSize = 8;
Utility::CircularBuffer<int16_t, histSize> xHistory = {};
Utility::CircularBuffer<int16_t, histSize> yHistory = {};
Utility::CircularBuffer<int16_t, histSize> zHistory = {};
int32_t accumulatedSpeed = 0;
+2 -2
View File
@@ -9,7 +9,7 @@ namespace Pinetime {
namespace Controllers {
class Settings {
public:
enum class ClockType : uint8_t { H24, H12, Fuzzy };
enum class ClockType : uint8_t { H24, H12, FUZZY };
enum class WeatherFormat : uint8_t { Metric, Imperial };
enum class Notification : uint8_t { On, Off, Sleep };
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
@@ -329,7 +329,7 @@ namespace Pinetime {
uint16_t shakeWakeThreshold = 150;
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
Location location = {(int16_t)0,(int16_t)0,(int8_t)0};
Location location = {(int16_t)44,(int16_t)-123,(int8_t)-8};
};
SettingsData settings;
+9 -9
View File
@@ -26,6 +26,8 @@
#include "displayapp/screens/FlashLight.h"
#include "displayapp/screens/BatteryInfo.h"
#include "displayapp/screens/Steps.h"
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Weather.h"
#include "displayapp/screens/PassKey.h"
#include "displayapp/screens/Error.h"
@@ -48,6 +50,7 @@
#include "displayapp/screens/settings/SettingChimes.h"
#include "displayapp/screens/settings/SettingShakeThreshold.h"
#include "displayapp/screens/settings/SettingBluetooth.h"
#include "displayapp/screens/settings/SettingLocation.h"
#include "libs/lv_conf.h"
#include "UserApps.h"
@@ -124,6 +127,7 @@ void DisplayApp::Start(System::BootErrors error) {
bootError = error;
lvgl.Init();
motorController.Init();
if (error == System::BootErrors::TouchController) {
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
@@ -141,9 +145,6 @@ void DisplayApp::Process(void* instance) {
NRF_LOG_INFO("displayapp task started!");
app->InitHw();
// Send a dummy notification to unlock the lvgl display driver for the first iteration
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
while (true) {
app->Refresh();
}
@@ -152,7 +153,6 @@ void DisplayApp::Process(void* instance) {
void DisplayApp::InitHw() {
brightnessController.Init();
ApplyBrightness();
motorController.Init();
lcd.Init();
}
@@ -482,6 +482,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
else {
currentScreen.reset(userWatchFaces[0].create(controllers));
}
settingsController.SetAppMenu(0);
} break;
case Apps::Error:
currentScreen = std::make_unique<Screens::Error>(bootError);
@@ -527,10 +528,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
break;
case Apps::SettingWatchFace: {
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
int i = 0;
for (const auto& userWatchFace : userWatchFaces) {
items[i++] = Screens::CheckboxList::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)};
items[i++] =
Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
}
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
} break;
@@ -599,9 +601,7 @@ void DisplayApp::PushMessage(Messages msg) {
if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} else {
TickType_t timeout = portMAX_DELAY;
// Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
+2 -11
View File
@@ -38,9 +38,6 @@ void DisplayApp::Process(void* instance) {
auto* app = static_cast<DisplayApp*>(instance);
NRF_LOG_INFO("displayapp task started!");
// Send a dummy notification to unlock the lvgl display driver for the first iteration
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
app->InitHw();
while (true) {
app->Refresh();
@@ -94,7 +91,6 @@ void DisplayApp::DisplayLogo(uint16_t color) {
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
for (int i = 0; i < displayWidth; i++) {
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
ulTaskNotifyTake(pdTRUE, 500);
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
}
}
@@ -103,20 +99,15 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) {
const uint8_t barHeight = 20;
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
for (int i = 0; i < barHeight; i++) {
ulTaskNotifyTake(pdTRUE, 500);
uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
}
}
void DisplayApp::PushMessage(Display::Messages msg) {
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
/* Actual macro used here is port specific. */
// TODO : should I do something here?
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
-1
View File
@@ -5,7 +5,6 @@
#include <drivers/SpiMaster.h>
#include <bits/unique_ptr.h>
#include <queue.h>
#include "components/gfx/Gfx.h"
#include "drivers/Cst816s.h"
#include <drivers/Watchdog.h>
#include <components/motor/MotorController.h>
-5
View File
@@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
uint16_t y1, y2, width, height = 0;
ulTaskNotifyTake(pdTRUE, 200);
// Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
// which cannot be set/clear during a transfer.
if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
@@ -219,7 +215,6 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
if (height > 0) {
lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
ulTaskNotifyTake(pdTRUE, 100);
}
uint16_t pixOffset = width * height;
+3
View File
@@ -3,6 +3,7 @@
#include "Controllers.h"
#include "displayapp/screens/Alarm.h"
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Timer.h"
#include "displayapp/screens/Twos.h"
#include "displayapp/screens/Tile.h"
@@ -13,6 +14,8 @@
// #include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFacePineTimeStyle.h"
// #include "displayapp/screens/WatchFaceTerminal.h"
// #include "displayapp/screens/WatchFaceFuzzy.h"
// #include "displayapp/screens/WatchFaceSundial.h"
namespace Pinetime {
namespace Applications {
+12 -2
View File
@@ -27,6 +27,8 @@ namespace Pinetime {
Metronome,
Motion,
Steps,
Dice,
Weather,
PassKey,
QuickSettings,
Settings,
@@ -41,13 +43,17 @@ namespace Pinetime {
SettingChimes,
SettingShakeThreshold,
SettingBluetooth,
Error,
Weather
Error
};
enum class WatchFace : uint8_t {
Analog,
PineTimeStyle,
#Terminal,
#Fuzzy,
#Sundial,
#Infineat,
#CasioStyleG7710,
};
template <Apps>
@@ -68,8 +74,12 @@ namespace Pinetime {
static constexpr size_t Count = sizeof...(Ws);
};
<<<<<<< HEAD
using UserWatchFaceTypes = WatchFaceTypeList<WatchFace::Analog,
WatchFace::PineTimeStyle>;
=======
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
>>>>>>> main
static_assert(UserWatchFaceTypes::Count >= 1);
}
+22 -6
View File
@@ -1,9 +1,25 @@
#if(DEFINED ENABLE_USERAPPS)
# set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
#else ()
set(USERAPP_TYPES "Apps::Navigation, Apps::StopWatch, Apps::Timer, Apps::Alarm, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Twos" CACHE STRING "List of user apps to build into the firmware")
#Apps::Paint, Apps::Metronome, Apps::Paddle
#endif ()
#"Apps::Navigation, Apps::StopWatch, Apps::Timer, Apps::Alarm, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Twos"
#Apps::Paint, Apps::Metronome, Apps::Paddle
if(DEFINED ENABLE_USERAPPS)
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
else ()
set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
endif ()
if(DEFINED ENABLE_WATCHFACES)
set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware")
else()
set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Fuzzy")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Sundial")
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
endif()
add_library(infinitime_apps INTERFACE)
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
+1 -1
View File
@@ -16,7 +16,7 @@
- Define the new symbols in `src/displayapp/screens/Symbols.h`:
```
static constexpr const char* newSymbol = "\xEF\x86\x85";
static constexpr const char* newSymbol = "\xEF\x99\x81";
```
### the config file format:
+21 -7
View File
@@ -2,17 +2,30 @@
"jetbrains_mono_bold_20": {
"sources": [
{
"file": "JetBrainsMono-Bold.ttf",
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
"file": "Vulf_Mono-Italic.woff",
"range": "0x20-0x7e, 0xB0"
},
{
"file": "FontAwesome5-Solid+Brands+Regular.woff",
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf743"
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
}
],
"bpp": 1,
"size": 20,
"patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
"size": 20
},
"jetbrains_mono_bold_24": {
"sources": [
{
"file": "Vulf_Mono-Italic.woff",
"range": "0x20-0x7e, 0xB0"
},
{
"file": "FontAwesome5-Solid+Brands+Regular.woff",
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
}
],
"bpp": 1,
"size": 26
},
"noto_serif_cjk_20": {
"sources": [
@@ -39,7 +52,8 @@
{
"file": "JetBrainsMono-Regular.ttf",
"disabledfile": "Vulf Mono Light Italic.ttf",
"range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a"
"range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a",
"disabledrange": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
}
],
"bpp": 1,
@@ -49,7 +63,7 @@
"sources": [
{
"file": "JetBrainsMono-Light.ttf",
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
}
],
"bpp": 1,
+1 -1
View File
@@ -69,7 +69,7 @@ namespace Pinetime {
template <>
struct AppTraits<Apps::Alarm> {
static constexpr Apps app = Apps::Alarm;
static constexpr const char* icon = Screens::Symbols::clock;
static constexpr const char* icon = Screens::Symbols::bell;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Alarm(controllers.alarmController,
+199
View File
@@ -0,0 +1,199 @@
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
#include "components/settings/Settings.h"
#include "components/motor/MotorController.h"
#include "components/motion/MotionController.h"
using namespace Pinetime::Applications::Screens;
namespace {
lv_obj_t* MakeLabel(lv_font_t* font,
lv_color_t color,
lv_label_long_mode_t longMode,
uint8_t width,
lv_label_align_t labelAlignment,
const char* text,
lv_obj_t* reference,
lv_align_t alignment,
int8_t x,
int8_t y) {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
lv_label_set_long_mode(label, longMode);
if (width != 0) {
lv_obj_set_width(label, width);
}
lv_label_set_align(label, labelAlignment);
lv_label_set_text(label, text);
lv_obj_align(label, reference, alignment, x, y);
return label;
}
void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<Dice*>(obj->user_data);
if (event == LV_EVENT_CLICKED) {
screen->Roll();
}
}
}
Dice::Dice(Controllers::MotionController& motionController,
Controllers::MotorController& motorController,
Controllers::Settings& settingsController)
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
static_cast<uint32_t>(motionController.X()),
static_cast<uint32_t>(motionController.Y()),
static_cast<uint32_t>(motionController.Z())};
gen.seed(sseq);
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
LV_COLOR_WHITE,
LV_LABEL_LONG_EXPAND,
0,
LV_LABEL_ALIGN_CENTER,
"count",
lv_scr_act(),
LV_ALIGN_IN_TOP_LEFT,
0,
0);
lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
LV_COLOR_WHITE,
LV_LABEL_LONG_EXPAND,
0,
LV_LABEL_ALIGN_CENTER,
"sides",
nCounterLabel,
LV_ALIGN_OUT_RIGHT_MID,
20,
0);
nCounter.Create();
lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
nCounter.SetValue(1);
dCounter.Create();
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
dCounter.SetValue(6);
std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
currentColorIndex = distrib(gen);
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
resultColors[currentColorIndex],
LV_LABEL_LONG_BREAK,
120,
LV_LABEL_ALIGN_CENTER,
"",
lv_scr_act(),
LV_ALIGN_IN_TOP_RIGHT,
11,
38);
resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20,
resultColors[currentColorIndex],
LV_LABEL_LONG_BREAK,
90,
LV_LABEL_ALIGN_CENTER,
"",
resultTotalLabel,
LV_ALIGN_OUT_BOTTOM_MID,
0,
10);
Roll();
openingRoll = false;
btnRoll = lv_btn_create(lv_scr_act(), nullptr);
btnRoll->user_data = this;
lv_obj_set_event_cb(btnRoll, btnRollEventHandler);
lv_obj_set_size(btnRoll, 240, 50);
lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
btnRollLabel = MakeLabel(&jetbrains_mono_bold_20,
LV_COLOR_WHITE,
LV_LABEL_LONG_EXPAND,
0,
LV_LABEL_ALIGN_CENTER,
Symbols::dice,
btnRoll,
LV_ALIGN_CENTER,
0,
0);
// Spagetti code in motion controller: it only updates the shake speed when shake to wake is on...
enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake);
if (enableShakeForDice) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true);
}
refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
}
Dice::~Dice() {
// reset the shake to wake mode.
if (enableShakeForDice) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false);
enableShakeForDice = false;
}
lv_task_del(refreshTask);
lv_obj_clean(lv_scr_act());
}
void Dice::Refresh() {
// we only reset the hysteresis when at rest
if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) {
if (currentRollHysteresis <= 0) {
// this timestamp is used for the screen timeout
lv_disp_get_next(NULL)->last_activity_time = lv_tick_get();
Roll();
}
} else if (currentRollHysteresis > 0)
--currentRollHysteresis;
}
void Dice::Roll() {
uint8_t resultIndividual;
uint16_t resultTotal = 0;
std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
lv_label_set_text(resultIndividualLabel, "");
if (nCounter.GetValue() == 1) {
resultTotal = distrib(gen);
if (dCounter.GetValue() == 2) {
switch (resultTotal) {
case 1:
lv_label_set_text(resultIndividualLabel, "HEADS");
break;
case 2:
lv_label_set_text(resultIndividualLabel, "TAILS");
break;
}
}
} else {
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
resultIndividual = distrib(gen);
resultTotal += resultIndividual;
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
if (i < (nCounter.GetValue() - 1)) {
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+");
}
}
}
lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal);
if (openingRoll == false) {
motorController.RunForDuration(30);
NextColor();
currentRollHysteresis = rollHysteresis;
}
}
void Dice::NextColor() {
currentColorIndex = (currentColorIndex + 1) % resultColors.size();
lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
}
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/widgets/Counter.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
#include <array>
#include <random>
namespace Pinetime {
namespace Applications {
namespace Screens {
class Dice : public Screen {
public:
Dice(Controllers::MotionController& motionController,
Controllers::MotorController& motorController,
Controllers::Settings& settingsController);
~Dice() override;
void Roll();
void Refresh() override;
private:
lv_obj_t* btnRoll;
lv_obj_t* btnRollLabel;
lv_obj_t* resultTotalLabel;
lv_obj_t* resultIndividualLabel;
lv_task_t* refreshTask;
bool enableShakeForDice = false;
std::mt19937 gen;
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
uint8_t currentColorIndex;
void NextColor();
Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42);
Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42);
bool openingRoll = true;
uint8_t currentRollHysteresis = 0;
static constexpr uint8_t rollHysteresis = 10;
Controllers::MotorController& motorController;
Controllers::MotionController& motionController;
Controllers::Settings& settingsController;
};
}
template <>
struct AppTraits<Apps::Dice> {
static constexpr Apps app = Apps::Dice;
static constexpr const char* icon = Screens::Symbols::dice;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
};
};
}
}
+7 -3
View File
@@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
}
lv_label_set_text_static(label_hr, "000");
lv_label_set_text_static(label_hr, "---");
lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
label_bpm = lv_label_create(lv_scr_act(), nullptr);
@@ -82,10 +82,14 @@ void HeartRate::Refresh() {
case Controllers::HeartRateController::States::NoTouch:
case Controllers::HeartRateController::States::NotEnoughData:
// case Controllers::HeartRateController::States::Stopped:
lv_label_set_text_static(label_hr, "000");
lv_label_set_text_static(label_hr, "---");
break;
default:
lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
if (heartRateController.HeartRate() == 0) {
lv_label_set_text_static(label_hr, "---");
} else {
lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
}
}
lv_label_set_text_static(label_status, ToString(state));
+4 -4
View File
@@ -53,9 +53,9 @@ void Motion::Refresh() {
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
lv_label_set_text_fmt(label,
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#",
motionController.X() / 0x10,
motionController.Y() / 0x10,
motionController.Z() / 0x10);
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg",
motionController.X(),
motionController.Y(),
motionController.Z());
lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
}
+2
View File
@@ -11,6 +11,7 @@ namespace Pinetime {
static constexpr const char* plug = "\xEF\x87\xA6";
static constexpr const char* shoe = "\xEF\x95\x8B";
static constexpr const char* clock = "\xEF\x80\x97";
static constexpr const char* bell = "\xEF\x83\xB3";
static constexpr const char* info = "\xEF\x84\xA9";
static constexpr const char* list = "\xEF\x80\xBA";
static constexpr const char* sun = "\xEF\x86\x85";
@@ -34,6 +35,7 @@ namespace Pinetime {
static constexpr const char* hourGlass = "\xEF\x89\x92";
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
static constexpr const char* drum = "\xEF\x95\xA9";
static constexpr const char* dice = "\xEF\x94\xA2";
static constexpr const char* eye = "\xEF\x81\xAE";
static constexpr const char* home = "\xEF\x80\x95";
static constexpr const char* sleep = "\xEE\xBD\x84";
+1 -1
View File
@@ -407,7 +407,7 @@ void WatchFaceAnalog::Refresh() {
if (currentDateTime.IsUpdated()) {
UpdateClock();
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
/*char const* MonthsString[] = {"--", "IANUARIUS","FEBRUARIUS","MARTIUS","APRILIS","MARTIUSIUNIUS","QUINTILIS","SEXTILIS","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"};
+1 -2
View File
@@ -44,8 +44,7 @@ namespace Pinetime {
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
Utility::DirtyValue<bool> notificationState {false};
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;
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_obj_t* minor_scales;
lv_obj_t* major_scales;
@@ -0,0 +1,334 @@
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/BleIcon.h"
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::FS& filesystem)
: currentDateTime {{}},
batteryIcon(false),
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificatioManager {notificatioManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController} {
lfs_file f = {};
if (filesystem.FileOpen(&f, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_dot40 = lv_font_load("F:/fonts/lv_font_dots_40.bin");
}
if (filesystem.FileOpen(&f, "/fonts/7segments_40.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_segment40 = lv_font_load("F:/fonts/7segments_40.bin");
}
if (filesystem.FileOpen(&f, "/fonts/7segments_115.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_segment115 = lv_font_load("F:/fonts/7segments_115.bin");
}
label_battery_value = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_battery_value, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
lv_obj_set_style_local_text_color(label_battery_value, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(label_battery_value, "00%");
batteryIcon.Create(lv_scr_act());
batteryIcon.SetColor(color_text);
lv_obj_align(batteryIcon.GetObject(), label_battery_value, LV_ALIGN_OUT_LEFT_MID, -5, 0);
batteryPlug = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(batteryPlug, Symbols::plug);
lv_obj_align(batteryPlug, batteryIcon.GetObject(), LV_ALIGN_OUT_LEFT_MID, -5, 0);
bleIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(bleIcon, Symbols::bluetooth);
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, bleIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
label_day_of_week = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_day_of_week, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, 64);
lv_obj_set_style_local_text_color(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40);
lv_label_set_text_static(label_day_of_week, "SUN");
label_week_number = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_week_number, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 22);
lv_obj_set_style_local_text_color(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40);
lv_label_set_text_static(label_week_number, "WK26");
label_day_of_year = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_day_of_year, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 30);
lv_obj_set_style_local_text_color(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40);
lv_label_set_text_static(label_day_of_year, "181-184");
lv_style_init(&style_line);
lv_style_set_line_width(&style_line, LV_STATE_DEFAULT, 2);
lv_style_set_line_color(&style_line, LV_STATE_DEFAULT, color_text);
lv_style_set_line_rounded(&style_line, LV_STATE_DEFAULT, true);
lv_style_init(&style_border);
lv_style_set_line_width(&style_border, LV_STATE_DEFAULT, 6);
lv_style_set_line_color(&style_border, LV_STATE_DEFAULT, color_text);
lv_style_set_line_rounded(&style_border, LV_STATE_DEFAULT, true);
line_icons = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_icons, line_icons_points, 3);
lv_obj_add_style(line_icons, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_icons, nullptr, LV_ALIGN_IN_TOP_RIGHT, -10, 18);
line_day_of_week_number = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_day_of_week_number, line_day_of_week_number_points, 4);
lv_obj_add_style(line_day_of_week_number, LV_LINE_PART_MAIN, &style_border);
lv_obj_align(line_day_of_week_number, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 8);
line_day_of_year = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_day_of_year, line_day_of_year_points, 3);
lv_obj_add_style(line_day_of_year, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_day_of_year, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 60);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 70);
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40);
lv_label_set_text_static(label_date, "6-30");
line_date = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_date, line_date_points, 3);
lv_obj_add_style(line_date, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_date, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 100);
label_time = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment115);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40);
line_time = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_time, line_time_points, 3);
lv_obj_add_style(line_time, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_time, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, -25);
label_time_ampm = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_time_ampm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(label_time_ampm, "");
lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 5, -5);
backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_click(backgroundLabel, true);
lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
lv_obj_set_size(backgroundLabel, 240, 240);
lv_obj_set_pos(backgroundLabel, 0, 0);
lv_label_set_text_static(backgroundLabel, "");
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(heartbeatValue, "");
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(stepValue, "0");
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(stepIcon, Symbols::shoe);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() {
lv_task_del(taskRefresh);
lv_style_reset(&style_line);
lv_style_reset(&style_border);
if (font_dot40 != nullptr) {
lv_font_free(font_dot40);
}
if (font_segment40 != nullptr) {
lv_font_free(font_segment40);
}
if (font_segment115 != nullptr) {
lv_font_free(font_segment115);
}
lv_obj_clean(lv_scr_act());
}
void WatchFaceCasioStyleG7710::Refresh() {
powerPresent = batteryController.IsPowerPresent();
if (powerPresent.IsUpdated()) {
lv_label_set_text_static(batteryPlug, BatteryIcon::GetPlugIcon(powerPresent.Get()));
}
batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated()) {
auto batteryPercent = batteryPercentRemaining.Get();
batteryIcon.SetBatteryPercentage(batteryPercent);
lv_label_set_text_fmt(label_battery_value, "%d%%", batteryPercent);
}
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get()));
}
lv_obj_realign(label_battery_value);
lv_obj_realign(batteryIcon.GetObject());
lv_obj_realign(batteryPlug);
lv_obj_realign(bleIcon);
lv_obj_realign(notificationIcon);
notificationState = notificatioManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
}
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
char ampmChar[2] = "A";
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(label_time_ampm, ampmChar);
lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute);
} else {
lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute);
}
lv_obj_realign(label_time);
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
const char* weekNumberFormat = "%V";
uint16_t year = dateTimeController.Year();
Controllers::DateTime::Months month = dateTimeController.Month();
uint8_t day = dateTimeController.Day();
int dayOfYear = dateTimeController.DayOfYear();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
// 24h mode: ddmmyyyy, first DOW=Monday;
lv_label_set_text_fmt(label_date, "%3d-%2d", day, month);
weekNumberFormat = "%V"; // Replaced by the week number of the year (Monday as the first day of the week) as a decimal number
// [01,53]. If the week containing 1 January has four or more days in the new year, then it is considered
// week 1. Otherwise, it is the last week of the previous year, and the next week is week 1. Both January
// 4th and the first Thursday of January are always in week 1. [ tm_year, tm_wday, tm_yday]
} else {
// 12h mode: mmddyyyy, first DOW=Sunday;
lv_label_set_text_fmt(label_date, "%3d-%2d", month, day);
weekNumberFormat = "%U"; // Replaced by the week number of the year as a decimal number [00,53]. The first Sunday of January is the
// first day of week 1; days in the new year before this are in week 0. [ tm_year, tm_wday, tm_yday]
}
time_t ttTime =
std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(currentDateTime.Get()));
tm* tmTime = std::localtime(&ttTime);
// TODO: When we start using C++20, use std::chrono::year::is_leap
int daysInCurrentYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? 366 : 365;
uint16_t daysTillEndOfYearNumber = daysInCurrentYear - dayOfYear;
char buffer[8];
strftime(buffer, 8, weekNumberFormat, tmTime);
uint8_t weekNumber = atoi(buffer);
lv_label_set_text_fmt(label_day_of_week, "%s", dateTimeController.DayOfWeekShortToString());
lv_label_set_text_fmt(label_day_of_year, "%3d-%3d", dayOfYear, daysTillEndOfYearNumber);
lv_label_set_text_fmt(label_week_number, "WK%02d", weekNumber);
lv_obj_realign(label_day_of_week);
lv_obj_realign(label_day_of_year);
lv_obj_realign(label_week_number);
lv_obj_realign(label_date);
}
}
heartbeat = heartRateController.HeartRate();
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
if (heartbeatRunning.Get()) {
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
} else {
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
lv_label_set_text_static(heartbeatValue, "");
}
lv_obj_realign(heartbeatIcon);
lv_obj_realign(heartbeatValue);
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_realign(stepValue);
lv_obj_realign(stepIcon);
}
}
bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) {
lfs_file file = {};
if (filesystem.FileOpen(&file, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/7segments_40.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/7segments_115.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
return true;
}
@@ -0,0 +1,126 @@
#pragma once
#include <displayapp/screens/BatteryIcon.h>
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include <displayapp/Controllers.h>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "components/ble/BleController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceCasioStyleG7710 : public Screen {
public:
WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::FS& filesystem);
~WatchFaceCasioStyleG7710() override;
void Refresh() override;
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
private:
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
Utility::DirtyValue<bool> powerPresent {};
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<bool> bleRadioEnabled {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
Utility::DirtyValue<uint32_t> stepCount {};
Utility::DirtyValue<uint8_t> heartbeat {};
Utility::DirtyValue<bool> heartbeatRunning {};
Utility::DirtyValue<bool> notificationState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}};
lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}};
lv_point_t line_day_of_year_points[3] {{0, 5}, {130, 5}, {135, 0}};
lv_point_t line_date_points[3] {{0, 5}, {135, 5}, {140, 0}};
lv_point_t line_time_points[3] {{0, 0}, {230, 0}, {235, 5}};
lv_color_t color_text = lv_color_hex(0x98B69A);
lv_style_t style_line;
lv_style_t style_border;
lv_obj_t* label_time;
lv_obj_t* line_time;
lv_obj_t* label_time_ampm;
lv_obj_t* label_date;
lv_obj_t* line_date;
lv_obj_t* label_day_of_week;
lv_obj_t* label_week_number;
lv_obj_t* line_day_of_week_number;
lv_obj_t* label_day_of_year;
lv_obj_t* line_day_of_year;
lv_obj_t* backgroundLabel;
lv_obj_t* bleIcon;
lv_obj_t* batteryPlug;
lv_obj_t* label_battery_value;
lv_obj_t* heartbeatIcon;
lv_obj_t* heartbeatValue;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* line_icons;
BatteryIcon batteryIcon;
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificatioManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
lv_task_t* taskRefresh;
lv_font_t* font_dot40 = nullptr;
lv_font_t* font_segment40 = nullptr;
lv_font_t* font_segment115 = nullptr;
};
}
template <>
struct WatchFaceTraits<WatchFace::CasioStyleG7710> {
static constexpr WatchFace watchFace = WatchFace::CasioStyleG7710;
static constexpr const char* name = "Casio G7710";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceCasioStyleG7710(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController,
controllers.filesystem);
};
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
return Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem);
}
};
}
}
+193
View File
@@ -0,0 +1,193 @@
#include "displayapp/screens/WatchFaceDigital.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weatherService)
: currentDateTime {{}},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController},
weatherService {weatherService},
statusIcons(batteryController, bleController) {
statusIcons.Create();
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
weatherIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
lv_label_set_text(weatherIcon, "");
lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50);
lv_obj_set_auto_realign(weatherIcon, true);
temperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
lv_label_set_text(temperature, "");
lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
label_time = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
label_time_ampm = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(label_time_ampm, "");
lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -30, -55);
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_label_set_text_static(heartbeatValue, "");
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
lv_label_set_text_static(stepValue, "0");
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
lv_label_set_text_static(stepIcon, Symbols::shoe);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceDigital::~WatchFaceDigital() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void WatchFaceDigital::Refresh() {
statusIcons.Update();
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
}
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
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(label_time_ampm, ampmChar);
lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
} else {
lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
}
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
uint16_t year = dateTimeController.Year();
uint8_t day = dateTimeController.Day();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
lv_label_set_text_fmt(label_date,
"%s %d %s %d",
dateTimeController.DayOfWeekShortToString(),
day,
dateTimeController.MonthShortToString(),
year);
} else {
lv_label_set_text_fmt(label_date,
"%s %s %d %d",
dateTimeController.DayOfWeekShortToString(),
dateTimeController.MonthShortToString(),
day,
year);
}
lv_obj_realign(label_date);
}
}
heartbeat = heartRateController.HeartRate();
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
if (heartbeatRunning.Get()) {
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
} else {
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
lv_label_set_text_static(heartbeatValue, "");
}
lv_obj_realign(heartbeatIcon);
lv_obj_realign(heartbeatValue);
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_realign(stepValue);
lv_obj_realign(stepIcon);
}
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
tempUnit = 'F';
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else {
lv_label_set_text_static(temperature, "");
lv_label_set_text(weatherIcon, "");
}
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}
}
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/ble/BleController.h"
#include "displayapp/widgets/StatusIcons.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceDigital : public Screen {
public:
WatchFaceDigital(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weather);
~WatchFaceDigital() override;
void Refresh() override;
private:
uint8_t displayedHour = -1;
uint8_t displayedMinute = -1;
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
Utility::DirtyValue<uint32_t> stepCount {};
Utility::DirtyValue<uint8_t> heartbeat {};
Utility::DirtyValue<bool> heartbeatRunning {};
Utility::DirtyValue<bool> notificationState {};
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_obj_t* label_time;
lv_obj_t* label_time_ampm;
lv_obj_t* label_date;
lv_obj_t* heartbeatIcon;
lv_obj_t* heartbeatValue;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* weatherIcon;
lv_obj_t* temperature;
Controllers::DateTime& dateTimeController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
Controllers::SimpleWeatherService& weatherService;
lv_task_t* taskRefresh;
Widgets::StatusIcons statusIcons;
};
}
template <>
struct WatchFaceTraits<WatchFace::Digital> {
static constexpr WatchFace watchFace = WatchFace::Digital;
static constexpr const char* name = "Digital face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceDigital(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController,
*controllers.weatherController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}
+189
View File
@@ -0,0 +1,189 @@
#include "displayapp/screens/WatchFaceFuzzy.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceFuzzy::WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weatherService)
: currentDateTime {{}},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController},
weatherService {weatherService}
{
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
weatherIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
lv_label_set_text(weatherIcon, "");
lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50);
lv_obj_set_auto_realign(weatherIcon, true);
temperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
lv_label_set_text(temperature, "");
lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
label_time = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_24);
lv_label_set_recolor(label_time, true);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_label_set_text_static(heartbeatValue, "");
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
lv_label_set_text_static(stepValue, "0");
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
lv_label_set_text_static(stepIcon, Symbols::shoe);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceFuzzy::~WatchFaceFuzzy() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void WatchFaceFuzzy::Refresh() {
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
}
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
printTimeWords(hour, minute);
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDate.IsUpdated()) {
uint16_t year = dateTimeController.Year();
uint8_t day = dateTimeController.Day();
lv_label_set_text_fmt(label_date,
"%s %d %s %d",
dateTimeController.DayOfWeekShortToString(),
day,
dateTimeController.MonthShortToString(),
year);
lv_obj_realign(label_date);
}
}
heartbeat = heartRateController.HeartRate();
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
if (heartbeatRunning.Get()) {
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
} else {
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
lv_label_set_text_static(heartbeatValue, "");
}
lv_obj_realign(heartbeatIcon);
lv_obj_realign(heartbeatValue);
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_realign(stepValue);
lv_obj_realign(stepIcon);
}
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
tempUnit = 'F';
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else {
lv_label_set_text_static(temperature, "");
lv_label_set_text(weatherIcon, "");
}
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}
}
char const* WatchFaceFuzzy::mods[] = {"", "five", "ten", "quarter", "twenty", "twenty five", "half"};
char const* WatchFaceFuzzy::nums[] = {"twelve", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"};
void WatchFaceFuzzy::printTimeWords(int h, int m) {
const char* mod;
if (m <= 30) {
mod = mods[m / 5];
} else {
mod = mods[(60-m) / 5];
}
h = (h % 12);
if (m >= 57) {
sprintf(timeStr, "#ffffff nearly %s#\n#808080 o' clock#", nums[(h+1) % 12]);
}
else if (m == 0 || m <= 4) {
sprintf(timeStr, "#ffffff %s#\n#808080 o' clock#", nums[h]);
}
else if (m <= 32) {
sprintf(timeStr, "#ffffff %s#\n#808080 past# #FFFFFF %s#", mod, nums[h]);
}
else if (m > 32) {
sprintf(timeStr, "#ffffff %s#\n#808080 to# #FFFFFF %s#", mod, nums[(h+1) % 12]);
}
printf("%s\n", timeStr);
lv_label_set_text(label_time, timeStr);
lv_obj_realign(label_time);
}
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/ble/BleController.h"
#include "displayapp/widgets/StatusIcons.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
extern lv_font_t jetbrains_mono_bold_24;
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceFuzzy : public Screen {
public:
WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weather);
~WatchFaceFuzzy() override;
void Refresh() override;
private:
uint8_t displayedHour = -1;
uint8_t displayedMinute = -1;
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
Utility::DirtyValue<uint32_t> stepCount {};
Utility::DirtyValue<uint8_t> heartbeat {};
Utility::DirtyValue<bool> heartbeatRunning {};
Utility::DirtyValue<bool> notificationState {};
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_obj_t* label_time;
lv_obj_t* label_date;
lv_obj_t* heartbeatIcon;
lv_obj_t* heartbeatValue;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* weatherIcon;
lv_obj_t* temperature;
Controllers::DateTime& dateTimeController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
Controllers::SimpleWeatherService& weatherService;
lv_task_t* taskRefresh;
static char const *nums[];
static char const *mods[];
char timeStr[64];
void printTimeWords(int hour, int minute);
};
}
template <>
struct WatchFaceTraits<WatchFace::Fuzzy> {
static constexpr WatchFace watchFace = WatchFace::Fuzzy;
static constexpr const char* name = "Fuzzy face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceFuzzy(controllers.dateTimeController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController,
*controllers.weatherController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}
@@ -0,0 +1,509 @@
#include "displayapp/screens/WatchFaceInfineat.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/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/motion/MotionController.h"
using namespace Pinetime::Applications::Screens;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<WatchFaceInfineat*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
enum class colors {
orange,
blue,
green,
rainbow,
gray,
nordBlue,
nordGreen,
};
constexpr int nColors = 7; // must match number of colors in InfineatColors
constexpr int nLines = WatchFaceInfineat::nLines;
constexpr std::array<lv_color_t, nLines> orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b),
LV_COLOR_MAKE(0xdb, 0x33, 0x16),
LV_COLOR_MAKE(0x6f, 0x10, 0x00),
LV_COLOR_MAKE(0xfd, 0x7a, 0x0a),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xe8, 0x51, 0x02),
LV_COLOR_MAKE(0xea, 0x1c, 0x00)};
constexpr std::array<lv_color_t, nLines> blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff),
LV_COLOR_MAKE(0x22, 0x32, 0xd0),
LV_COLOR_MAKE(0x18, 0x2a, 0x8b),
LV_COLOR_MAKE(0xe7, 0xf8, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x59, 0x91, 0xff),
LV_COLOR_MAKE(0x16, 0x36, 0xff)};
constexpr std::array<lv_color_t, nLines> greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b),
LV_COLOR_MAKE(0x08, 0x86, 0x08),
LV_COLOR_MAKE(0x00, 0x4a, 0x00),
LV_COLOR_MAKE(0xb8, 0xff, 0x9b),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x62, 0xd5, 0x15),
LV_COLOR_MAKE(0x00, 0x74, 0x00)};
constexpr std::array<lv_color_t, nLines> rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00),
LV_COLOR_MAKE(0xac, 0x09, 0xc4),
LV_COLOR_MAKE(0xfe, 0x03, 0x03),
LV_COLOR_MAKE(0x0d, 0x57, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xe0, 0xb9, 0x00),
LV_COLOR_MAKE(0xe8, 0x51, 0x02)};
constexpr std::array<lv_color_t, nLines> grayColors = {LV_COLOR_MAKE(0xee, 0xee, 0xee),
LV_COLOR_MAKE(0x98, 0x95, 0x9b),
LV_COLOR_MAKE(0x19, 0x19, 0x19),
LV_COLOR_MAKE(0xee, 0xee, 0xee),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x91, 0x91, 0x91),
LV_COLOR_MAKE(0x3a, 0x3a, 0x3a)};
constexpr std::array<lv_color_t, nLines> nordBlueColors = {LV_COLOR_MAKE(0xc3, 0xda, 0xf2),
LV_COLOR_MAKE(0x4d, 0x78, 0xce),
LV_COLOR_MAKE(0x15, 0x34, 0x51),
LV_COLOR_MAKE(0xc3, 0xda, 0xf2),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x5d, 0x8a, 0xd2),
LV_COLOR_MAKE(0x21, 0x51, 0x8a)};
constexpr std::array<lv_color_t, nLines> nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9),
LV_COLOR_MAKE(0x23, 0x83, 0x73),
LV_COLOR_MAKE(0x1d, 0x41, 0x3f),
LV_COLOR_MAKE(0xd5, 0xf0, 0xe9),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x2f, 0xb8, 0xa2),
LV_COLOR_MAKE(0x11, 0x70, 0x5a)};
constexpr const std::array<lv_color_t, nLines>* returnColor(colors color) {
if (color == colors::orange) {
return &orangeColors;
}
if (color == colors::blue) {
return &blueColors;
}
if (color == colors::green) {
return &greenColors;
}
if (color == colors::rainbow) {
return &rainbowColors;
}
if (color == colors::gray) {
return &grayColors;
}
if (color == colors::nordBlue) {
return &nordBlueColors;
}
return &nordGreenColors;
}
}
WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController,
Controllers::FS& filesystem)
: currentDateTime {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificationManager {notificationManager},
settingsController {settingsController},
motionController {motionController} {
lfs_file f = {};
if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_teko = lv_font_load("F:/fonts/teko.bin");
}
if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_bebas = lv_font_load("F:/fonts/bebas.bin");
}
// Side Cover
static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}},
{{26, 167}, {43, 216}},
{{27, 40}, {27, 196}},
{{12, 182}, {65, 249}},
{{17, 99}, {17, 144}},
{{14, 81}, {40, 127}},
{{14, 163}, {40, 118}},
{{-20, 124}, {25, -11}},
{{-29, 89}, {27, 254}}};
static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48};
const std::array<lv_color_t, nLines>* colors = returnColor(static_cast<enum colors>(settingsController.GetInfineatColorIndex()));
for (int i = 0; i < nLines; i++) {
lines[i] = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]);
lv_color_t color = (*colors)[i];
lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color);
lv_line_set_points(lines[i], linePoints[i], 2);
}
logoPine = lv_img_create(lv_scr_act(), nullptr);
lv_img_set_src(logoPine, "F:/images/pine_small.bin");
lv_obj_set_pos(logoPine, 15, 106);
lineBattery = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24);
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]);
lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190);
lineBatteryPoints[0] = {27, 105};
lineBatteryPoints[1] = {27, 106};
lv_line_set_points(lineBattery, lineBatteryPoints, 2);
lv_obj_move_foreground(lineBattery);
notificationIcon = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]);
lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
lv_obj_set_size(notificationIcon, 13, 13);
lv_obj_set_hidden(notificationIcon, true);
if (!settingsController.GetInfineatShowSideCover()) {
ToggleBatteryIndicatorColor(false);
for (auto& line : lines) {
lv_obj_set_hidden(line, true);
}
}
timeContainer = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_size(timeContainer, 185, 185);
lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10);
labelHour = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(labelHour, "01");
lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas);
lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0);
labelMinutes = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas);
lv_label_set_text_static(labelMinutes, "00");
lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
lv_label_set_text_static(labelTimeAmPm, "");
lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15);
dateContainer = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_size(dateContainer, 60, 30);
lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5);
static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99);
labelDate = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0);
lv_label_set_text_static(labelDate, "Mon 01");
bleIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_label_set_text_static(bleIcon, Symbols::bluetooth);
lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0);
lv_label_set_text_static(stepValue, "0");
stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_label_set_text_static(stepIcon, Symbols::shoe);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
// Setting buttons
btnClose = lv_btn_create(lv_scr_act(), nullptr);
btnClose->user_data = this;
lv_obj_set_size(btnClose, 60, 60);
lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80);
lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_t* lblClose = lv_label_create(btnClose, nullptr);
lv_label_set_text_static(lblClose, "X");
lv_obj_set_event_cb(btnClose, event_handler);
lv_obj_set_hidden(btnClose, true);
btnNextColor = lv_btn_create(lv_scr_act(), nullptr);
btnNextColor->user_data = this;
lv_obj_set_size(btnNextColor, 60, 60);
lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0);
lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr);
lv_label_set_text_static(lblNextColor, ">");
lv_obj_set_event_cb(btnNextColor, event_handler);
lv_obj_set_hidden(btnNextColor, true);
btnPrevColor = lv_btn_create(lv_scr_act(), nullptr);
btnPrevColor->user_data = this;
lv_obj_set_size(btnPrevColor, 60, 60);
lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0);
lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr);
lv_label_set_text_static(lblPrevColor, "<");
lv_obj_set_event_cb(btnPrevColor, event_handler);
lv_obj_set_hidden(btnPrevColor, true);
btnToggleCover = lv_btn_create(lv_scr_act(), nullptr);
btnToggleCover->user_data = this;
lv_obj_set_size(btnToggleCover, 60, 60);
lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80);
lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF";
lblToggle = lv_label_create(btnToggleCover, nullptr);
lv_label_set_text_static(lblToggle, labelToggle);
lv_obj_set_event_cb(btnToggleCover, event_handler);
lv_obj_set_hidden(btnToggleCover, true);
// Button to access the settings
btnSettings = lv_btn_create(lv_scr_act(), nullptr);
btnSettings->user_data = this;
lv_obj_set_size(btnSettings, 150, 150);
lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30);
lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_set_event_cb(btnSettings, event_handler);
labelBtnSettings = lv_label_create(btnSettings, nullptr);
lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
lv_label_set_text_static(labelBtnSettings, Symbols::settings);
lv_obj_set_hidden(btnSettings, true);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceInfineat::~WatchFaceInfineat() {
lv_task_del(taskRefresh);
if (font_bebas != nullptr) {
lv_font_free(font_bebas);
}
if (font_teko != nullptr) {
lv_font_free(font_teko);
}
lv_obj_clean(lv_scr_act());
}
bool WatchFaceInfineat::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) {
lv_obj_set_hidden(btnSettings, false);
savedTick = lv_tick_get();
return true;
}
// Prevent screen from sleeping when double tapping with settings on
if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) {
return true;
}
return false;
}
void WatchFaceInfineat::CloseMenu() {
settingsController.SaveSettings();
lv_obj_set_hidden(btnClose, true);
lv_obj_set_hidden(btnNextColor, true);
lv_obj_set_hidden(btnPrevColor, true);
lv_obj_set_hidden(btnToggleCover, true);
}
bool WatchFaceInfineat::OnButtonPushed() {
if (!lv_obj_get_hidden(btnClose)) {
CloseMenu();
return true;
}
return false;
}
void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
bool showSideCover = settingsController.GetInfineatShowSideCover();
int colorIndex = settingsController.GetInfineatColorIndex();
if (object == btnSettings) {
lv_obj_set_hidden(btnSettings, true);
lv_obj_set_hidden(btnClose, false);
lv_obj_set_hidden(btnNextColor, !showSideCover);
lv_obj_set_hidden(btnPrevColor, !showSideCover);
lv_obj_set_hidden(btnToggleCover, false);
}
if (object == btnClose) {
CloseMenu();
}
if (object == btnToggleCover) {
settingsController.SetInfineatShowSideCover(!showSideCover);
ToggleBatteryIndicatorColor(!showSideCover);
for (auto& line : lines) {
lv_obj_set_hidden(line, showSideCover);
}
lv_obj_set_hidden(btnNextColor, showSideCover);
lv_obj_set_hidden(btnPrevColor, showSideCover);
const char* labelToggle = showSideCover ? "OFF" : "ON";
lv_label_set_text_static(lblToggle, labelToggle);
}
if (object == btnNextColor) {
colorIndex = (colorIndex + 1) % nColors;
settingsController.SetInfineatColorIndex(colorIndex);
}
if (object == btnPrevColor) {
colorIndex -= 1;
if (colorIndex < 0)
colorIndex = nColors - 1;
settingsController.SetInfineatColorIndex(colorIndex);
}
if (object == btnNextColor || object == btnPrevColor) {
const std::array<lv_color_t, nLines>* colors = returnColor(static_cast<enum colors>(settingsController.GetInfineatColorIndex()));
for (int i = 0; i < nLines; i++) {
lv_color_t color = (*colors)[i];
lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color);
}
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]);
}
}
}
void WatchFaceInfineat::Refresh() {
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
lv_obj_set_hidden(notificationIcon, !notificationState.Get());
lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
}
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
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);
}
lv_label_set_text_fmt(labelHour, "%02d", hour);
lv_label_set_text_fmt(labelMinutes, "%02d", minute);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10);
lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5);
lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
}
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
uint8_t day = dateTimeController.Day();
Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek();
lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day);
lv_obj_realign(labelDate);
}
}
batteryPercentRemaining = batteryController.PercentRemaining();
isCharging = batteryController.IsCharging();
if (batteryController.IsCharging()) { // Charging battery animation
chargingBatteryPercent += 1;
if (chargingBatteryPercent > 100) {
chargingBatteryPercent = batteryPercentRemaining.Get();
}
SetBatteryLevel(chargingBatteryPercent);
} else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) {
chargingBatteryPercent = batteryPercentRemaining.Get();
SetBatteryLevel(chargingBatteryPercent);
}
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated()) {
lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get()));
lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3);
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
}
if (!lv_obj_get_hidden(btnSettings)) {
if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) {
lv_obj_set_hidden(btnSettings, true);
savedTick = 0;
}
}
}
void WatchFaceInfineat::SetBatteryLevel(uint8_t batteryPercent) {
// starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100
lineBatteryPoints[1] = {27, static_cast<lv_coord_t>(105 + 32 * (100 - batteryPercent) / 100)};
lv_line_set_points(lineBattery, lineBatteryPoints, 2);
}
void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) {
if (!showSideCover) { // make indicator and notification icon color white
lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100);
lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
} else {
lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0);
const std::array<lv_color_t, nLines>* colors = returnColor(static_cast<enum colors>(settingsController.GetInfineatColorIndex()));
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]);
}
}
bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) {
lfs_file file = {};
if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
return true;
}
+123
View File
@@ -0,0 +1,123 @@
#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 WatchFaceInfineat : public Screen {
public:
static constexpr int nLines = 9;
WatchFaceInfineat(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController,
Controllers::FS& fs);
~WatchFaceInfineat() override;
bool OnTouchEvent(TouchEvents event) override;
bool OnButtonPushed() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
void CloseMenu();
void Refresh() override;
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
private:
uint32_t savedTick = 0;
uint8_t chargingBatteryPercent = 101; // not a mistake ;)
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<uint32_t> stepCount {};
Utility::DirtyValue<bool> notificationState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
// Lines making up the side cover
lv_obj_t* lineBattery;
lv_point_t lineBatteryPoints[2];
lv_obj_t* logoPine;
lv_obj_t* timeContainer;
lv_obj_t* labelHour;
lv_obj_t* labelMinutes;
lv_obj_t* labelTimeAmPm;
lv_obj_t* dateContainer;
lv_obj_t* labelDate;
lv_obj_t* bleIcon;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* btnClose;
lv_obj_t* btnNextColor;
lv_obj_t* btnToggleCover;
lv_obj_t* btnPrevColor;
lv_obj_t* btnSettings;
lv_obj_t* labelBtnSettings;
lv_obj_t* lblToggle;
lv_obj_t* lines[nLines];
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::MotionController& motionController;
void SetBatteryLevel(uint8_t batteryPercent);
void ToggleBatteryIndicatorColor(bool showSideCover);
lv_task_t* taskRefresh;
lv_font_t* font_teko = nullptr;
lv_font_t* font_bebas = nullptr;
};
}
template <>
struct WatchFaceTraits<WatchFace::Infineat> {
static constexpr WatchFace watchFace = WatchFace::Infineat;
static constexpr const char* name = "Infineat face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceInfineat(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.motionController,
controllers.filesystem);
};
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
return Screens::WatchFaceInfineat::IsAvailable(filesystem);
}
};
}
}
@@ -540,7 +540,6 @@ void WatchFacePineTimeStyle::Refresh() {
}
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
@@ -551,12 +550,10 @@ void WatchFacePineTimeStyle::Refresh() {
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°", temp);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
} else {
lv_label_set_text(temperature, "--");
lv_label_set_text(weatherIcon, Symbols::ban);
}
} else {
lv_label_set_text(temperature, "--");
lv_label_set_text(weatherIcon, Symbols::ban);
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}
+259
View File
@@ -0,0 +1,259 @@
#include "displayapp/screens/WatchFaceSundial.h"
#include <lvgl/lvgl.h>
#include <cstdio>
// #include <cmath>
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "displayapp/InfiniTimeTheme.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
#include "sunset/src/sunset.h"
using namespace Pinetime::Applications::Screens;
namespace {
int16_t HourLength = 70;
constexpr int16_t MinuteLength = 90;
constexpr int16_t SecondLength = 110;
constexpr int16_t SunDialVerticalOffset = 40;
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
const lv_color_t DARK_GRAY = lv_color_make(48, 48, 48);
const lv_color_t DARK_ORANGE = lv_color_make(48, 26, 0);
int16_t Cosine(int16_t angle) {
return _lv_trigo_sin(angle + 90);
}
int16_t Sine(int16_t angle) {
return _lv_trigo_sin(angle);
}
int16_t CoordinateXRelocate(int16_t x) {
return (x + LV_HOR_RES / 2);
}
int16_t CoordinateYRelocateSundial(int16_t y) {
return std::abs(y - SunDialVerticalOffset);
}
lv_point_t CoordinateRelocateSundial(int16_t radius, int16_t angle) {
return lv_point_t {.x = CoordinateXRelocate(radius * static_cast<int32_t>(Sine(angle)) / LV_TRIG_SCALE),
.y = CoordinateYRelocateSundial(radius * static_cast<int32_t>(Cosine(angle)) / LV_TRIG_SCALE)};
}
}
WatchFaceSundial::WatchFaceSundial(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController)
: currentDateTime {{}},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
settingsController {settingsController}
{
// minor_scales = lv_linemeter_create(lv_scr_act(), nullptr);
// lv_linemeter_set_scale(minor_scales, 300, 51);
// lv_linemeter_set_angle_offset(minor_scales, 180);
// lv_obj_set_size(minor_scales, 240, 240);
// lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
// lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
// lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
// lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
// major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
// lv_linemeter_set_scale(major_scales, 300, 11);
// lv_linemeter_set_angle_offset(major_scales, 180);
// lv_obj_set_size(major_scales, 240, 240);
// lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
// lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6);
// lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
// lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
// large_scales = lv_linemeter_create(lv_scr_act(), nullptr);
// lv_linemeter_set_scale(large_scales, 180, 3);
// lv_linemeter_set_angle_offset(large_scales, 180);
// lv_obj_set_size(large_scales, 240, 240);
// lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
// lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20);
// lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
// lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
twelve = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_align(twelve, LV_LABEL_ALIGN_RIGHT);
lv_label_set_text_static(twelve, "XII");
lv_obj_align(twelve, NULL, LV_ALIGN_IN_TOP_RIGHT, -20, SunDialVerticalOffset-20);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
one = lv_label_create(lv_scr_act(), NULL);
lv_label_set_align(one, LV_LABEL_ALIGN_LEFT);
lv_label_set_text(one, "I");
lv_obj_align(one, NULL, LV_ALIGN_IN_TOP_LEFT, 20, SunDialVerticalOffset-20);
lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
// Date - Day / Week day
label_date_day = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_ORANGE);
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
lv_label_set_align(label_date_day, LV_LABEL_ALIGN_CENTER);
lv_obj_align(label_date_day, nullptr, LV_ALIGN_CENTER, 50, 0);
minute_body = lv_line_create(lv_scr_act(), nullptr);
minute_body_trace = lv_line_create(lv_scr_act(), nullptr);
hour_body = lv_line_create(lv_scr_act(), nullptr);
hour_body_trace = lv_line_create(lv_scr_act(), nullptr);
second_body = lv_line_create(lv_scr_act(), nullptr);
lv_style_init(&second_line_style);
lv_style_set_line_width(&second_line_style, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&second_line_style, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_style_set_line_rounded(&second_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(second_body, LV_LINE_PART_MAIN, &second_line_style);
lv_style_init(&minute_line_style);
lv_style_set_line_width(&minute_line_style, LV_STATE_DEFAULT, 7);
lv_style_set_line_color(&minute_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&minute_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(minute_body, LV_LINE_PART_MAIN, &minute_line_style);
lv_style_init(&minute_line_style_trace);
lv_style_set_line_width(&minute_line_style_trace, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&minute_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&minute_line_style_trace, LV_STATE_DEFAULT, false);
lv_obj_add_style(minute_body_trace, LV_LINE_PART_MAIN, &minute_line_style_trace);
lv_style_init(&hour_line_style);
lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(hour_body, LV_LINE_PART_MAIN, &hour_line_style);
lv_style_init(&hour_line_style_trace);
lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, false);
lv_obj_add_style(hour_body_trace, LV_LINE_PART_MAIN, &hour_line_style_trace);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
UpdateClock();
Refresh();
}
WatchFaceSundial::~WatchFaceSundial() {
lv_task_del(taskRefresh);
lv_style_reset(&hour_line_style);
lv_style_reset(&hour_line_style_trace);
lv_style_reset(&minute_line_style);
lv_style_reset(&minute_line_style_trace);
lv_style_reset(&second_line_style);
lv_obj_clean(lv_scr_act());
}
void WatchFaceSundial::Refresh() {
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
UpdateClock();
if (currentDate.IsUpdated()) {
char const* MonthsString[] = {"--", "IANUARIUS","FEBRUARIUS","MARTIUS","APRILIS","MARTIUSIUNIUS","QUINTILIS","SEXTILIS","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"};
char const* DaysString[] = {"--", "LUNAE", "MARTIS", "MERCURII", "IOVIS", "VENERIS", "SATURNI", "SOLIS"};
char const* RomanNumeralsString[] = {"--", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"};
lv_label_set_text_fmt(label_date_day, "%s\n%s %s",
DaysString[static_cast<uint8_t>(dateTimeController.DayOfWeek())],
RomanNumeralsString[static_cast<uint8_t>(dateTimeController.Day())],
MonthsString[static_cast<uint8_t>(dateTimeController.Month())]);
lv_obj_align(label_date_day, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -20);
}
}
}
void WatchFaceSundial::UpdateClock() {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
location = settingsController.GetLocation();
if (sHour != hour || sMinute != minute) {
// sun.setPosition(settings.lat.toFloat(), settings.lon.toFloat(), settings.gmtOffset / 3600);
sun.setPosition((float)location.latitude, (float)location.longitude, location.tzOffset);
//from minutes past midnight
sun.setCurrentDate(dateTimeController.Year(), static_cast<uint8_t>(dateTimeController.Month())+1, dateTimeController.Day());
sun.setTZOffset(location.tzOffset);
minutesSunrise = sun.calcSunrise(); //360;
minutesSunset = sun.calcSunset(); //1080;
minutesDaytime = (minutesSunset - minutesSunrise);
minutesNighttime = (1440 - minutesDaytime);
minutesBeforeSunset = minutesSunset - (hour * 60 + minute); // i.e.zero degrees
HourLength = 90; // sundial hand length
int16_t hourAngle;
if(minutesBeforeSunset > 0 && minutesBeforeSunset < minutesDaytime) { // day (after sunrise)
hourAngle = 180.0 * minutesBeforeSunset / minutesDaytime + 90;
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
} else { // night (before sunrise or after sunset)
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, DARK_GRAY);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, DARK_GRAY);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_ORANGE);
lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
if(minutesBeforeSunset > minutesDaytime) { // before sunrise
hourAngle = 180.0 * (minutesBeforeSunset - minutesDaytime) / minutesNighttime + 90;
} else { // after sunset
hourAngle = 180 + 180.0 * minutesBeforeSunset / minutesNighttime + 90;
}
}
/*NRF_LOG_INFO("a: %d, la: %f, lo: %f, ri: %d, se: %d, be: %d",
hourAngle,
(float)location.latitude,
(float)location.longitude,
minutesSunrise,
minutesSunset,
minutesBeforeSunset);*/
sHour = hour;
sMinute = minute;
printf("H%d:%d lat%f lng%f z%d\n", hour, minute, (float)location.latitude, (float)location.longitude, location.tzOffset);
printf("%d before sunset, sunrise at %d:%d sunset at %d:%d angle %d\n",
minutesBeforeSunset,
minutesSunrise/60, minutesSunrise % 60,
minutesSunset/60, minutesSunset % 60, hourAngle);
hour_point_trace[0] = CoordinateRelocateSundial(HourLength*.75, hourAngle);
hour_point_trace[1] = CoordinateRelocateSundial(HourLength, hourAngle);
hour_point[0] = CoordinateRelocateSundial(0, hourAngle);
hour_point[1] = CoordinateRelocateSundial(HourLength*.75, hourAngle);
lv_line_set_points(hour_body, hour_point, 2);
lv_line_set_points(hour_body_trace, hour_point_trace, 2);
}
}
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "displayapp/screens/BatteryIcon.h"
#include "utility/DirtyValue.h"
#include "sunset/src/sunset.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
}
namespace Applications {
namespace Screens {
class WatchFaceSundial : public Screen {
public:
WatchFaceSundial(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController);
~WatchFaceSundial() override;
void Refresh() override;
private:
uint8_t sHour, sMinute, sSecond;
Utility::DirtyValue<uint8_t> batteryPercentRemaining {0};
Utility::DirtyValue<bool> isCharging {};
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
Utility::DirtyValue<bool> notificationState {false};
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* major_scales;
lv_obj_t* one;
lv_obj_t* twelve;
lv_obj_t* hour_body;
lv_obj_t* hour_body_trace;
lv_obj_t* minute_body;
lv_obj_t* minute_body_trace;
lv_obj_t* second_body;
lv_point_t hour_point[2];
lv_point_t hour_point_trace[2];
lv_point_t minute_point[2];
lv_point_t minute_point_trace[2];
lv_point_t second_point[2];
lv_style_t hour_line_style;
lv_style_t hour_line_style_trace;
lv_style_t minute_line_style;
lv_style_t minute_line_style_trace;
lv_style_t second_line_style;
lv_obj_t* label_date_day;
lv_obj_t* plugIcon;
lv_obj_t* notificationIcon;
lv_obj_t* bleIcon;
Controllers::Settings::Location location;
SunSet sun;
int16_t minutesSunrise;
int16_t minutesSunset;
int16_t minutesDaytime;
int16_t minutesNighttime;
int16_t minutesBeforeSunset;
const Controllers::DateTime& dateTimeController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
void drawWatchFaceModeNight();
void UpdateClock();
void SetBatteryIcon();
lv_task_t* taskRefresh;
};
}
template <>
struct WatchFaceTraits<WatchFace::Sundial> {
static constexpr WatchFace watchFace = WatchFace::Sundial;
static constexpr const char* name = "Sundial face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceSundial(controllers.dateTimeController,
controllers.notificationManager,
controllers.settingsController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}
@@ -0,0 +1,151 @@
#include <lvgl/lvgl.h>
#include "displayapp/screens/WatchFaceTerminal.h"
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController)
: currentDateTime {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController} {
batteryValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(batteryValue, true);
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);
connectState = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(connectState, true);
lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -100);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label_date, true);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);
label_prompt_1 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_prompt_1, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -80);
lv_label_set_text_static(label_prompt_1, "user@watch:~ $ now");
label_prompt_2 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
label_time = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label_time, true);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -60);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(heartbeatValue, true);
lv_obj_align(heartbeatValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 20);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(stepValue, true);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceTerminal::~WatchFaceTerminal() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void WatchFaceTerminal::Refresh() {
powerPresent = batteryController.IsPowerPresent();
batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
lv_label_set_text_fmt(batteryValue, "[BATT]#387b54 %d%%", batteryPercentRemaining.Get());
if (batteryController.IsPowerPresent()) {
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
}
}
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
if (!bleRadioEnabled.Get()) {
lv_label_set_text_static(connectState, "[STAT]#0082fc Disabled#");
} else {
if (bleState.Get()) {
lv_label_set_text_static(connectState, "[STAT]#0082fc Connected#");
} else {
lv_label_set_text_static(connectState, "[STAT]#0082fc Disconnected#");
}
}
}
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
if (notificationState.Get()) {
lv_label_set_text_static(notificationIcon, "You have mail.");
} else {
lv_label_set_text_static(notificationIcon, "");
}
}
currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
uint8_t second = dateTimeController.Seconds();
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_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
} else {
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
}
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
uint16_t year = dateTimeController.Year();
Controllers::DateTime::Months month = dateTimeController.Month();
uint8_t day = dateTimeController.Day();
lv_label_set_text_fmt(label_date, "[DATE]#007fff %04d-%02d-%02d#", short(year), char(month), char(day));
}
}
heartbeat = heartRateController.HeartRate();
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
if (heartbeatRunning.Get()) {
lv_label_set_text_fmt(heartbeatValue, "[L_HR]#ee3311 %d bpm#", heartbeat.Get());
} else {
lv_label_set_text_static(heartbeatValue, "[L_HR]#ee3311 ---#");
}
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "[STEP]#ee3377 %lu steps#", stepCount.Get());
}
}
@@ -0,0 +1,92 @@
#pragma once
#include <lvgl/src/lv_core/lv_obj.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"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceTerminal : public Screen {
public:
WatchFaceTerminal(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController);
~WatchFaceTerminal() override;
void Refresh() override;
private:
Utility::DirtyValue<int> batteryPercentRemaining {};
Utility::DirtyValue<bool> powerPresent {};
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<bool> bleRadioEnabled {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> currentDateTime {};
Utility::DirtyValue<uint32_t> stepCount {};
Utility::DirtyValue<uint8_t> heartbeat {};
Utility::DirtyValue<bool> heartbeatRunning {};
Utility::DirtyValue<bool> notificationState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_obj_t* label_time;
lv_obj_t* label_date;
lv_obj_t* label_prompt_1;
lv_obj_t* label_prompt_2;
lv_obj_t* batteryValue;
lv_obj_t* heartbeatValue;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* connectState;
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
lv_task_t* taskRefresh;
};
}
template <>
struct WatchFaceTraits<WatchFace::Terminal> {
static constexpr WatchFace watchFace = WatchFace::Terminal;
static constexpr const char* name = "Terminal";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceTerminal(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}
+198
View File
@@ -0,0 +1,198 @@
#include "displayapp/screens/Weather.h"
#include <lvgl/lvgl.h>
#include "components/ble/SimpleWeatherService.h"
#include "components/datetime/DateTimeController.h"
#include "components/settings/Settings.h"
#include "displayapp/DisplayApp.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "displayapp/InfiniTimeTheme.h"
using namespace Pinetime::Applications::Screens;
namespace {
lv_color_t TemperatureColor(int16_t temperature) {
if (temperature <= 0) { // freezing
return Colors::blue;
} else if (temperature <= 400) { // ice
return LV_COLOR_CYAN;
} else if (temperature >= 2700) { // hot
return Colors::deepOrange;
}
return Colors::orange; // normal
}
uint8_t TemperatureStyle(int16_t temperature) {
if (temperature <= 0) { // freezing
return LV_TABLE_PART_CELL3;
} else if (temperature <= 400) { // ice
return LV_TABLE_PART_CELL4;
} else if (temperature >= 2700) { // hot
return LV_TABLE_PART_CELL6;
}
return LV_TABLE_PART_CELL5; // normal
}
int16_t RoundTemperature(int16_t temp) {
return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
}
}
Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
: settingsController {settingsController}, weatherService {weatherService} {
temperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_label_set_text(temperature, "---");
lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -30);
lv_obj_set_auto_realign(temperature, true);
minTemperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(minTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg);
lv_label_set_text(minTemperature, "");
lv_obj_align(minTemperature, temperature, LV_ALIGN_OUT_LEFT_MID, -10, 0);
lv_obj_set_auto_realign(minTemperature, true);
maxTemperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(maxTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg);
lv_label_set_text(maxTemperature, "");
lv_obj_align(maxTemperature, temperature, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
lv_obj_set_auto_realign(maxTemperature, true);
condition = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
lv_label_set_text(condition, "");
lv_obj_align(condition, temperature, LV_ALIGN_OUT_TOP_MID, 0, -10);
lv_obj_set_auto_realign(condition, true);
icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
lv_label_set_text(icon, "");
lv_obj_align(icon, condition, LV_ALIGN_OUT_TOP_MID, 0, 0);
lv_obj_set_auto_realign(icon, true);
forecast = lv_table_create(lv_scr_act(), nullptr);
lv_table_set_col_cnt(forecast, Controllers::SimpleWeatherService::MaxNbForecastDays);
lv_table_set_row_cnt(forecast, 4);
// LV_TABLE_PART_CELL1: Default table style
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray);
// LV_TABLE_PART_CELL2: Condition icon
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons);
// LV_TABLE_PART_CELL3: Freezing
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue);
// LV_TABLE_PART_CELL4: Ice
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN);
// LV_TABLE_PART_CELL5: Normal
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange);
// LV_TABLE_PART_CELL6: Hot
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange);
lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
lv_table_set_col_width(forecast, i, 48);
lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2);
lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER);
lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER);
lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER);
lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER);
}
taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this);
Refresh();
}
Weather::~Weather() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void Weather::Refresh() {
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
int16_t minTemp = optCurrentWeather->minTemperature;
int16_t maxTemp = optCurrentWeather->maxTemperature;
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp));
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
tempUnit = 'F';
}
lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp));
} else {
lv_label_set_text(icon, "");
lv_label_set_text(condition, "");
lv_label_set_text(temperature, "---");
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_label_set_text(minTemperature, "");
lv_label_set_text(maxTemperature, "");
}
}
currentForecast = weatherService.GetForecast();
if (currentForecast.IsUpdated()) {
auto optCurrentForecast = currentForecast.Get();
if (optCurrentForecast) {
std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
int16_t minTemp = optCurrentForecast->days[i].minTemperature;
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp));
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
}
uint8_t wday = localTime.tm_wday + i + 1;
if (wday > 7) {
wday -= 7;
}
maxTemp = RoundTemperature(maxTemp);
minTemp = RoundTemperature(minTemp);
const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
lv_table_set_cell_value(forecast, 0, i, dayOfWeek);
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId));
// Pad cells based on the largest number of digits on each column
char maxPadding[3] = " ";
char minPadding[3] = " ";
int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp);
if (diff <= 0) {
maxPadding[-diff] = '\0';
minPadding[0] = '\0';
} else {
maxPadding[0] = '\0';
minPadding[diff] = '\0';
}
lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp);
lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp);
}
} else {
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
lv_table_set_cell_value(forecast, 0, i, "");
lv_table_set_cell_value(forecast, 1, i, "");
lv_table_set_cell_value(forecast, 2, i, "");
lv_table_set_cell_value(forecast, 3, i, "");
lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1);
lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1);
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <cstdint>
#include <lvgl/lvgl.h>
#include "displayapp/screens/Screen.h"
#include "components/ble/SimpleWeatherService.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
#include "utility/DirtyValue.h"
namespace Pinetime {
namespace Controllers {
class Settings;
}
namespace Applications {
namespace Screens {
class Weather : public Screen {
public:
Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService);
~Weather() override;
void Refresh() override;
private:
Controllers::Settings& settingsController;
Controllers::SimpleWeatherService& weatherService;
Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::Forecast>> currentForecast {};
lv_obj_t* icon;
lv_obj_t* condition;
lv_obj_t* temperature;
lv_obj_t* minTemperature;
lv_obj_t* maxTemperature;
lv_obj_t* forecast;
lv_task_t* taskRefresh;
};
}
template <>
struct AppTraits<Apps::Weather> {
static constexpr Apps app = Apps::Weather;
static constexpr const char* icon = Screens::Symbols::cloudSunRain;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Weather(controllers.settingsController, *controllers.weatherController);
};
};
}
}
+61
View File
@@ -0,0 +1,61 @@
#include "displayapp/screens/WeatherSymbols.h"
const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
switch (icon) {
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
return Symbols::sun;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
return Symbols::cloudSun;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
return Symbols::cloud;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
return Symbols::cloudMeatball;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
return Symbols::bolt;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
return Symbols::snowflake;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
return Symbols::cloudShowersHeavy;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
return Symbols::cloudSunRain;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
return Symbols::smog;
break;
default:
return Symbols::ban;
break;
}
}
const char* Pinetime::Applications::Screens::Symbols::GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
switch (icon) {
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
return "Clear sky";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
return "Few clouds";
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
return "Scattered clouds";
case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
return "Broken clouds";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
return "Shower rain";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
return "Rain";
case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
return "Thunderstorm";
case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
return "Snow";
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
return "Mist";
default:
return "";
}
}
+2 -34
View File
@@ -6,40 +6,8 @@ namespace Pinetime {
namespace Applications {
namespace Screens {
namespace Symbols {
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
switch (icon) {
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
return Symbols::sun;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
return Symbols::cloudSun;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
return Symbols::cloud;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
return Symbols::cloudMeatball;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
return Symbols::bolt;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
return Symbols::snowflake;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
return Symbols::cloudShowersHeavy;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
return Symbols::cloudSunRain;
break;
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
return Symbols::smog;
break;
default:
return Symbols::ban;
break;
}
}
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
}
}
}
@@ -36,17 +36,19 @@ namespace {
SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: app {app},
settings {settingsController},
checkboxList(
0,
1,
"Bluetooth",
Symbols::bluetooth,
settingsController.GetBleRadioEnabled() ? 0 : 1,
[&settings = settingsController](uint32_t index) {
[this](uint32_t index) {
const bool priorMode = settings.GetBleRadioEnabled();
const bool newMode = options[index].radioEnabled;
if (newMode != priorMode) {
settings.SetBleRadioEnabled(newMode);
this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
}
},
CreateOptionArray()) {
@@ -54,6 +56,4 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine
SettingBluetooth::~SettingBluetooth() {
lv_obj_clean(lv_scr_act());
// Pushing the message in the OnValueChanged function causes a freeze?
app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
}
@@ -20,6 +20,7 @@ namespace Pinetime {
private:
DisplayApp* app;
Pinetime::Controllers::Settings& settings;
CheckboxList checkboxList;
};
}
@@ -9,6 +9,9 @@
using namespace Pinetime::Applications::Screens;
namespace {
constexpr int16_t POS_X_LAT = -80;
constexpr int16_t POS_X_LONG = 0;
constexpr int16_t POS_X_TZ = 80;
constexpr int16_t POS_Y_TEXT = 25;
void ValueChangedHandler(void* userData) {
@@ -27,6 +30,7 @@ SettingLocation::SettingLocation(Pinetime::Controllers::Settings& settingsContro
lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_label_set_text_static(icon, Symbols::map);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
@@ -34,21 +38,18 @@ SettingLocation::SettingLocation(Pinetime::Controllers::Settings& settingsContro
Controllers::Settings::Location loc = settingsController.GetLocation();
latCounter.Create();
latCounter.SetWidth(80);
latCounter.SetValue(loc.latitude);
lv_obj_align(latCounter.GetObject(), nullptr, LV_ALIGN_CENTER, -90, POS_Y_TEXT);
lv_obj_align(latCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LAT, POS_Y_TEXT);
latCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
longCounter.Create();
longCounter.SetWidth(110);
longCounter.SetValue(loc.longitude);
lv_obj_align(longCounter.GetObject(), nullptr, LV_ALIGN_CENTER, -5, POS_Y_TEXT);
lv_obj_align(longCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LONG, POS_Y_TEXT);
longCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
tzCounter.Create();
tzCounter.SetWidth(60);
tzCounter.SetValue(loc.tzOffset);
lv_obj_align(tzCounter.GetObject(), nullptr, LV_ALIGN_CENTER, 75, POS_Y_TEXT);
lv_obj_align(tzCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_TZ, POS_Y_TEXT);
tzCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
UpdateScreen();
@@ -66,4 +67,4 @@ void SettingLocation::UpdateScreen() {
tzOffset: (int8_t)tzCounter.GetValue(),
};
settingsController.SetLocation(loc);
}
}
@@ -22,9 +22,9 @@ namespace Pinetime {
private:
Controllers::Settings& settingsController;
Widgets::Counter latCounter = Widgets::Counter(-90, 90, jetbrains_mono_42);
Widgets::Counter longCounter = Widgets::Counter(-180, 180, jetbrains_mono_42);
Widgets::Counter tzCounter = Widgets::Counter(-12, 12, jetbrains_mono_42);
Widgets::Counter latCounter = Widgets::Counter(-90, 90, jetbrains_mono_bold_20);
Widgets::Counter longCounter = Widgets::Counter(-180, 180, jetbrains_mono_bold_20);
Widgets::Counter tzCounter = Widgets::Counter(-12, 12, jetbrains_mono_bold_20);
};
}
}
@@ -9,6 +9,37 @@ using namespace Pinetime::Applications::Screens;
constexpr const char* SettingWatchFace::title;
constexpr const char* SettingWatchFace::symbol;
namespace {
uint32_t IndexOf(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
Pinetime::Applications::WatchFace watchface) {
size_t index = 0;
auto found = std::find_if(watchfaces.begin(),
watchfaces.end(),
[&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) {
const bool result = item.watchface == watchface;
if (!result) {
index++;
}
return result;
});
if (found == watchfaces.end()) {
index = 0;
}
return index;
}
Pinetime::Applications::WatchFace IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
size_t index) {
if (index >= watchfaces.size()) {
return watchfaces[0].watchface;
}
return watchfaces[index].watchface;
}
}
auto SettingWatchFace::CreateScreenList() const {
std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
for (size_t i = 0; i < screens.size(); i++) {
@@ -20,7 +51,7 @@ auto SettingWatchFace::CreateScreenList() const {
}
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem)
: app {app},
@@ -44,7 +75,8 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
watchfacesOnThisScreen[i] = {"", false};
} else {
watchfacesOnThisScreen[i] = watchfaceItems[i + (screenNum * settingsPerScreen)];
auto& item = watchfaceItems[i + (screenNum * settingsPerScreen)];
watchfacesOnThisScreen[i] = Screens::CheckboxList::Item {item.name, item.enabled};
}
}
@@ -53,9 +85,9 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
nScreens,
title,
symbol,
static_cast<uint32_t>(settingsController.GetWatchFace()),
[&settings = settingsController](uint32_t index) {
settings.SetWatchFace(static_cast<WatchFace>(index));
static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())),
[this, &settings = settingsController](uint32_t index) {
settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index));
settings.SaveSettings();
},
watchfacesOnThisScreen);
@@ -19,8 +19,14 @@ namespace Pinetime {
class SettingWatchFace : public Screen {
public:
struct Item {
const char* name;
WatchFace watchface;
bool enabled;
};
SettingWatchFace(DisplayApp* app,
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem);
~SettingWatchFace() override;
@@ -33,7 +39,7 @@ namespace Pinetime {
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
static constexpr int settingsPerScreen = 4;
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> watchfaceItems;
std::array<Item, UserWatchFaceTypes::Count> watchfaceItems;
static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
Controllers::Settings& settingsController;
+20 -9
View File
@@ -22,6 +22,16 @@ namespace {
void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
nrf_delay_us(period_us);
}
// Scale factors to convert accelerometer counts to milli-g
// from datasheet: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf
// The array index to use is stored in accel_conf.range
constexpr int16_t accelScaleFactors[] = {
[BMA4_ACCEL_RANGE_2G] = 1024, // LSB/g +/- 2g range
[BMA4_ACCEL_RANGE_4G] = 512, // LSB/g +/- 4g range
[BMA4_ACCEL_RANGE_8G] = 256, // LSB/g +/- 8g range
[BMA4_ACCEL_RANGE_16G] = 128 // LSB/g +/- 16g range
};
}
Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
@@ -74,7 +84,6 @@ void Bma421::Init() {
if (ret != BMA4_OK)
return;
struct bma4_accel_config accel_conf;
accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
accel_conf.range = BMA4_ACCEL_RANGE_2G;
accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
@@ -102,19 +111,21 @@ void Bma421::Write(uint8_t registerAddress, const uint8_t* data, size_t size) {
Bma421::Values Bma421::Process() {
if (not isOk)
return {};
struct bma4_accel rawData;
struct bma4_accel data;
bma4_read_accel_xyz(&data, &bma);
bma4_read_accel_xyz(&rawData, &bma);
// Scale the measured ADC counts to units of 'binary milli-g'
// where 1g = 1024 'binary milli-g' units.
// See https://github.com/InfiniTimeOrg/InfiniTime/pull/1950 for
// discussion of why we opted for scaling to 1024 rather than 1000.
data.x = 1024 * rawData.x / accelScaleFactors[accel_conf.range];
data.y = 1024 * rawData.y / accelScaleFactors[accel_conf.range];
data.z = 1024 * rawData.z / accelScaleFactors[accel_conf.range];
uint32_t steps = 0;
bma423_step_counter_output(&steps, &bma);
int32_t temperature;
bma4_get_temperature(&temperature, BMA4_DEG, &bma);
temperature = temperature / 1000;
uint8_t activity = 0;
bma423_activity_output(&activity, &bma);
// X and Y axis are swapped because of the way the sensor is mounted in the PineTime
return {steps, data.y, data.x, data.z};
}
+1
View File
@@ -41,6 +41,7 @@ namespace Pinetime {
TwiMaster& twiMaster;
uint8_t deviceAddress = 0x18;
struct bma4_dev bma;
struct bma4_accel_config accel_conf; // Store the device configuration for later reference.
bool isOk = false;
bool isResetOk = false;
DeviceTypes deviceType = DeviceTypes::Unknown;
+1 -1
View File
@@ -19,7 +19,7 @@ namespace {
}
/** Driver for the HRS3300 heart rate sensor.
* Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/drivers/hrs3300.py
* Original implementation from wasp-os : https://github.com/wasp-os/wasp-os/blob/master/wasp/drivers/hrs3300.py
*
* Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour
*/
+2 -2
View File
@@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn {
nrf_gpio_pin_set(pinCsn);
}
bool Spi::Write(const uint8_t* data, size_t size) {
return spiMaster.Write(pinCsn, data, size);
bool Spi::Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
return spiMaster.Write(pinCsn, data, size, preTransactionHook);
}
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <functional>
#include "drivers/SpiMaster.h"
namespace Pinetime {
@@ -14,7 +15,7 @@ namespace Pinetime {
Spi& operator=(Spi&&) = delete;
bool Init();
bool Write(const uint8_t* data, size_t size);
bool Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
void Sleep();
+44 -39
View File
@@ -94,32 +94,45 @@ bool SpiMaster::Init() {
return true;
}
void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
// Create an event when SCK toggles.
NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | (spim->PSEL.SCK << GPIOTE_CONFIG_PSEL_Pos) |
(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
void SpiMaster::SetupWorkaroundForErratum58() {
nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
nrfx_gpiote_in_config_t gpioteCfg = {.sense = NRF_GPIOTE_POLARITY_TOGGLE,
.pull = NRF_GPIO_PIN_NOPULL,
.is_watcher = false,
.hi_accuracy = true,
.skip_gpio_setup = true};
if (!workaroundActive) {
// Create an event when SCK toggles.
APP_ERROR_CHECK(nrfx_gpiote_in_init(pin, &gpioteCfg, NULL));
nrfx_gpiote_in_event_enable(pin, false);
// Stop the spim instance when SCK toggles.
nrf_ppi_channel_endpoint_setup(workaroundPpi, nrfx_gpiote_in_event_addr_get(pin), spiBaseAddress->TASKS_STOP);
nrf_ppi_channel_enable(workaroundPpi);
}
// Stop the spim instance when SCK toggles.
NRF_PPI->CH[ppi_channel].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel];
NRF_PPI->CH[ppi_channel].TEP = (uint32_t) &spim->TASKS_STOP;
NRF_PPI->CHENSET = 1U << ppi_channel;
spiBaseAddress->EVENTS_END = 0;
// Disable IRQ
spim->INTENCLR = (1 << 6);
spim->INTENCLR = (1 << 1);
spim->INTENCLR = (1 << 19);
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
workaroundActive = true;
}
void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
NRF_GPIOTE->CONFIG[gpiote_channel] = 0;
NRF_PPI->CH[ppi_channel].EEP = 0;
NRF_PPI->CH[ppi_channel].TEP = 0;
NRF_PPI->CHENSET = ppi_channel;
void SpiMaster::DisableWorkaroundForErratum58() {
nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
if (workaroundActive) {
nrfx_gpiote_in_uninit(pin);
nrf_ppi_channel_disable(workaroundPpi);
}
spiBaseAddress->EVENTS_END = 0;
spim->INTENSET = (1 << 6);
spim->INTENSET = (1 << 1);
spim->INTENSET = (1 << 19);
// Enable IRQ
spiBaseAddress->INTENSET = (1 << 6);
spiBaseAddress->INTENSET = (1 << 1);
spiBaseAddress->INTENSET = (1 << 19);
workaroundActive = false;
}
void SpiMaster::OnEndEvent() {
@@ -136,17 +149,11 @@ void SpiMaster::OnEndEvent() {
spiBaseAddress->TASKS_START = 1;
} else {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (taskToNotify != nullptr) {
vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
BaseType_t xHigherPriorityTaskWoken2 = pdFALSE;
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2);
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
@@ -173,21 +180,23 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
spiBaseAddress->EVENTS_END = 0;
}
bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
if (data == nullptr)
return false;
auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
ASSERT(ok == true);
taskToNotify = xTaskGetCurrentTaskHandle();
this->pinCsn = pinCsn;
if (size == 1) {
SetupWorkaroundForFtpan58(spiBaseAddress, 0, 0);
SetupWorkaroundForErratum58();
} else {
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
}
if (preTransactionHook != nullptr) {
preTransactionHook();
}
nrf_gpio_pin_clear(this->pinCsn);
currentBufferAddr = (uint32_t) data;
@@ -205,7 +214,7 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
xSemaphoreGive(mutex);
}
@@ -216,10 +225,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
taskToNotify = nullptr;
this->pinCsn = pinCsn;
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
@@ -265,10 +272,8 @@ void SpiMaster::Wakeup() {
bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
taskToNotify = nullptr;
this->pinCsn = pinCsn;
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
+8 -4
View File
@@ -1,10 +1,13 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <FreeRTOS.h>
#include <semphr.h>
#include <task.h>
#include "nrfx_gpiote.h"
#include "nrf_ppi.h"
namespace Pinetime {
namespace Drivers {
@@ -31,7 +34,7 @@ namespace Pinetime {
SpiMaster& operator=(SpiMaster&&) = delete;
bool Init();
bool Write(uint8_t pinCsn, const uint8_t* data, size_t size);
bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
@@ -43,8 +46,8 @@ namespace Pinetime {
void Wakeup();
private:
void SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void SetupWorkaroundForErratum58();
void DisableWorkaroundForErratum58();
void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
void PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size);
@@ -56,8 +59,9 @@ namespace Pinetime {
volatile uint32_t currentBufferAddr = 0;
volatile size_t currentBufferSize = 0;
volatile TaskHandle_t taskToNotify;
SemaphoreHandle_t mutex = nullptr;
static constexpr nrf_ppi_channel_t workaroundPpi = NRF_PPI_CHANNEL0;
bool workaroundActive = false;
};
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() {
void SpiNorFlash::Sleep() {
auto cmd = static_cast<uint8_t>(Commands::DeepPowerDown);
spi.Write(&cmd, sizeof(uint8_t));
spi.Write(&cmd, sizeof(uint8_t), nullptr);
NRF_LOG_INFO("[SpiNorFlash] Sleep")
}
+61 -42
View File
@@ -1,8 +1,8 @@
#include "drivers/St7789.h"
#include <hal/nrf_gpio.h>
#include <libraries/delay/nrf_delay.h>
#include <nrfx_log.h>
#include "drivers/Spi.h"
#include "task.h"
using namespace Pinetime::Drivers;
@@ -29,37 +29,77 @@ void St7789::Init() {
DisplayOn();
}
void St7789::WriteCommand(uint8_t cmd) {
nrf_gpio_pin_clear(pinDataCommand);
WriteSpi(&cmd, 1);
}
void St7789::WriteData(uint8_t data) {
nrf_gpio_pin_set(pinDataCommand);
WriteSpi(&data, 1);
WriteData(&data, 1);
}
void St7789::WriteSpi(const uint8_t* data, size_t size) {
spi.Write(data, size);
void St7789::WriteData(const uint8_t* data, size_t size) {
WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
nrf_gpio_pin_set(pinDataCommand);
});
}
void St7789::WriteCommand(uint8_t data) {
WriteCommand(&data, 1);
}
void St7789::WriteCommand(const uint8_t* data, size_t size) {
WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
nrf_gpio_pin_clear(pinDataCommand);
});
}
void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
spi.Write(data, size, preTransactionHook);
}
void St7789::SoftwareReset() {
EnsureSleepOutPostDelay();
WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset));
nrf_delay_ms(150);
// If sleep in: must wait 120ms before sleep out can sent (see driver datasheet)
// Unconditionally wait as software reset doesn't need to be performant
sleepIn = true;
lastSleepExit = xTaskGetTickCount();
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::SleepOut() {
if (!sleepIn) {
return;
}
WriteCommand(static_cast<uint8_t>(Commands::SleepOut));
// Wait 5ms for clocks to stabilise
// pdMS rounds down => 6 used here
vTaskDelay(pdMS_TO_TICKS(6));
// Cannot send sleep in or software reset for 120ms
lastSleepExit = xTaskGetTickCount();
sleepIn = false;
}
void St7789::EnsureSleepOutPostDelay() {
TickType_t delta = xTaskGetTickCount() - lastSleepExit;
// Due to timer wraparound, there is a chance of delaying when not necessary
// It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad
if (delta < pdMS_TO_TICKS(125)) {
vTaskDelay(pdMS_TO_TICKS(125) - delta);
}
}
void St7789::SleepIn() {
if (sleepIn) {
return;
}
EnsureSleepOutPostDelay();
WriteCommand(static_cast<uint8_t>(Commands::SleepIn));
// Wait 5ms for clocks to stabilise
// pdMS rounds down => 6 used here
vTaskDelay(pdMS_TO_TICKS(6));
sleepIn = true;
}
void St7789::ColMod() {
WriteCommand(static_cast<uint8_t>(Commands::ColMod));
WriteData(0x55);
nrf_delay_ms(10);
}
void St7789::MemoryDataAccessControl() {
@@ -96,12 +136,10 @@ void St7789::RowAddressSet() {
void St7789::DisplayInversionOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
nrf_delay_ms(10);
}
void St7789::NormalModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
nrf_delay_ms(10);
}
void St7789::DisplayOn() {
@@ -120,12 +158,11 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
WriteData(y0 & 0xff);
WriteData(y1 >> 8);
WriteData(y1 & 0xff);
WriteToRam();
}
void St7789::WriteToRam() {
void St7789::WriteToRam(const uint8_t* data, size_t size) {
WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
WriteData(data, size);
}
void St7789::SetVdv() {
@@ -137,17 +174,6 @@ void St7789::SetVdv() {
void St7789::DisplayOff() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOff));
nrf_delay_ms(500);
}
void St7789::VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollDefinition));
WriteData(topFixedLines >> 8u);
WriteData(topFixedLines & 0x00ffu);
WriteData(scrollLines >> 8u);
WriteData(scrollLines & 0x00ffu);
WriteData(bottomFixedLines >> 8u);
WriteData(bottomFixedLines & 0x00ffu);
}
void St7789::VerticalScrollStartAddress(uint16_t line) {
@@ -160,27 +186,20 @@ void St7789::VerticalScrollStartAddress(uint16_t line) {
void St7789::Uninit() {
}
void St7789::DrawPixel(uint16_t x, uint16_t y, uint32_t color) {
if (x >= Width || y >= Height) {
return;
}
SetAddrWindow(x, y, x + 1, y + 1);
nrf_gpio_pin_set(pinDataCommand);
WriteSpi(reinterpret_cast<const uint8_t*>(&color), 2);
}
void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) {
SetAddrWindow(x, y, x + width - 1, y + height - 1);
nrf_gpio_pin_set(pinDataCommand);
WriteSpi(data, size);
WriteToRam(data, size);
}
void St7789::HardwareReset() {
nrf_gpio_pin_clear(pinReset);
nrf_delay_ms(10);
vTaskDelay(pdMS_TO_TICKS(1));
nrf_gpio_pin_set(pinReset);
// If hardware reset started while sleep out, reset time may be up to 120ms
// Unconditionally wait as hardware reset doesn't need to be performant
sleepIn = true;
lastSleepExit = xTaskGetTickCount();
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::Sleep() {
+10 -4
View File
@@ -1,6 +1,9 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <FreeRTOS.h>
namespace Pinetime {
namespace Drivers {
@@ -16,9 +19,7 @@ namespace Pinetime {
void Init();
void Uninit();
void DrawPixel(uint16_t x, uint16_t y, uint32_t color);
void VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void VerticalScrollStartAddress(uint16_t line);
void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
@@ -31,23 +32,27 @@ namespace Pinetime {
uint8_t pinDataCommand;
uint8_t pinReset;
uint8_t verticalScrollingStartAddress = 0;
bool sleepIn;
TickType_t lastSleepExit;
void HardwareReset();
void SoftwareReset();
void SleepOut();
void EnsureSleepOutPostDelay();
void SleepIn();
void ColMod();
void MemoryDataAccessControl();
void DisplayInversionOn();
void NormalModeOn();
void WriteToRam();
void WriteToRam(const uint8_t* data, size_t size);
void DisplayOn();
void DisplayOff();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void SetVdv();
void WriteCommand(uint8_t cmd);
void WriteSpi(const uint8_t* data, size_t size);
void WriteCommand(const uint8_t* data, size_t size);
void WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
enum class Commands : uint8_t {
SoftwareReset = 0x01,
@@ -67,6 +72,7 @@ namespace Pinetime {
VdvSet = 0xc4,
};
void WriteData(uint8_t data);
void WriteData(const uint8_t* data, size_t size);
void ColumnAddressSet();
static constexpr uint16_t Width = 240;
+1 -4
View File
@@ -103,10 +103,7 @@ void HeartRateTask::Work() {
void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
/* Actual macro used here is port specific. */
// TODO : should I do something here?
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void HeartRateTask::StartMeasurement() {
+3 -1
View File
@@ -731,7 +731,9 @@ typedef void* lv_obj_user_data_t;
#define LV_USE_TABLE 1
#if LV_USE_TABLE
#define LV_TABLE_COL_MAX 12
#define LV_TABLE_CELL_STYLE_CNT 5
#define LV_TABLE_CELL_STYLE_CNT 6
#define LV_TABLE_PART_CELL5 5
#define LV_TABLE_PART_CELL6 6
#endif
-5
View File
@@ -10,7 +10,6 @@
#include <libraries/gpiote/app_gpiote.h>
#include <hal/nrf_wdt.h>
#include <cstring>
#include <components/gfx/Gfx.h>
#include <drivers/St7789.h>
#include <components/brightness/BrightnessController.h>
#include <algorithm>
@@ -48,7 +47,6 @@ Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi};
Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset};
Pinetime::Components::Gfx gfx {lcd};
Pinetime::Controllers::BrightnessController brightnessController;
void DisplayProgressBar(uint8_t percent, uint16_t color);
@@ -92,7 +90,6 @@ void Process(void* /*instance*/) {
spiNorFlash.Wakeup();
brightnessController.Init();
lcd.Init();
gfx.Init();
NRF_LOG_INFO("Display logo")
DisplayLogo();
@@ -124,7 +121,6 @@ void DisplayLogo() {
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb));
for (int i = 0; i < displayWidth; i++) {
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
ulTaskNotifyTake(pdTRUE, 500);
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
}
}
@@ -133,7 +129,6 @@ void DisplayProgressBar(uint8_t percent, uint16_t color) {
static constexpr uint8_t barHeight = 20;
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
for (int i = 0; i < barHeight; i++) {
ulTaskNotifyTake(pdTRUE, 500);
uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
}
+8 -1
View File
@@ -101,6 +101,13 @@ def main():
img = Image.open(img_path)
img_height = img.height
img_width = img.width
if args.color_format == "CF_TRUE_COLOR_ALPHA" and img.mode != "RGBA":
# support pictures stored in other formats like with a color palette 'P'
# see: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
img = img.convert(mode="RGBA")
elif args.color_format == "CF_INDEXED_1_BIT" and img.mode != "L":
# for CF_INDEXED_1_BIT we need just a grayscale value per pixel
img = img.convert(mode="L")
if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
for y in range(img_height):
@@ -140,7 +147,7 @@ def main():
for y in range(img_height):
for x in range(img_width):
c, a = img.getpixel((x,y))
c = img.getpixel((x,y))
p = w * y + (x >> 3) + 8 # +8 for the palette
buf[p] |= (c & 0x1) << (7 - (x & 0x7))
# write palette information, for indexed-1-bit we need palette with two values
+11 -5
View File
@@ -342,8 +342,12 @@ void SystemTask::Work() {
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
alarmController.State() != AlarmController::AlarmState::Alerting) {
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
// request running first and repush the chime message
if (state == SystemTaskState::Sleeping) {
GoToRunning();
PushMessage(msg);
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
}
}
@@ -353,8 +357,12 @@ void SystemTask::Work() {
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
alarmController.State() != AlarmController::AlarmState::Alerting) {
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
// request running first and repush the chime message
if (state == SystemTaskState::Sleeping) {
GoToRunning();
PushMessage(msg);
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
}
}
@@ -418,7 +426,8 @@ void SystemTask::UpdateMotion() {
}
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake))) {
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
motionController.GetService()->IsMotionNotificationSubscribed())) {
return;
}
@@ -503,10 +512,7 @@ void SystemTask::PushMessage(System::Messages msg) {
if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
/* Actual macro used here is port specific. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} else {
xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);
}