Skip to content

Commit 4a0a11b

Browse files
committed
watchface: add implementation and infrastructure fixes
- Added new watchface implementation ("Prime") - Formatting and patch cleanup - Fix short ref not set in GitHub builds - Init notification buffer to prevent uninitialized memory reads - Fix nRF SDK download links in Docker and build scripts - Refactor watchfaces to use xTaskGetTickCount() instead of lv_tick_get()
1 parent faf3985 commit 4a0a11b

10 files changed

Lines changed: 362 additions & 10 deletions

File tree

.github/workflows/main.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ jobs:
3939
apt-get -y install --no-install-recommends python3-pil
4040
- name: Build
4141
shell: bash
42-
run: /opt/build.sh all
42+
run: |
43+
git config --global --add safe.directory /__w/InfiniTime/InfiniTime
44+
/opt/build.sh all
4345
- name: Output build size
4446
id: output-sizes
47+
shell: bash
4548
run: |
4649
. /opt/build.sh
4750
.github/workflows/getSize.sh "$BUILD_DIR"/src/pinetime-app-*.out >> $GITHUB_OUTPUT
@@ -159,6 +162,7 @@ jobs:
159162

160163
- name: Output build size
161164
id: output-sizes
165+
shell: bash
162166
run: |
163167
. /opt/build.sh
164168
.github/workflows/getSize.sh "$BUILD_DIR"/src/pinetime-app-*.out >> $GITHUB_OUTPUT

doc/buildAndProgram.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
To build this project, you'll need:
66

77
- A cross-compiler : [ARM-GCC (10.3-2021.10)](https://developer.arm.com/downloads/-/gnu-rm)
8-
- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip)
8+
- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/nrf5sdk153059ac345.zip)
99
- The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt))
1010
- To keep the system clean, you can install python modules into a python virtual environment (`venv`)
1111
```sh

docker/build.sh

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export npm_config_cache="${NPM_DIR}"
1818
export BUILD_TYPE=${BUILD_TYPE:=Release}
1919
export GCC_ARM_VER=${GCC_ARM_VER:="10.3-2021.10"}
2020
export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"}
21+
# convert to lower case and remove _ and . character
22+
# the download URL uses the SLUG, but the extracted folder is named like the original value
23+
NRF_SDK_VER_SLUG=${NRF_SDK_VER,,}
24+
export NRF_SDK_VER_SLUG=${NRF_SDK_VER_SLUG//[_.]/}
2125

2226
MACHINE="$(uname -m)"
2327
[ "$MACHINE" = "arm64" ] && MACHINE="aarch64"
@@ -47,17 +51,29 @@ main() {
4751

4852
GetGcc() {
4953
wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/$GCC_ARM_VER/$GCC_ARM_PATH-$MACHINE-linux.tar.bz2 -O - | tar -xj -C $TOOLS_DIR/
54+
if [ ! -d "$TOOLS_DIR/$GCC_ARM_PATH" ]; then
55+
echo "missing GCC path: $TOOLS_DIR/$GCC_ARM_PATH"
56+
return 1
57+
fi
5058
}
5159

5260
GetMcuBoot() {
5361
git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
5462
pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
63+
if [ ! -d "$TOOLS_DIR/mcuboot" ]; then
64+
echo "missing mcuboot path: $TOOLS_DIR/mcuboot"
65+
return 1
66+
fi
5567
}
5668

5769
GetNrfSdk() {
58-
wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER
70+
wget -q "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/sdks/nrf5/binaries/$NRF_SDK_VER_SLUG.zip" -O /tmp/$NRF_SDK_VER
5971
unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/"
6072
rm /tmp/$NRF_SDK_VER
73+
if [ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]; then
74+
echo "missing NRF_SDK path: $TOOLS_DIR/$NRF_SDK_VER"
75+
return 1
76+
fi
6177
}
6278

6379
CmakeGenerate() {

src/components/ble/NotificationManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace Pinetime {
2828
using Id = uint8_t;
2929
using Idx = uint8_t;
3030

31-
std::array<char, MessageSize + 1> message;
31+
std::array<char, MessageSize + 1> message{};
3232
uint8_t size;
3333
Categories category = Categories::Unknown;
3434
Id id = 0;

src/displayapp/screens/WatchFaceInfineat.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ WatchFaceInfineat::~WatchFaceInfineat() {
316316
bool WatchFaceInfineat::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
317317
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) {
318318
lv_obj_set_hidden(btnSettings, false);
319-
savedTick = lv_tick_get();
319+
savedTick = xTaskGetTickCount();
320320
return true;
321321
}
322322
// Prevent screen from sleeping when double tapping with settings on
@@ -463,7 +463,7 @@ void WatchFaceInfineat::Refresh() {
463463
}
464464

465465
if (!lv_obj_get_hidden(btnSettings)) {
466-
if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) {
466+
if ((savedTick > 0) && (xTaskGetTickCount() - savedTick > pdMS_TO_TICKS(3000))) {
467467
lv_obj_set_hidden(btnSettings, true);
468468
savedTick = 0;
469469
}

src/displayapp/screens/WatchFaceInfineat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ namespace Pinetime {
4545
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
4646

4747
private:
48-
uint32_t savedTick = 0;
4948
uint8_t chargingBatteryPercent = 101; // not a mistake ;)
49+
TickType_t savedTick = 0;
5050
TickType_t chargingAnimationTick = 0;
5151

5252
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};

src/displayapp/screens/WatchFacePineTimeStyle.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ bool WatchFacePineTimeStyle::OnTouchEvent(Pinetime::Applications::TouchEvents ev
407407
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnClose)) {
408408
lv_obj_set_hidden(btnSetColor, false);
409409
lv_obj_set_hidden(btnSetOpts, false);
410-
savedTick = lv_tick_get();
410+
savedTick = xTaskGetTickCount();
411411
return true;
412412
}
413413
if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && (lv_obj_get_hidden(btnClose) == false)) {
@@ -558,7 +558,7 @@ void WatchFacePineTimeStyle::Refresh() {
558558
}
559559

560560
if (!lv_obj_get_hidden(btnSetColor)) {
561-
if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) {
561+
if ((savedTick > 0) && (xTaskGetTickCount() - savedTick > pdMS_TO_TICKS(3000))) {
562562
lv_obj_set_hidden(btnSetColor, true);
563563
lv_obj_set_hidden(btnSetOpts, true);
564564
savedTick = 0;

src/displayapp/screens/WatchFacePineTimeStyle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ namespace Pinetime {
5252
Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
5353
Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
5454
uint8_t currentDay = 0;
55-
uint32_t savedTick = 0;
55+
TickType_t savedTick = 0;
5656

5757
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
5858
Utility::DirtyValue<bool> isCharging {};
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#include "displayapp/screens/WatchFacePrime.h"
2+
3+
#include <lvgl/lvgl.h>
4+
#include <cstdio>
5+
6+
#include "displayapp/screens/NotificationIcon.h"
7+
#include "displayapp/screens/Symbols.h"
8+
#include "displayapp/screens/WeatherSymbols.h"
9+
#include "components/battery/BatteryController.h"
10+
#include "components/ble/BleController.h"
11+
#include "components/ble/NotificationManager.h"
12+
#include "components/heartrate/HeartRateController.h"
13+
#include "components/motion/MotionController.h"
14+
#include "components/ble/SimpleWeatherService.h"
15+
#include "components/settings/Settings.h"
16+
#include "displayapp/InfiniTimeTheme.h"
17+
#include "components/ble/MusicService.h"
18+
19+
using namespace Pinetime::Applications::Screens;
20+
21+
WatchFacePrime::WatchFacePrime(Controllers::DateTime& dateTimeController,
22+
const Controllers::Battery& batteryController,
23+
const Controllers::Ble& bleController,
24+
const Controllers::AlarmController& alarmController,
25+
Controllers::NotificationManager& notificationManager,
26+
Controllers::Settings& settingsController,
27+
Controllers::HeartRateController& heartRateController,
28+
Controllers::MotionController& motionController,
29+
Controllers::SimpleWeatherService& weatherService,
30+
Controllers::MusicService& music)
31+
: currentDateTime {{}},
32+
dateTimeController {dateTimeController},
33+
notificationManager {notificationManager},
34+
settingsController {settingsController},
35+
heartRateController {heartRateController},
36+
motionController {motionController},
37+
weatherService {weatherService},
38+
musicService(music),
39+
statusIcons(batteryController, bleController, alarmController) {
40+
41+
statusIcons.Create();
42+
43+
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
44+
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
45+
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
46+
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
47+
48+
weatherIcon = lv_label_create(lv_scr_act(), nullptr);
49+
lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
50+
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
51+
lv_label_set_text_static(weatherIcon, Symbols::ban);
52+
lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, 0, 30);
53+
lv_obj_set_auto_realign(weatherIcon, true);
54+
55+
temperature = lv_label_create(lv_scr_act(), nullptr);
56+
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
57+
lv_label_set_text_static(temperature, "--°C");
58+
lv_obj_align(temperature, weatherIcon, LV_ALIGN_CENTER, 0, 25);
59+
60+
label_date = lv_label_create(lv_scr_act(), nullptr);
61+
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 50);
62+
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
63+
64+
label_music = lv_label_create(lv_scr_act(), nullptr);
65+
lv_label_set_text_fmt(label_music, "%s Not Playing", Symbols::music);
66+
lv_obj_set_style_local_text_color(label_music, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bgAlt);
67+
lv_label_set_long_mode(label_music, LV_LABEL_LONG_SROLL_CIRC);
68+
lv_obj_set_width(label_music, LV_HOR_RES - 12);
69+
lv_label_set_align(label_music, LV_LABEL_ALIGN_CENTER);
70+
lv_obj_align(label_music, lv_scr_act(), LV_ALIGN_CENTER, 0, 78);
71+
72+
label_time = lv_label_create(lv_scr_act(), nullptr);
73+
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &prime);
74+
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
75+
76+
label_time_ampm = lv_label_create(lv_scr_act(), nullptr);
77+
lv_label_set_text_static(label_time_ampm, "");
78+
lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -30, -55);
79+
80+
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
81+
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
82+
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
83+
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 1);
84+
85+
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
86+
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xff4539));
87+
lv_label_set_text_static(heartbeatValue, "");
88+
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
89+
90+
stepValue = lv_label_create(lv_scr_act(), nullptr);
91+
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0a84ff));
92+
lv_label_set_text_static(stepValue, "0");
93+
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
94+
95+
stepIcon = lv_label_create(lv_scr_act(), nullptr);
96+
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0a84ff));
97+
lv_label_set_text_static(stepIcon, Symbols::shoe);
98+
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
99+
100+
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
101+
Refresh();
102+
}
103+
104+
WatchFacePrime::~WatchFacePrime() {
105+
lv_task_del(taskRefresh);
106+
lv_obj_clean(lv_scr_act());
107+
}
108+
109+
void WatchFacePrime::Refresh() {
110+
statusIcons.Update();
111+
112+
notificationState = notificationManager.AreNewNotificationsAvailable();
113+
if (notificationState.IsUpdated()) {
114+
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
115+
}
116+
117+
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
118+
119+
if (currentDateTime.IsUpdated()) {
120+
uint8_t hour = dateTimeController.Hours();
121+
uint8_t minute = dateTimeController.Minutes();
122+
123+
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
124+
char ampmChar[3] = "AM";
125+
if (hour == 0) {
126+
hour = 12;
127+
} else if (hour == 12) {
128+
ampmChar[0] = 'P';
129+
} else if (hour > 12) {
130+
hour = hour - 12;
131+
ampmChar[0] = 'P';
132+
}
133+
lv_label_set_text(label_time_ampm, ampmChar);
134+
lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute);
135+
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
136+
} else {
137+
lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute);
138+
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
139+
}
140+
141+
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
142+
if (currentDate.IsUpdated()) {
143+
uint16_t year = dateTimeController.Year();
144+
uint8_t day = dateTimeController.Day();
145+
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
146+
lv_label_set_text_fmt(label_date,
147+
"%s, %02d.%02d.%d",
148+
dateTimeController.DayOfWeekShortToStringLow(dateTimeController.DayOfWeek()),
149+
day,
150+
dateTimeController.Month(),
151+
year);
152+
} else {
153+
lv_label_set_text_fmt(label_date,
154+
"%s %s %d %d",
155+
dateTimeController.DayOfWeekShortToString(),
156+
dateTimeController.MonthShortToString(),
157+
day,
158+
year);
159+
}
160+
lv_obj_realign(label_date);
161+
}
162+
}
163+
164+
heartbeat = heartRateController.HeartRate();
165+
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
166+
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
167+
if (heartbeatRunning.Get()) {
168+
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xff4539));
169+
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
170+
} else {
171+
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
172+
lv_label_set_text_static(heartbeatValue, "");
173+
}
174+
175+
lv_obj_realign(heartbeatIcon);
176+
lv_obj_realign(heartbeatValue);
177+
}
178+
179+
stepCount = motionController.NbSteps();
180+
if (stepCount.IsUpdated()) {
181+
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
182+
lv_obj_realign(stepValue);
183+
lv_obj_realign(stepIcon);
184+
}
185+
186+
currentWeather = weatherService.Current();
187+
if (currentWeather.IsUpdated()) {
188+
auto optCurrentWeather = currentWeather.Get();
189+
if (optCurrentWeather) {
190+
int16_t temp = optCurrentWeather->temperature.Celsius();
191+
char tempUnit = 'C';
192+
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
193+
temp = optCurrentWeather->temperature.Fahrenheit();
194+
tempUnit = 'F';
195+
}
196+
if (temp <= 0) { // freezing
197+
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
198+
} else if (temp <= 5) { // near freezing
199+
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN);
200+
} else if (temp <= 15) { // early spring
201+
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x4db8d1));
202+
} else if (temp <= 25) { // warm
203+
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
204+
} else if (temp >= 25) { // hot
205+
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::deepOrange);
206+
}
207+
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
208+
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
209+
} else {
210+
lv_label_set_text_static(temperature, "--°");
211+
lv_label_set_text(weatherIcon, Symbols::ban);
212+
}
213+
lv_obj_realign(temperature);
214+
lv_obj_realign(weatherIcon);
215+
}
216+
if (track != musicService.getTrack()) {
217+
track = musicService.getTrack();
218+
lv_label_set_text_fmt(label_music, "%s %s", Symbols::music, track.data());
219+
lv_obj_realign(label_music);
220+
}
221+
}

0 commit comments

Comments
 (0)