diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e2b69b8b..eef66e3b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -364,6 +364,7 @@ list(APPEND SOURCE_FILES
BootloaderVersion.cpp
logging/NrfLogger.cpp
displayapp/DisplayApp.cpp
+ displayapp/WeatherHelper.cpp
displayapp/screens/Screen.cpp
displayapp/screens/Tile.cpp
displayapp/screens/InfiniPaint.cpp
@@ -588,6 +589,7 @@ set(INCLUDE_FILES
logging/Logger.h
logging/NrfLogger.h
displayapp/DisplayApp.h
+ displayapp/WeatherHelper.h
displayapp/Messages.h
displayapp/TouchEvents.h
displayapp/screens/Screen.h
diff --git a/src/displayapp/WeatherHelper.cpp b/src/displayapp/WeatherHelper.cpp
new file mode 100644
index 00000000..7a6e78a6
--- /dev/null
+++ b/src/displayapp/WeatherHelper.cpp
@@ -0,0 +1,110 @@
+/* Copyright (C) 2024 Caleb Fontenot
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "WeatherHelper.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace Pinetime::Applications;
+ //Linear gradient temperature color calculator :)
+
+ int16_t WeatherHelper::RoundTemperature(int16_t temp) {
+ return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
+ }
+ std::tuple rgb565to888(int r, int g, int b) {
+ return {
+ ( r * 527 + 23 ) >> 6,
+ ( g * 259 + 33 ) >> 6,
+ ( b * 527 + 23 ) >> 6
+ };
+ }
+
+ const char* WeatherHelper::floatToRgbHex(lv_color_t rgb) {
+ std::tuple tuple = rgb565to888(LV_COLOR_GET_R(rgb), LV_COLOR_GET_G(rgb), LV_COLOR_GET_B(rgb));
+ char *rgbHex = new char[7];
+ snprintf(rgbHex, 7, "%02X%02X%02X", std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple));
+ return rgbHex;
+ }
+
+ lv_color_t hexToFloat(int rgb) {
+ return lv_color_hex(rgb);
+ }
+
+ float normalize(float value) {
+ if (value < 0.0f) {
+ return 0.0f;
+ } else if (value > 1.0f) {
+ return 1.0f;
+ } else {
+ return value;
+ }
+}
+
+ // reference: https://dev.to/ndesmic/linear-color-gradients-from-scratch-1a0e
+ const lv_color_t lerp(lv_color_t pointA, lv_color_t pointB, float normalValue) {
+ auto [redA, greenA, blueA] = rgb565to888(LV_COLOR_GET_R(pointA), LV_COLOR_GET_G(pointA), LV_COLOR_GET_B(pointA));
+ auto [redB, greenB, blueB] = rgb565to888(LV_COLOR_GET_R(pointB), LV_COLOR_GET_G(pointB), LV_COLOR_GET_B(pointB));
+ NRF_LOG_INFO("Normal value: %f", normalValue);
+
+ int outputRed = (redA + (redB - redA) * normalValue);
+ int outputGreen = (greenA + (greenB - greenA) * normalValue);
+ int outputBlue = (blueA + (blueB - blueA) * normalValue);
+
+ //increase brightness
+ float incAmount = 1.2;
+ outputRed = std::min(255, static_cast(outputRed*incAmount));
+ outputGreen = std::min(255, static_cast(outputGreen*incAmount));
+ outputBlue = std::min(255, static_cast(outputBlue*incAmount));
+
+ auto lerpOutput = LV_COLOR_MAKE(outputRed, outputGreen, outputBlue);
+ NRF_LOG_INFO("pointA: %i, %i, %i", redA, greenA, blueA);
+ NRF_LOG_INFO("pointB: %i, %i, %i", redB, greenB, blueB);
+ NRF_LOG_INFO("lerpOutput: %i, %i, %i", LV_COLOR_GET_R(lerpOutput), LV_COLOR_GET_G(lerpOutput), LV_COLOR_GET_B(lerpOutput));
+ return lerpOutput;
+ }
+
+ constexpr std::array getColors() {
+ const std::array colors = {0x5555ff, 0x00c9ff, 0xff9b00, 0xff0000};
+ std::array stops;
+ int8_t i = 0;
+ for (auto colorVal: colors) {
+ stops[i++] = (hexToFloat(colorVal));
+ }
+ return stops;
+ }
+
+ const lv_color_t WeatherHelper::TemperatureColor(int16_t temperature) {
+ std::array stops = getColors();
+ int tempRounded = RoundTemperature(temperature);
+ if (tempRounded < 0) {
+ tempRounded = 1;
+ }
+ // convert temperature to range between newMin and newMax
+ float oldRange = (oldMax - oldMin);
+ float newRange = (newMax - newMin);
+ float newValue = (((tempRounded - oldMin) * newRange) / oldRange) + newMin;
+ newValue = normalize(newValue);
+ return lerp(stops[0], stops[3], newValue);
+ }
diff --git a/src/displayapp/WeatherHelper.h b/src/displayapp/WeatherHelper.h
new file mode 100644
index 00000000..bb3aba81
--- /dev/null
+++ b/src/displayapp/WeatherHelper.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2024 Caleb Fontenot
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#pragma once
+#include
+#include
+
+namespace Pinetime {
+ namespace Applications {
+ class WeatherHelper {
+ public:
+ static int16_t RoundTemperature(int16_t temp);
+ static const lv_color_t TemperatureColor(int16_t temperature);
+ static const char* floatToRgbHex(lv_color_t rgb);
+ constexpr static float oldMax = 50;
+ constexpr static float oldMin = 0;
+ constexpr static float newMax = 1;
+ constexpr static float newMin = 0;
+ };
+ }
+ }
diff --git a/src/displayapp/screens/WatchFaceTerminal.cpp b/src/displayapp/screens/WatchFaceTerminal.cpp
index 96d77741..020a6b1d 100644
--- a/src/displayapp/screens/WatchFaceTerminal.cpp
+++ b/src/displayapp/screens/WatchFaceTerminal.cpp
@@ -1,14 +1,21 @@
+#include
#include
#include "displayapp/screens/WatchFaceTerminal.h"
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.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/settings/Settings.h"
+#include "displayapp/WeatherHelper.h"
+#include
+#include
+#include
+#include
using namespace Pinetime::Applications::Screens;
@@ -18,7 +25,8 @@ WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
- Controllers::MotionController& motionController)
+ Controllers::MotionController& motionController,
+ Controllers::SimpleWeatherService& weatherController)
: currentDateTime {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
@@ -26,14 +34,15 @@ WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
- motionController {motionController} {
+ motionController {motionController},
+ weatherController {weatherController} {
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);
+ lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 80);
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -100);
@@ -47,7 +56,7 @@ WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
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_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 100);
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
label_time = lv_label_create(lv_scr_act(), nullptr);
@@ -62,6 +71,14 @@ WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
lv_label_set_recolor(stepValue, true);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
+ weatherStatus = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(weatherStatus, true);
+ lv_obj_align(weatherStatus, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);
+
+ bytesFree = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_recolor(bytesFree, true);
+ lv_obj_align(bytesFree, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
+
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
@@ -71,6 +88,42 @@ WatchFaceTerminal::~WatchFaceTerminal() {
lv_obj_clean(lv_scr_act());
}
+// https://www.wpc.ncep.noaa.gov/html/contract.html
+const char* WeatherString(uint8_t weatherID) {
+ switch (weatherID) {
+ case 0: {
+ return "CLR";
+ }
+ case 1: {
+ return "SLGT CLD";
+ }
+ case 2: {
+ return "CLD";
+ }
+ case 3: {
+ return "EXTRM CLD";
+ }
+ case 4: {
+ return "TSRA";
+ }
+ case 5: {
+ return "RA";
+ }
+ case 6: {
+ return "TSTM";
+ }
+ case 7: {
+ return "SN";
+ }
+ case 8: {
+ return "BR";
+ }
+ default: {
+ return "UNKN";
+ }
+ }
+}
+
void WatchFaceTerminal::Refresh() {
powerPresent = batteryController.IsPowerPresent();
batteryPercentRemaining = batteryController.PercentRemaining();
@@ -104,6 +157,41 @@ void WatchFaceTerminal::Refresh() {
}
}
+ // Following along from the example given by Weather.cpp...
+
+ // get an instance of the weather service? not exactly sure what this does...
+ currentWeather = weatherController.Current();
+ // check to see if we have valid weather data
+ if (currentWeather.IsUpdated()) {
+ auto optCurrentWeather = currentWeather.Get(); // the actual weather data
+ if (optCurrentWeather) {
+ int16_t temp = optCurrentWeather->temperature.PreciseCelsius(); // current temperature
+ uint8_t weatherId = static_cast(optCurrentWeather->iconId); // weather type
+ NRF_LOG_INFO("Raw temp: %d", temp);
+ NRF_LOG_INFO("Rounded temp: %d", WeatherHelper::RoundTemperature(temp));
+ // testColor(); //testVal * 100
+ auto color = WeatherHelper::floatToRgbHex(WeatherHelper::TemperatureColor(temp)); // call temperature color BEFORE unit conversion
+ // unit conversion
+ char tempUnit = 'C';
+ if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
+ temp = optCurrentWeather->temperature.PreciseFahrenheit();
+ tempUnit = 'F';
+ }
+ NRF_LOG_INFO("Color hex: %s", color);
+ lv_label_set_text_fmt(weatherStatus,
+ "[WTHR]#%s %d#°%c %s",
+ color,
+ WeatherHelper::RoundTemperature(temp),
+ tempUnit,
+ WeatherString(weatherId));
+ delete[] color;
+ } else {
+ lv_label_set_text_static(weatherStatus, "[WTHR]No Data");
+ }
+ }
+
+ lv_label_set_text_fmt(bytesFree, "[MEM] %i B Free", xPortGetFreeHeapSize());
+
currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
diff --git a/src/displayapp/screens/WatchFaceTerminal.h b/src/displayapp/screens/WatchFaceTerminal.h
index bf460866..4841ba39 100644
--- a/src/displayapp/screens/WatchFaceTerminal.h
+++ b/src/displayapp/screens/WatchFaceTerminal.h
@@ -7,6 +7,7 @@
#include
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
+#include "components/ble/SimpleWeatherService.h"
#include "utility/DirtyValue.h"
namespace Pinetime {
@@ -30,7 +31,9 @@ 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;
@@ -45,6 +48,7 @@ namespace Pinetime {
Utility::DirtyValue heartbeat {};
Utility::DirtyValue heartbeatRunning {};
Utility::DirtyValue notificationState {};
+ Utility::DirtyValue> currentWeather {};
Utility::DirtyValue> currentDate;
lv_obj_t* label_time;
@@ -56,6 +60,8 @@ namespace Pinetime {
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* connectState;
+ lv_obj_t* weatherStatus;
+ lv_obj_t* bytesFree;
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
@@ -64,7 +70,7 @@ namespace Pinetime {
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
-
+ Controllers::SimpleWeatherService& weatherController;
lv_task_t* taskRefresh;
};
}
@@ -81,7 +87,8 @@ namespace Pinetime {
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
- controllers.motionController);
+ controllers.motionController,
+ *controllers.weatherController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {