diff --git a/.gitignore b/.gitignore index 81e49ae0..40e809a3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ cmake_install.cmake Makefile build tools - +node_modules/ # Resulting binary files *.a *.so diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 9c99b664..f6038816 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,54 +1,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..75a67e67 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,118 @@ +{ + "name": "InfiniTime", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "lv_font_conv": "^1.5.2" + } + }, + "node_modules/lv_font_conv": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/lv_font_conv/-/lv_font_conv-1.5.2.tgz", + "integrity": "sha512-0UapRSTkVP/pnB8Z4r2HDHx5p2dJx/xUG1+14u/WXo59mwuC7BahR+Bnx/66jKoDrG1wFQwn9ZzoyMxRHOD9bg==", + "bundleDependencies": [ + "argparse", + "bit-buffer", + "debug", + "make-error", + "mkdirp", + "opentype.js", + "pngjs" + ], + "dependencies": { + "argparse": "^2.0.0", + "bit-buffer": "^0.2.5", + "debug": "^4.1.1", + "make-error": "^1.3.5", + "mkdirp": "^1.0.4", + "opentype.js": "^1.1.0", + "pngjs": "^6.0.0" + }, + "bin": { + "lv_font_conv": "lv_font_conv.js" + } + }, + "node_modules/lv_font_conv/node_modules/argparse": { + "version": "2.0.1", + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/lv_font_conv/node_modules/bit-buffer": { + "version": "0.2.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lv_font_conv/node_modules/make-error": { + "version": "1.3.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/lv_font_conv/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lv_font_conv/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/opentype.js": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/lv_font_conv/node_modules/pngjs": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/lv_font_conv/node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/lv_font_conv/node_modules/tiny-inflate": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..d339766c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lv_font_conv": "^1.5.2" + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd8ece62..a49fe863 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 @@ -586,6 +587,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/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index 03d2f6ff..1aa52771 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -60,7 +60,7 @@ namespace Pinetime { Smog = 8, // Mist Unknown = 255 }; - + using Location = std::array; // 32 char + \0 (end of string) struct CurrentWeather { @@ -111,6 +111,10 @@ namespace Pinetime { static int16_t CelsiusToFahrenheit(int16_t celsius) { return celsius * 9 / 5 + 3200; } + + static const char* TemperatureColor(int16_t temperature); + + static int16_t RoundTemperature(int16_t temp); private: // 00050000-78fc-48fe-8e23-433b3a1942d0 @@ -125,7 +129,7 @@ namespace Pinetime { } ble_uuid128_t weatherUuid {BaseUuid()}; - + ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)}; const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUuid.u, diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h index 67bbfa7d..531a447d 100644 --- a/src/displayapp/UserApps.h +++ b/src/displayapp/UserApps.h @@ -14,6 +14,7 @@ #include "displayapp/screens/WatchFaceInfineat.h" #include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFaceTerminal.h" +#include "displayapp/screens/Weather.h" namespace Pinetime { namespace Applications { 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/List.h b/src/displayapp/screens/List.h index 17a25f82..b5e0e555 100644 --- a/src/displayapp/screens/List.h +++ b/src/displayapp/screens/List.h @@ -8,6 +8,7 @@ #include "displayapp/apps/Apps.h" #include "components/settings/Settings.h" + #define MAXLISTITEMS 4 namespace Pinetime { diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index dd39f88a..6f20f0a4 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -81,7 +81,7 @@ std::unique_ptr SystemInfo::CreateScreen1() { lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); lv_label_set_recolor(label, true); lv_label_set_text_fmt(label, - "#FFFF00 InfiniTime#\n\n" + "#FFFF00 InfiniTime#\n" "#808080 Version# %ld.%ld.%ld\n" "#808080 Short Ref# %s\n" "#808080 Build date#\n" diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 5321b7cc..c53c28a7 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -1,4 +1,5 @@ #include "displayapp/screens/Weather.h" +#include "displayapp/WeatherHelper.h" #include #include "components/ble/SimpleWeatherService.h" #include "components/datetime/DateTimeController.h" @@ -9,34 +10,6 @@ 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} { @@ -123,7 +96,7 @@ void Weather::Refresh() { 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)); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, WeatherHelper::TemperatureColor(temp)); char tempUnit = 'C'; if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp); @@ -133,9 +106,11 @@ void Weather::Refresh() { } 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)); + lv_label_set_text_fmt(temperature, "%d°%c", WeatherHelper::RoundTemperature(temp), tempUnit); + + lv_label_set_text_fmt(minTemperature, "%d°", WeatherHelper::RoundTemperature(minTemp)); + lv_label_set_text_fmt(maxTemperature, "%d°", WeatherHelper::RoundTemperature(maxTemp)); + } else { lv_label_set_text(icon, ""); lv_label_set_text(condition, ""); @@ -155,8 +130,14 @@ void Weather::Refresh() { 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)); + + auto color = WeatherHelper::TemperatureColor(maxTemp); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, color); + lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL5); + + color = WeatherHelper::TemperatureColor(minTemp); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, color); + lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL6); if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp); minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp); @@ -165,8 +146,8 @@ void Weather::Refresh() { if (wday > 7) { wday -= 7; } - maxTemp = RoundTemperature(maxTemp); - minTemp = RoundTemperature(minTemp); + maxTemp = WeatherHelper::RoundTemperature(maxTemp); + minTemp = WeatherHelper::RoundTemperature(minTemp); const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast(wday)); lv_table_set_cell_value(forecast, 0, i, dayOfWeek); lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId)); @@ -181,7 +162,11 @@ void Weather::Refresh() { maxPadding[0] = '\0'; minPadding[diff] = '\0'; } + //auto color = WeatherHelper::TemperatureColor(maxTemp); + //lv_obj_set_style_local_text_color(forecast, 2, i, color); lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp); + //color = WeatherHelper::TemperatureColor(minTemp); + //lv_obj_set_style_local_text_color(forecast, 3, i, color); lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp); } } else { diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h index 6975311e..e72cc86e 100644 --- a/src/displayapp/screens/Weather.h +++ b/src/displayapp/screens/Weather.h @@ -24,7 +24,7 @@ namespace Pinetime { ~Weather() override; void Refresh() override; - + private: Controllers::Settings& settingsController; Controllers::SimpleWeatherService& weatherService;