Skip to content

Commit

Permalink
Terminal Watchface Improvements and added Weather.
Browse files Browse the repository at this point in the history
Use @vkareh's new weather forecast app as a template to implement
the weather into the terminal watch face. Use InfintimeTheme Colors
instead of hardcoded hex values. Reorder code to match the order of
the UI. Implement @vkareh's variable battery icon color to the battery
percentage text. Added a new IninftimeTheme color, gray, and turn
certain values gray when they contain no data, like weather.
  • Loading branch information
JustScott committed Feb 3, 2024
1 parent 2135e12 commit 66b10c5
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 61 deletions.
1 change: 1 addition & 0 deletions src/displayapp/InfiniTimeTheme.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Colors {
static constexpr lv_color_t green = LV_COLOR_MAKE(0x0, 0xb0, 0x0);
static constexpr lv_color_t blue = LV_COLOR_MAKE(0x0, 0x50, 0xff);
static constexpr lv_color_t lightGray = LV_COLOR_MAKE(0xb0, 0xb0, 0xb0);
static constexpr lv_color_t gray = LV_COLOR_MAKE(0x50, 0x50, 0x50);

static constexpr lv_color_t bg = LV_COLOR_MAKE(0x5d, 0x69, 0x7e);
static constexpr lv_color_t bgAlt = LV_COLOR_MAKE(0x38, 0x38, 0x38);
Expand Down
165 changes: 110 additions & 55 deletions src/displayapp/screens/WatchFaceTerminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,84 @@
#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/ble/SimpleWeatherService.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/settings/Settings.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
}
}

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)
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weatherService)
: 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);
motionController {motionController},
weatherService {weatherService} {

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_set_style_local_text_color(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
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);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);

weather = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(weather, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);

batteryValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);

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);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 20);

heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(heartbeatValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);

connectState = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);

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, 80);
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");

taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
Expand All @@ -72,29 +91,6 @@ WatchFaceTerminal::~WatchFaceTerminal() {
}

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()) {
Expand All @@ -104,6 +100,7 @@ void WatchFaceTerminal::Refresh() {
}
}


currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
Expand All @@ -120,32 +117,90 @@ void WatchFaceTerminal::Refresh() {
hour = hour - 12;
ampmChar[0] = 'P';
}
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
lv_label_set_text_fmt(label_time, "[TIME] %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
} else {
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
lv_label_set_text_fmt(label_time, "[TIME] %02d:%02d:%02d", hour, minute, second);
}

currentDate = std::chrono::time_point_cast<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));
lv_label_set_text_fmt(label_date, "[DATE] %04d-%02d-%02d#", short(year), char(month), char(day));
}
}


currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
char tempUnit = 'C';
lv_obj_set_style_local_text_color(weather, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, temperatureColor(temp));
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
condition = Symbols::GetSimpleCondition(optCurrentWeather->iconId);
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
tempUnit = 'F';
}
lv_label_set_text_fmt(weather, "[WTHR] %i°%c %s ", temp/100, tempUnit, condition);
} else {
lv_label_set_text(weather, "[WTHR] ---°");
lv_obj_set_style_local_text_color(weather, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
}
}


powerPresent = batteryController.IsPowerPresent();
batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
// HSV color model has red at 0° and green at 120°.
// We lock satuation and brightness at 100% and traverse the cilinder
// between red and green, thus avoiding the darker RGB on medium battery
// charges and giving us a much nicer color range.
uint8_t hue = batteryPercentRemaining.Get() * 120 / 100;
lv_obj_set_style_local_text_color(batteryValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hsv_to_rgb(hue, 100, 100));
lv_label_set_text_fmt(batteryValue, "[BATT] %d%%", batteryPercentRemaining.Get());
if (batteryController.IsPowerPresent()) {
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
}
}


stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "[STEP] %lu steps#", stepCount.Get());
}


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());

lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::deepOrange);
lv_label_set_text_fmt(heartbeatValue, "[L_HR] %d bpm#", heartbeat.Get());
} else {
lv_label_set_text_static(heartbeatValue, "[L_HR]#ee3311 ---#");
lv_label_set_text_static(heartbeatValue, "[L_HR] ---#");
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
}
}

stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "[STEP]#ee3377 %lu steps#", stepCount.Get());
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
if (!bleRadioEnabled.Get()) {
lv_label_set_text_static(connectState, "[STAT] Disabled#");
lv_obj_set_style_local_text_color(connectState, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
} else {
if (bleState.Get()) {
lv_label_set_text_static(connectState, "[STAT] Connected#");
lv_obj_set_style_local_text_color(connectState, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
} else {
lv_label_set_text_static(connectState, "[STAT] Disconnected#");
lv_obj_set_style_local_text_color(connectState, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
}
}
}
}
20 changes: 14 additions & 6 deletions src/displayapp/screens/WatchFaceTerminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <displayapp/Controllers.h>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "components/ble/SimpleWeatherService.h"
#include "utility/DirtyValue.h"

namespace Pinetime {
Expand All @@ -30,13 +31,15 @@ namespace Pinetime {
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController);
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weatherService);
~WatchFaceTerminal() override;

void Refresh() override;

private:
Utility::DirtyValue<int> batteryPercentRemaining {};
Utility::DirtyValue<uint8_t> hue {};
Utility::DirtyValue<bool> powerPresent {};
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<bool> bleRadioEnabled {};
Expand All @@ -47,16 +50,19 @@ namespace Pinetime {
Utility::DirtyValue<bool> notificationState {};
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<const char*> condition {};

lv_obj_t* notificationIcon;
lv_obj_t* label_prompt_1;
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* weather;
lv_obj_t* batteryValue;
lv_obj_t* heartbeatValue;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* heartbeatValue;
lv_obj_t* connectState;
lv_obj_t* label_prompt_2;

Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
Expand All @@ -65,6 +71,7 @@ namespace Pinetime {
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
Controllers::SimpleWeatherService& weatherService;

lv_task_t* taskRefresh;
};
Expand All @@ -82,7 +89,8 @@ namespace Pinetime {
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController);
controllers.motionController,
*controllers.weatherController);
};

static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
Expand Down
25 changes: 25 additions & 0 deletions src/displayapp/screens/WeatherSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,28 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::
break;
}
}

const char* Pinetime::Applications::Screens::Symbols::GetSimpleCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
switch (icon) {
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
return "Clear";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
return "Cloudy";
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
return "Cloudy";
case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
return "Cloudy";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
return "Rainy";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
return "Rainy";
case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
return "Stormy";
case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
return "Snowy";
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
return "Misty";
default:
return "";
}
}
1 change: 1 addition & 0 deletions src/displayapp/screens/WeatherSymbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Pinetime {
namespace Screens {
namespace Symbols {
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
const char* GetSimpleCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
}
}
}
Expand Down

0 comments on commit 66b10c5

Please sign in to comment.