weather: Refactor temperature type for type safety

There is now a Temperature struct in the weather service, which holds
the internal representation. There is also a temperature struct in the
Applications namespace, which holds the temperature in either Celsius or
Fahrenheit.
This commit is contained in:
FintasticMan 2024-02-20 11:25:02 +01:00 committed by JF
parent afeded0126
commit 29ad09f4ef
8 changed files with 84 additions and 62 deletions

View File

@ -398,6 +398,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/Styles.cpp displayapp/screens/Styles.cpp
displayapp/screens/WeatherSymbols.cpp displayapp/screens/WeatherSymbols.cpp
displayapp/Colors.cpp displayapp/Colors.cpp
displayapp/Weather.cpp
displayapp/widgets/Counter.cpp displayapp/widgets/Counter.cpp
displayapp/widgets/PageIndicator.cpp displayapp/widgets/PageIndicator.cpp
displayapp/widgets/DotIndicator.cpp displayapp/widgets/DotIndicator.cpp
@ -606,6 +607,7 @@ set(INCLUDE_FILES
displayapp/screens/ApplicationList.h displayapp/screens/ApplicationList.h
displayapp/screens/CheckboxList.h displayapp/screens/CheckboxList.h
displayapp/Apps.h displayapp/Apps.h
displayapp/Weather.h
displayapp/screens/Notifications.h displayapp/screens/Notifications.h
displayapp/screens/HeartRate.h displayapp/screens/HeartRate.h
displayapp/screens/Metronome.h displayapp/screens/Metronome.h

View File

@ -42,9 +42,9 @@ namespace {
std::memcpy(cityName.data(), &dataBuffer[16], 32); std::memcpy(cityName.data(), &dataBuffer[16], 32);
cityName[32] = '\0'; cityName[32] = '\0';
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]), return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
ToInt16(&dataBuffer[10]), SimpleWeatherService::Temperature {ToInt16(&dataBuffer[10])},
ToInt16(&dataBuffer[12]), SimpleWeatherService::Temperature {ToInt16(&dataBuffer[12])},
ToInt16(&dataBuffer[14]), SimpleWeatherService::Temperature {ToInt16(&dataBuffer[14])},
SimpleWeatherService::Icons {dataBuffer[16 + 32]}, SimpleWeatherService::Icons {dataBuffer[16 + 32]},
std::move(cityName)); std::move(cityName));
} }
@ -56,8 +56,8 @@ namespace {
const uint8_t nbDaysInBuffer = dataBuffer[10]; const uint8_t nbDaysInBuffer = dataBuffer[10];
const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer); const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer);
for (int i = 0; i < nbDays; i++) { for (int i = 0; i < nbDays; i++) {
days[i] = SimpleWeatherService::Forecast::Day {ToInt16(&dataBuffer[11 + (i * 5)]), days[i] = SimpleWeatherService::Forecast::Day {SimpleWeatherService::Temperature {ToInt16(&dataBuffer[11 + (i * 5)])},
ToInt16(&dataBuffer[13 + (i * 5)]), SimpleWeatherService::Temperature {ToInt16(&dataBuffer[13 + (i * 5)])},
SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}}; SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}};
} }
return SimpleWeatherService::Forecast {timestamp, nbDays, days}; return SimpleWeatherService::Forecast {timestamp, nbDays, days};
@ -154,13 +154,14 @@ std::optional<SimpleWeatherService::Forecast> SimpleWeatherService::GetForecast(
} }
bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const { bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const {
return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp && return this->iconId == other.iconId && this->temperature.temp == other.temperature.temp && this->timestamp == other.timestamp &&
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature && this->maxTemperature.temp == other.maxTemperature.temp && this->minTemperature.temp == other.maxTemperature.temp &&
std::strcmp(this->location.data(), other.location.data()) == 0; std::strcmp(this->location.data(), other.location.data()) == 0;
} }
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const { bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature; return this->iconId == other.iconId && this->maxTemperature.temp == other.maxTemperature.temp &&
this->minTemperature.temp == other.maxTemperature.temp;
} }
bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const { bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {

View File

@ -61,13 +61,17 @@ namespace Pinetime {
Unknown = 255 Unknown = 255
}; };
struct Temperature {
int16_t temp;
};
using Location = std::array<char, 33>; // 32 char + \0 (end of string) using Location = std::array<char, 33>; // 32 char + \0 (end of string)
struct CurrentWeather { struct CurrentWeather {
CurrentWeather(uint64_t timestamp, CurrentWeather(uint64_t timestamp,
int16_t temperature, Temperature temperature,
int16_t minTemperature, Temperature minTemperature,
int16_t maxTemperature, Temperature maxTemperature,
Icons iconId, Icons iconId,
Location&& location) Location&& location)
: timestamp {timestamp}, : timestamp {timestamp},
@ -79,9 +83,9 @@ namespace Pinetime {
} }
uint64_t timestamp; uint64_t timestamp;
int16_t temperature; Temperature temperature;
int16_t minTemperature; Temperature minTemperature;
int16_t maxTemperature; Temperature maxTemperature;
Icons iconId; Icons iconId;
Location location; Location location;
@ -93,8 +97,8 @@ namespace Pinetime {
uint8_t nbDays; uint8_t nbDays;
struct Day { struct Day {
int16_t minTemperature; Temperature minTemperature;
int16_t maxTemperature; Temperature maxTemperature;
Icons iconId; Icons iconId;
bool operator==(const Day& other) const; bool operator==(const Day& other) const;
@ -108,10 +112,6 @@ namespace Pinetime {
std::optional<CurrentWeather> Current() const; std::optional<CurrentWeather> Current() const;
std::optional<Forecast> GetForecast() const; std::optional<Forecast> GetForecast() const;
static int16_t CelsiusToFahrenheit(int16_t celsius) {
return celsius * 9 / 5 + 3200;
}
private: private:
// 00050000-78fc-48fe-8e23-433b3a1942d0 // 00050000-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t BaseUuid() { static constexpr ble_uuid128_t BaseUuid() {

View File

@ -0,0 +1,13 @@
#include "displayapp/Weather.h"
using namespace Pinetime::Applications;
Temperature Pinetime::Applications::Convert(Controllers::SimpleWeatherService::Temperature temp,
Controllers::Settings::WeatherFormat format) {
Temperature t = {temp.temp};
if (format == Controllers::Settings::WeatherFormat::Imperial) {
t.temp = t.temp * 9 / 5 + 3200;
}
t.temp = t.temp / 100 + (t.temp % 100 >= 50 ? 1 : 0);
return t;
}

17
src/displayapp/Weather.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
#include <variant>
#include "components/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
namespace Pinetime {
namespace Applications {
struct Temperature {
int16_t temp;
};
Temperature Convert(Controllers::SimpleWeatherService::Temperature temp, Controllers::Settings::WeatherFormat format);
}
}

View File

@ -2,6 +2,8 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <cstdio> #include <cstdio>
#include "displayapp/Weather.h"
#include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h" #include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h" #include "displayapp/screens/WeatherSymbols.h"
@ -174,14 +176,12 @@ void WatchFaceDigital::Refresh() {
if (currentWeather.IsUpdated()) { if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get(); auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) { if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
char tempUnit = 'C'; char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
tempUnit = 'F'; tempUnit = 'F';
} }
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0); Applications::Temperature temp = Applications::Convert(optCurrentWeather->temperature, settingsController.GetWeatherFormat());
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); lv_label_set_text_fmt(temperature, "%d°%c", temp.temp, tempUnit);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else { } else {
lv_label_set_text_static(temperature, ""); lv_label_set_text_static(temperature, "");

View File

@ -22,7 +22,8 @@
#include "displayapp/screens/WatchFacePineTimeStyle.h" #include "displayapp/screens/WatchFacePineTimeStyle.h"
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <cstdio> #include <cstdio>
#include <displayapp/Colors.h> #include "displayapp/Colors.h"
#include "displayapp/Weather.h"
#include "displayapp/screens/BatteryIcon.h" #include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/BleIcon.h" #include "displayapp/screens/BleIcon.h"
#include "displayapp/screens/NotificationIcon.h" #include "displayapp/screens/NotificationIcon.h"
@ -543,11 +544,7 @@ void WatchFacePineTimeStyle::Refresh() {
if (currentWeather.IsUpdated()) { if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get(); auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) { if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature; Applications::Temperature temp = Applications::Convert(optCurrentWeather->temperature, settingsController.GetWeatherFormat());
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°", temp); lv_label_set_text_fmt(temperature, "%d°", temp);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else { } else {

View File

@ -1,8 +1,11 @@
#include "displayapp/screens/Weather.h" #include "displayapp/screens/Weather.h"
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "components/ble/SimpleWeatherService.h" #include "components/ble/SimpleWeatherService.h"
#include "components/datetime/DateTimeController.h" #include "components/datetime/DateTimeController.h"
#include "components/settings/Settings.h" #include "components/settings/Settings.h"
#include "displayapp/Weather.h"
#include "displayapp/DisplayApp.h" #include "displayapp/DisplayApp.h"
#include "displayapp/screens/WeatherSymbols.h" #include "displayapp/screens/WeatherSymbols.h"
#include "displayapp/InfiniTimeTheme.h" #include "displayapp/InfiniTimeTheme.h"
@ -10,31 +13,27 @@
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
namespace { namespace {
lv_color_t TemperatureColor(int16_t temperature) { lv_color_t TemperatureColor(Pinetime::Applications::Temperature temp) {
if (temperature <= 0) { // freezing if (temp.temp <= 0) { // freezing
return Colors::blue; return Colors::blue;
} else if (temperature <= 400) { // ice } else if (temp.temp <= 4) { // ice
return LV_COLOR_CYAN; return LV_COLOR_CYAN;
} else if (temperature >= 2700) { // hot } else if (temp.temp >= 27) { // hot
return Colors::deepOrange; return Colors::deepOrange;
} }
return Colors::orange; // normal return Colors::orange; // normal
} }
uint8_t TemperatureStyle(int16_t temperature) { uint8_t TemperatureStyle(Pinetime::Applications::Temperature temp) {
if (temperature <= 0) { // freezing if (temp.temp <= 0) { // freezing
return LV_TABLE_PART_CELL3; return LV_TABLE_PART_CELL3;
} else if (temperature <= 400) { // ice } else if (temp.temp <= 4) { // ice
return LV_TABLE_PART_CELL4; return LV_TABLE_PART_CELL4;
} else if (temperature >= 2700) { // hot } else if (temp.temp >= 27) { // hot
return LV_TABLE_PART_CELL6; return LV_TABLE_PART_CELL6;
} }
return LV_TABLE_PART_CELL5; // normal 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) Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
@ -120,22 +119,19 @@ void Weather::Refresh() {
if (currentWeather.IsUpdated()) { if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get(); auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) { if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature; Applications::Temperature temp = Applications::Convert(optCurrentWeather->temperature, settingsController.GetWeatherFormat());
int16_t minTemp = optCurrentWeather->minTemperature; Applications::Temperature minTemp = Applications::Convert(optCurrentWeather->minTemperature, settingsController.GetWeatherFormat());
int16_t maxTemp = optCurrentWeather->maxTemperature; Applications::Temperature maxTemp = Applications::Convert(optCurrentWeather->maxTemperature, settingsController.GetWeatherFormat());
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, TemperatureColor(temp));
char tempUnit = 'C'; char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
tempUnit = 'F'; tempUnit = 'F';
} }
lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId)); lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
lv_label_set_text(condition, Symbols::GetCondition(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(temperature, "%d°%c", temp.temp, tempUnit);
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp)); lv_label_set_text_fmt(minTemperature, "%d°", minTemp.temp);
lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp)); lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp.temp);
} else { } else {
lv_label_set_text(icon, ""); lv_label_set_text(icon, "");
lv_label_set_text(condition, ""); lv_label_set_text(condition, "");
@ -153,27 +149,23 @@ void Weather::Refresh() {
std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp)); std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
int16_t maxTemp = optCurrentForecast->days[i].maxTemperature; Applications::Temperature maxTemp =
int16_t minTemp = optCurrentForecast->days[i].minTemperature; Applications::Convert(optCurrentForecast->days[i].maxTemperature, settingsController.GetWeatherFormat());
Applications::Temperature minTemp =
Applications::Convert(optCurrentForecast->days[i].minTemperature, settingsController.GetWeatherFormat());
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp)); lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp)); 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; uint8_t wday = localTime.tm_wday + i + 1;
if (wday > 7) { if (wday > 7) {
wday -= 7; wday -= 7;
} }
maxTemp = RoundTemperature(maxTemp);
minTemp = RoundTemperature(minTemp);
const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday)); 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, 0, i, dayOfWeek);
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId)); 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 // Pad cells based on the largest number of digits on each column
char maxPadding[3] = " "; char maxPadding[3] = " ";
char minPadding[3] = " "; char minPadding[3] = " ";
int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp); int diff = snprintf(nullptr, 0, "%d", maxTemp.temp) - snprintf(nullptr, 0, "%d", minTemp.temp);
if (diff <= 0) { if (diff <= 0) {
maxPadding[-diff] = '\0'; maxPadding[-diff] = '\0';
minPadding[0] = '\0'; minPadding[0] = '\0';
@ -181,8 +173,8 @@ void Weather::Refresh() {
maxPadding[0] = '\0'; maxPadding[0] = '\0';
minPadding[diff] = '\0'; minPadding[diff] = '\0';
} }
lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp); lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp.temp);
lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp); lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp.temp);
} }
} else { } else {
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {