From c94a59e7d3e0f9929171263412033a56872c168a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sat, 9 Dec 2023 20:39:08 +0100 Subject: [PATCH] SimpleWeather service : new weather implementation This new implementation of the weather feature provides a new BLE API and a new weather service. The API uses a single characteristic that allows companion apps to write the weather conditions (current and forecast for the next 5 days). The SimpleWeather service exposes those data as std::optional fields. This new implementation replaces the previous WeahterService. The API is documented in docs/SimpleWeatherService.md. --- doc/SimpleWeatherService.md | 68 ++ doc/ble.md | 5 +- src/CMakeLists.txt | 48 +- src/components/ble/NimbleController.h | 6 +- src/components/ble/SimpleWeatherService.cpp | 153 +++++ src/components/ble/SimpleWeatherService.h | 131 ++++ src/components/ble/weather/WeatherData.h | 385 ----------- src/components/ble/weather/WeatherService.cpp | 614 ------------------ src/components/ble/weather/WeatherService.h | 169 ----- src/displayapp/Controllers.h | 4 +- src/displayapp/DisplayApp.cpp | 3 +- src/displayapp/DisplayApp.h | 3 +- src/displayapp/screens/StopWatch.cpp | 2 + .../screens/WatchFacePineTimeStyle.cpp | 46 +- .../screens/WatchFacePineTimeStyle.h | 10 +- src/displayapp/screens/Weather.cpp | 2 +- src/displayapp/screens/Weather.h | 2 +- 17 files changed, 406 insertions(+), 1245 deletions(-) create mode 100644 doc/SimpleWeatherService.md create mode 100644 src/components/ble/SimpleWeatherService.cpp create mode 100644 src/components/ble/SimpleWeatherService.h delete mode 100644 src/components/ble/weather/WeatherData.h delete mode 100644 src/components/ble/weather/WeatherService.cpp delete mode 100644 src/components/ble/weather/WeatherService.h diff --git a/doc/SimpleWeatherService.md b/doc/SimpleWeatherService.md new file mode 100644 index 00000000..c510e428 --- /dev/null +++ b/doc/SimpleWeatherService.md @@ -0,0 +1,68 @@ +# Simple Weather Service + +## Introduction + +The Simple Weather Service provide a simple and straightforward API to specify the current weather and the forecast for the next 5 days. It effectively replaces the original Weather Service (from InfiniTime 1.8) since InfiniTime 1.14 + +## Service + +The service UUID is `00050000-78fc-48fe-8e23-433b3a1942d0`. + +## Characteristics + +## Weather data (UUID 00050001-78fc-48fe-8e23-433b3a1942d0) + +The host uses this characteristic to update the current weather information and the forecast for the next 5 days. + +This characteristics accepts a byte array with the following 2-Bytes header: + + - [0] Message Type : + - `0` : Current weather + - `1` : Forecast + - [1] Message Version : Version `0` is currently supported. Other versions might be added in future releases + +### Current Weather + +The byte array must contain the following data: + + - [0] : Message type = `0` + - [1] : Message version = `0` + - [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of nanoseconds elapsed since 1 JAN 1970) + - [10] : Current temperature (°C) + - [11] : Minimum temperature (°C) + - [12] : Maximum temperature (°C) + - [13]..[44] : location (string, unused characters should be set to `0`) + - [45] : icon ID + - 0 = Sun, clear sky + - 1 = Few clouds + - 2 = Clouds + - 3 = Heavy clouds + - 4 = Clouds & rain + - 5 = Rain + - 6 = Thunderstorm + - 7 = snow + - 8 = mist, smog + +### Forecast + +The byte array must contain the following data: + + - [0] : Message type = `0` + - [0] : Message version = `0` + - [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of nanoseconds elapsed since 1 JAN 1970) + - [10] Number of days (Max 5, fields for unused days should be set to `0`) + - [11] Day 0 Minimum temperature + - [12] Day 0 Maximum temperature + - [13] Day 0 Icon ID + - [14] Day 1 Minimum temperature + - [15] Day 1 Maximum temperature + - [16] Day 1 Icon ID + - [17] Day 2 Minimum temperature + - [18] Day 2 Maximum temperature + - [19] Day 2 Icon ID + - [20] Day 3 Minimum temperature + - [21] Day 3 Maximum temperature + - [22] Day 3 Icon ID + - [23] Day 4 Minimum temperature + - [24] Day 4 Maximum temperature + - [25] Day 4 Incon ID \ No newline at end of file diff --git a/doc/ble.md b/doc/ble.md index 9c170418..72434408 100644 --- a/doc/ble.md +++ b/doc/ble.md @@ -92,7 +92,10 @@ The following custom services are implemented in InfiniTime: - Since InfiniTime 1.8: - - [Weather Service](/src/components/ble/weather/WeatherService.h): `00040000-78fc-48fe-8e23-433b3a1942d0` + - ~~Weather Service: `00040000-78fc-48fe-8e23-433b3a1942d0`~~ (replaced by Simple Weather Service in InfiniTime 1.14) + +- Since InfiniTime 1.14 + - [Simple Weather Server](SimpleWeahterService.md) : `00050000-78fc-48fe-8e23-433b3a1942d0` --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a993e02..bb7b90c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -355,14 +355,6 @@ set(LVGL_SRC libs/lvgl/src/lv_widgets/lv_win.c ) -set(QCBOR_SRC - libs/QCBOR/src/ieee754.c - libs/QCBOR/src/qcbor_decode.c - libs/QCBOR/src/qcbor_encode.c - libs/QCBOR/src/qcbor_err_to_str.c - libs/QCBOR/src/UsefulBuf.c - ) - list(APPEND IMAGE_FILES displayapp/icons/battery/batteryicon.c ) @@ -384,7 +376,6 @@ list(APPEND SOURCE_FILES displayapp/screens/Label.cpp displayapp/screens/FirmwareUpdate.cpp displayapp/screens/Music.cpp - displayapp/screens/Weather.cpp displayapp/screens/Navigation.cpp displayapp/screens/Metronome.cpp displayapp/screens/Motion.cpp @@ -459,7 +450,7 @@ list(APPEND SOURCE_FILES components/ble/CurrentTimeService.cpp components/ble/AlertNotificationService.cpp components/ble/MusicService.cpp - components/ble/weather/WeatherService.cpp + components/ble/SimpleWeatherService.cpp components/ble/NavigationService.cpp components/ble/BatteryInformationService.cpp components/ble/FSService.cpp @@ -528,7 +519,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/ble/CurrentTimeService.cpp components/ble/AlertNotificationService.cpp components/ble/MusicService.cpp - components/ble/weather/WeatherService.cpp + components/ble/SimpleWeatherService.cpp components/ble/BatteryInformationService.cpp components/ble/FSService.cpp components/ble/ImmediateAlertService.cpp @@ -655,7 +646,7 @@ set(INCLUDE_FILES components/ble/BleClient.h components/ble/HeartRateService.h components/ble/MotionService.h - components/ble/weather/WeatherService.h + components/ble/SimpleWeatherService.h components/settings/Settings.h components/timer/Timer.h components/alarm/AlarmController.h @@ -891,27 +882,6 @@ target_compile_options(lvgl PRIVATE $<$: ${ASM_FLAGS}> ) -# QCBOR -add_library(QCBOR STATIC ${QCBOR_SRC}) -target_include_directories(QCBOR SYSTEM PUBLIC libs/QCBOR/inc) -# This is required with the current configuration -target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_FLOAT_HW_USE) -# These are for space-saving -target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_PREFERRED_FLOAT) -target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_EXP_AND_MANTISSA) -target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_STRINGS) -#target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_ARRAYS) -target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_UNCOMMON_TAGS) -target_compile_definitions(QCBOR PUBLIC USEFULBUF_CONFIG_LITTLE_ENDIAN) -set_target_properties(QCBOR PROPERTIES LINKER_LANGUAGE C) -target_compile_options(QCBOR PRIVATE - ${COMMON_FLAGS} - $<$: ${DEBUG_FLAGS}> - $<$: ${RELEASE_FLAGS}> - $<$: ${ASM_FLAGS}> - -O3 - ) - # LITTLEFS_SRC add_library(littlefs STATIC ${LITTLEFS_SRC}) target_include_directories(littlefs SYSTEM PUBLIC . ../) @@ -930,7 +900,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld") add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES}) set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME}) -target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts) +target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts) target_compile_options(${EXECUTABLE_NAME} PUBLIC ${COMMON_FLAGS} ${WARNING_FLAGS} @@ -964,7 +934,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld") add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts) +target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts) set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME}) target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC ${COMMON_FLAGS} @@ -1006,7 +976,7 @@ endif() set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery") set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts) +target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts) set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME}) target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY") target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC @@ -1038,7 +1008,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-$ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex) set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts) +target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts) set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}) target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY") target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC @@ -1078,7 +1048,7 @@ endif() set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader") set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}) add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts) +target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts) set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME}) target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC ${COMMON_FLAGS} @@ -1113,7 +1083,7 @@ set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_N set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME_HEX ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex) set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts) +target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts) set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}) target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC ${COMMON_FLAGS} diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h index 8f1dfed7..29a395ea 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -21,7 +21,7 @@ #include "components/ble/NavigationService.h" #include "components/ble/ServiceDiscovery.h" #include "components/ble/MotionService.h" -#include "components/ble/weather/WeatherService.h" +#include "components/ble/SimpleWeatherService.h" #include "components/fs/FS.h" namespace Pinetime { @@ -67,7 +67,7 @@ namespace Pinetime { return anService; }; - Pinetime::Controllers::WeatherService& weather() { + Pinetime::Controllers::SimpleWeatherService& weather() { return weatherService; }; @@ -99,7 +99,7 @@ namespace Pinetime { AlertNotificationClient alertNotificationClient; CurrentTimeService currentTimeService; MusicService musicService; - WeatherService weatherService; + SimpleWeatherService weatherService; NavigationService navService; BatteryInformationService batteryInformationService; ImmediateAlertService immediateAlertService; diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp new file mode 100644 index 00000000..fa0ce198 --- /dev/null +++ b/src/components/ble/SimpleWeatherService.cpp @@ -0,0 +1,153 @@ +/* Copyright (C) 2023 Jean-François Milants + + 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 +#include "SimpleWeatherService.h" +#include +#include +using namespace Pinetime::Controllers; + +namespace { + enum class MessageType { + CurrentWeather, + Forecast, + Unknown + }; + + SimpleWeatherService::CurrentWeather CreateCurrentWeather(const uint8_t* dataBuffer) { + char cityName[33]; + std::memcpy(&cityName[0], &dataBuffer[13], 32); + cityName[32] = '\0'; + return SimpleWeatherService::CurrentWeather{dataBuffer[2] + (dataBuffer[3] << 8) + (dataBuffer[4] << 16) + (dataBuffer[5] << 24) + + ((uint64_t) dataBuffer[6] << 32) + ((uint64_t) dataBuffer[7] << 40) + ((uint64_t) dataBuffer[8] << 48) + + ((uint64_t) dataBuffer[9] << 54), + dataBuffer[10], + dataBuffer[11], + dataBuffer[12], + dataBuffer[13 + 32], + cityName}; + } + + SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) { + uint64_t timestamp = static_cast(dataBuffer[2] + (dataBuffer[3] << 8) + (dataBuffer[4] << 16) + (dataBuffer[5] << 24) + + ((uint64_t) dataBuffer[6] << 32) + ((uint64_t) dataBuffer[7] << 40) + ((uint64_t) dataBuffer[8] << 48) + + ((uint64_t) dataBuffer[9] << 54)); + uint8_t nbDays = dataBuffer[10]; + std::array days; + for (int i = 0; i < nbDays; i++) { + days[i] = SimpleWeatherService::Forecast::Day {dataBuffer[11 + (i * 3)], + dataBuffer[12 + (i * 3)], + dataBuffer[13 + (i * 3)]}; + } + return SimpleWeatherService::Forecast {timestamp, nbDays, days}; + } + + MessageType GetMessageType(const uint8_t* dataBuffer) { + switch(dataBuffer[0]) { + case 0: return MessageType::CurrentWeather; break; + case 1: return MessageType::Forecast; break; + default: return MessageType::Unknown; break; + } + } + + uint8_t GetVersion(const uint8_t* dataBuffer) { + return dataBuffer[1]; + } +} + +int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) { + return static_cast(arg)->OnCommand(ctxt); +} + +SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) { + +} + +void SimpleWeatherService::Init() { + ble_gatts_count_cfg(serviceDefinition); + ble_gatts_add_svcs(serviceDefinition); +} + +int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { + const auto* buffer = ctxt->om; + const auto* dataBuffer = buffer->om_data; + + switch(GetMessageType(dataBuffer)) { + case MessageType::CurrentWeather: + if(GetVersion(dataBuffer) == 0) { + currentWeather = CreateCurrentWeather(dataBuffer); + NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s", + currentWeather->timestamp, + currentWeather->temperature, + currentWeather->minTemperature, + currentWeather->maxTemperature, + currentWeather->iconId, + currentWeather->location); + } + break; + case MessageType::Forecast: + if(GetVersion(dataBuffer) == 0) { + forecast = CreateForecast(dataBuffer); + NRF_LOG_INFO("Forecast : Timestamp : %d", forecast->timestamp); + for(int i = 0; i < 5; i++) { + NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d", i, forecast->days[i].minTemperature, forecast->days[i].maxTemperature, forecast->days[i].iconId); + } + } + break; + default: + break; + } + + return 0; +} + +std::optional SimpleWeatherService::Current() const { + if(currentWeather) { + auto currentTime = dateTimeController.UTCDateTime().time_since_epoch(); + auto weatherTpSecond = std::chrono::seconds{currentWeather->timestamp}; + auto weatherTp = std::chrono::duration_cast(weatherTpSecond); + auto delta = currentTime - weatherTp; + + if(delta < std::chrono::hours{24}) { + return currentWeather; + } + } + return {}; +} + +std::optional SimpleWeatherService::GetForecast() const { + if(forecast) { + auto currentTime = dateTimeController.UTCDateTime().time_since_epoch(); + auto weatherTpSecond = std::chrono::seconds{forecast->timestamp}; + auto weatherTp = std::chrono::duration_cast(weatherTpSecond); + auto delta = currentTime - weatherTp; + + if(delta < std::chrono::hours{24}) { + return this->forecast; + } + } + return {}; +} + +bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const { + return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp && + this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature; +} + +bool SimpleWeatherService::CurrentWeather::operator!=(const SimpleWeatherService::CurrentWeather& other) const { + return !operator==(other); +} diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h new file mode 100644 index 00000000..46d5e187 --- /dev/null +++ b/src/components/ble/SimpleWeatherService.h @@ -0,0 +1,131 @@ +/* Copyright (C) 2023 Jean-François Milants + + 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 +#include +#include + +#define min // workaround: nimble's min/max macros conflict with libstdc++ +#define max +#include +#include +#include +#include +#undef max +#undef min + +#include "components/datetime/DateTimeController.h" + +int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg); + +namespace Pinetime { + namespace Controllers { + + class SimpleWeatherService { + public: + explicit SimpleWeatherService(const DateTime& dateTimeController); + + void Init(); + + int OnCommand(struct ble_gatt_access_ctxt* ctxt); + + enum class Icons : uint8_t { + Sun = 0, // ClearSky + CloudsSun = 1, // FewClouds + Clouds = 2, // Scattered clouds + BrokenClouds = 3, + CloudShowerHeavy = 4, // shower rain + CloudSunRain = 5, // rain + Thunderstorm = 6, + Snow = 7, + Smog = 8, // Mist + Unknown = 255 + }; + + struct CurrentWeather { + CurrentWeather(uint64_t timestamp, uint8_t temperature, uint8_t minTemperature, uint8_t maxTemperature, + uint8_t iconId, const char* location) + : timestamp{timestamp}, temperature{temperature}, minTemperature{minTemperature}, maxTemperature{maxTemperature}, + iconId{iconId} { + std::memcpy(this->location, location, 32); + this->location[32] = 0; + } + uint64_t timestamp; + uint8_t temperature; + uint8_t minTemperature; + uint8_t maxTemperature; + Icons iconId; + char location[33]; // 32 char + \0 (end of string) + + bool operator==(const CurrentWeather& other) const; + bool operator!=(const CurrentWeather& other) const; + }; + + struct Forecast { + uint64_t timestamp; + uint8_t nbDays; + struct Day { + uint8_t minTemperature; + uint8_t maxTemperature; + uint8_t iconId; + }; + std::array days; + }; + + std::optional Current() const; + std::optional GetForecast() const; + + + private: + // 00050000-78fc-48fe-8e23-433b3a1942d0 + static constexpr ble_uuid128_t BaseUuid() { + return CharUuid(0x00, 0x00); + } + + // 0005yyxx-78fc-48fe-8e23-433b3a1942d0 + static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) { + return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128}, + .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x05, 0x00}}; + } + + ble_uuid128_t weatherUuid {BaseUuid()}; + + ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)}; + + const struct ble_gatt_chr_def characteristicDefinition[2] = { + {.uuid = &weatherDataCharUuid.u, + .access_cb = WeatherCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE, + .val_handle = &eventHandle}, + {0}}; + const struct ble_gatt_svc_def serviceDefinition[2] = { + {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition}, + {0}}; + + uint16_t eventHandle {}; + + const Pinetime::Controllers::DateTime& dateTimeController; + + std::optional currentWeather; + std::optional forecast; + }; + } +} diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h deleted file mode 100644 index 1a995eb9..00000000 --- a/src/components/ble/weather/WeatherData.h +++ /dev/null @@ -1,385 +0,0 @@ -/* Copyright (C) 2021 Avamander - - 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 - -/** - * Different weather events, weather data structures used by {@link WeatherService.h} - * - * How to upload events to the timeline? - * - * All timeline write payloads are simply CBOR-encoded payloads of the structs described below. - * - * All payloads have a mandatory header part and the dynamic part that - * depends on the event type specified in the header. If you don't, - * you'll get an error returned. Data is relatively well-validated, - * so keep in the bounds of the data types given. - * - * Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic. - * Mind the MTU. - * - * How to debug? - * - * There's a Screen that you can compile into your firmware that shows currently valid events. - * You can adapt that to display something else. That part right now is very much work in progress - * because the exact requirements are not yet known. - * - * - * Implemented based on and other material: - * https://en.wikipedia.org/wiki/METAR - * https://www.weather.gov/jetstream/obscurationtypes - * http://www.faraim.org/aim/aim-4-03-14-493.html - */ - -namespace Pinetime { - namespace Controllers { - class WeatherData { - public: - /** - * Visibility obscuration types - */ - enum class obscurationtype { - /** No obscuration */ - None = 0, - /** Water particles suspended in the air; low visibility; does not fall */ - Fog = 1, - /** Tiny, dry particles in the air; invisible to the eye; opalescent */ - Haze = 2, - /** Small fire-created particles suspended in the air */ - Smoke = 3, - /** Fine rock powder, from for example volcanoes */ - Ash = 4, - /** Fine particles of earth suspended in the air by the wind */ - Dust = 5, - /** Fine particles of sand suspended in the air by the wind */ - Sand = 6, - /** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */ - Mist = 7, - /** This is SPECIAL in the sense that the thing raining down is doing the obscuration */ - Precipitation = 8, - Length - }; - - /** - * Types of precipitation - */ - enum class precipitationtype { - /** - * No precipitation - * - * Theoretically we could just _not_ send the event, but then - * how do we differentiate between no precipitation and - * no information about precipitation - */ - None = 0, - /** Drops larger than a drizzle; also widely separated drizzle */ - Rain = 1, - /** Fairly uniform rain consisting of fine drops */ - Drizzle = 2, - /** Rain that freezes upon contact with objects and ground */ - FreezingRain = 3, - /** Rain + hail; ice pellets; small translucent frozen raindrops */ - Sleet = 4, - /** Larger ice pellets; falling separately or in irregular clumps */ - Hail = 5, - /** Hail with smaller grains of ice; mini-snowballs */ - SmallHail = 6, - /** Snow... */ - Snow = 7, - /** Frozen drizzle; very small snow crystals */ - SnowGrains = 8, - /** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */ - IceCrystals = 9, - /** It's raining down ash, e.g. from a volcano */ - Ash = 10, - Length - }; - - /** - * These are special events that can "enhance" the "experience" of existing weather events - */ - enum class specialtype { - /** Strong wind with a sudden onset that lasts at least a minute */ - Squall = 0, - /** Series of waves in a water body caused by the displacement of a large volume of water */ - Tsunami = 1, - /** Violent; rotating column of air */ - Tornado = 2, - /** Unplanned; unwanted; uncontrolled fire in an area */ - Fire = 3, - /** Thunder and/or lightning */ - Thunder = 4, - Length - }; - - /** - * These are used for weather timeline manipulation - * that isn't just adding to the stack of weather events - */ - enum class controlcodes { - /** How much is stored already */ - GetLength = 0, - /** This wipes the entire timeline */ - DelTimeline = 1, - /** There's a currently valid timeline event with the given type */ - HasValidEvent = 3, - Length - }; - - /** - * Events have types - * then they're easier to parse after sending them over the air - */ - enum class eventtype : uint8_t { - /** @see obscuration */ - Obscuration = 0, - /** @see precipitation */ - Precipitation = 1, - /** @see wind */ - Wind = 2, - /** @see temperature */ - Temperature = 3, - /** @see airquality */ - AirQuality = 4, - /** @see special */ - Special = 5, - /** @see pressure */ - Pressure = 6, - /** @see location */ - Location = 7, - /** @see cloud */ - Clouds = 8, - /** @see humidity */ - Humidity = 9, - Length - }; - - /** - * Valid event query - * - * NOTE: Not currently available, until needs are better known - */ - class ValidEventQuery { - public: - static constexpr controlcodes code = controlcodes::HasValidEvent; - eventtype eventType; - }; - - /** The header used for further parsing */ - class TimelineHeader { - public: - /** - * UNIX timestamp - * TODO: This is currently WITH A TIMEZONE OFFSET! - * Please send events with the timestamp offset by the timezone. - **/ - uint64_t timestamp; - /** - * Time in seconds until the event expires - * - * 32 bits ought to be enough for everyone - * - * If there's a newer event of the same type then it overrides this one, even if it hasn't expired - */ - uint32_t expires; - /** - * What type of weather-related event - */ - eventtype eventType; - }; - - /** Specifies how cloudiness is stored */ - class Clouds : public TimelineHeader { - public: - /** Cloud coverage in percentage, 0-100% */ - uint8_t amount; - }; - - /** Specifies how obscuration is stored */ - class Obscuration : public TimelineHeader { - public: - /** Type of precipitation */ - obscurationtype type; - /** - * Visibility distance in meters - * 65535 is reserved for unspecified - */ - uint16_t amount; - }; - - /** Specifies how precipitation is stored */ - class Precipitation : public TimelineHeader { - public: - /** Type of precipitation */ - precipitationtype type; - /** - * How much is it going to rain? In millimeters - * 255 is reserved for unspecified - **/ - uint8_t amount; - }; - - /** - * How wind speed is stored - * - * In order to represent bursts of wind instead of constant wind, - * you have minimum and maximum speeds. - * - * As direction can fluctuate wildly and some watch faces might wish to display it nicely, - * we're following the aerospace industry weather report option of specifying a range. - */ - class Wind : public TimelineHeader { - public: - /** Meters per second */ - uint8_t speedMin; - /** Meters per second */ - uint8_t speedMax; - /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */ - uint8_t directionMin; - /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */ - uint8_t directionMax; - }; - - /** - * How temperature is stored - * - * As it's annoying to figure out the dewpoint on the watch, - * please send it from the companion - * - * We don't do floats, picodegrees are not useful. Make sure to multiply. - */ - class Temperature : public TimelineHeader { - public: - /** - * Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250) - * -32768 is reserved for "no data" - */ - int16_t temperature; - /** - * Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250) - * -32768 is reserved for "no data" - */ - int16_t dewPoint; - }; - - /** - * How location info is stored - * - * This can be mostly static with long expiration, - * as it usually is, but it could change during a trip for ex. - * so we allow changing it dynamically. - * - * Location info can be for some kind of map watch face - * or daylight calculations, should those be required. - * - */ - class Location : public TimelineHeader { - public: - /** Location name */ - std::string location; - /** Altitude relative to sea level in meters */ - int16_t altitude; - /** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */ - int32_t latitude; - /** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */ - int32_t longitude; - }; - - /** - * How humidity is stored - */ - class Humidity : public TimelineHeader { - public: - /** Relative humidity, 0-100% */ - uint8_t humidity; - }; - - /** - * How air pressure is stored - */ - class Pressure : public TimelineHeader { - public: - /** Air pressure in hectopascals (hPa) */ - int16_t pressure; - }; - - /** - * How special events are stored - */ - class Special : public TimelineHeader { - public: - /** Special event's type */ - specialtype type; - }; - - /** - * How air quality is stored - * - * These events are a bit more complex because the topic is not simple, - * the intention is to heavy-lift the annoying preprocessing from the watch - * this allows watch face or watchapp makers to generate accurate alerts and graphics - * - * If this needs further enforced standardization, pull requests are welcome - */ - class AirQuality : public TimelineHeader { - public: - /** - * The name of the pollution - * - * for the sake of better compatibility with watchapps - * that might want to use this data for say visuals - * don't localize the name. - * - * Ideally watchapp itself localizes the name, if it's at all needed. - * - * E.g. - * For generic ones use "PM0.1", "PM5", "PM10" - * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3" - * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores - */ - std::string polluter; - /** - * Amount of the pollution in SI units, - * otherwise it's going to be difficult to create UI, alerts - * and so on and for. - * - * See more: - * https://ec.europa.eu/environment/air/quality/standards.htm - * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf - * - * Example units: - * count/m³ for pollen - * µgC/m³ for micrograms of organic carbon - * µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust - * mg/m³ CO2, CO - * ng/m³ for heavy metals - * - * List is not comprehensive, should be improved. - * The current ones are what watchapps assume! - * - * Note: ppb and ppm to concentration should be calculated on the companion, using - * the correct formula (taking into account temperature and air pressure) - * - * Note2: The amount is off by times 100, for two decimal places of precision. - * E.g. 54.32µg/m³ is 5432 - * - */ - uint32_t amount; - }; - }; - } -} diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp deleted file mode 100644 index b9a6af55..00000000 --- a/src/components/ble/weather/WeatherService.cpp +++ /dev/null @@ -1,614 +0,0 @@ -/* Copyright (C) 2021 Avamander - - 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 -#include -#include "WeatherService.h" -#include "libs/QCBOR/inc/qcbor/qcbor.h" - -int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) { - return static_cast(arg)->OnCommand(ctxt); -} - -namespace Pinetime { - namespace Controllers { - WeatherService::WeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) { - nullHeader = &nullTimelineheader; - nullTimelineheader->timestamp = 0; - } - - void WeatherService::Init() { - uint8_t res = 0; - res = ble_gatts_count_cfg(serviceDefinition); - ASSERT(res == 0); - - res = ble_gatts_add_svcs(serviceDefinition); - ASSERT(res == 0); - } - - int WeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { - if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { - const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - if (packetLen <= 0) { - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - // Decode - QCBORDecodeContext decodeContext; - UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - - QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL); - // KINDLY provide us a fixed-length map - QCBORDecode_EnterMap(&decodeContext, nullptr); - // Always encodes to the smallest number of bytes based on the value - int64_t tmpTimestamp = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp); - if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - int64_t tmpExpires = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires); - if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - int64_t tmpEventType = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType); - if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 || - tmpEventType >= static_cast(WeatherData::eventtype::Length)) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - - switch (static_cast(tmpEventType)) { - case WeatherData::eventtype::AirQuality: { - std::unique_ptr airquality = std::make_unique(); - airquality->timestamp = tmpTimestamp; - airquality->eventType = static_cast(tmpEventType); - airquality->expires = tmpExpires; - - UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here? - QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf); - if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - airquality->polluter = std::string(static_cast(stringBuf.ptr), stringBuf.len); - - int64_t tmpAmount = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); - if (tmpAmount < 0 || tmpAmount > 4294967295) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - if (!AddEventToTimeline(std::move(airquality))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Obscuration: { - std::unique_ptr obscuration = std::make_unique(); - obscuration->timestamp = tmpTimestamp; - obscuration->eventType = static_cast(tmpEventType); - obscuration->expires = tmpExpires; - - int64_t tmpType = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType); - if (tmpType < 0 || tmpType >= static_cast(WeatherData::obscurationtype::Length)) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - obscuration->type = static_cast(tmpType); - - int64_t tmpAmount = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); - if (tmpAmount < 0 || tmpAmount > 65535) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - if (!AddEventToTimeline(std::move(obscuration))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Precipitation: { - std::unique_ptr precipitation = std::make_unique(); - precipitation->timestamp = tmpTimestamp; - precipitation->eventType = static_cast(tmpEventType); - precipitation->expires = tmpExpires; - - int64_t tmpType = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType); - if (tmpType < 0 || tmpType >= static_cast(WeatherData::precipitationtype::Length)) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - precipitation->type = static_cast(tmpType); - - int64_t tmpAmount = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); - if (tmpAmount < 0 || tmpAmount > 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - if (!AddEventToTimeline(std::move(precipitation))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Wind: { - std::unique_ptr wind = std::make_unique(); - wind->timestamp = tmpTimestamp; - wind->eventType = static_cast(tmpEventType); - wind->expires = tmpExpires; - - int64_t tmpMin = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin); - if (tmpMin < 0 || tmpMin > 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - int64_t tmpMax = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax); - if (tmpMax < 0 || tmpMax > 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - int64_t tmpDMin = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin); - if (tmpDMin < 0 || tmpDMin > 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - int64_t tmpDMax = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax); - if (tmpDMax < 0 || tmpDMax > 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - if (!AddEventToTimeline(std::move(wind))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Temperature: { - std::unique_ptr temperature = std::make_unique(); - temperature->timestamp = tmpTimestamp; - temperature->eventType = static_cast(tmpEventType); - temperature->expires = tmpExpires; - - int64_t tmpTemperature = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature); - if (tmpTemperature < -32768 || tmpTemperature > 32767) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - temperature->temperature = - static_cast(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - int64_t tmpDewPoint = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint); - if (tmpDewPoint < -32768 || tmpDewPoint > 32767) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - temperature->dewPoint = - static_cast(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - if (!AddEventToTimeline(std::move(temperature))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Special: { - std::unique_ptr special = std::make_unique(); - special->timestamp = tmpTimestamp; - special->eventType = static_cast(tmpEventType); - special->expires = tmpExpires; - - int64_t tmpType = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType); - if (tmpType < 0 || tmpType >= static_cast(WeatherData::specialtype::Length)) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - special->type = static_cast(tmpType); - - if (!AddEventToTimeline(std::move(special))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Pressure: { - std::unique_ptr pressure = std::make_unique(); - pressure->timestamp = tmpTimestamp; - pressure->eventType = static_cast(tmpEventType); - pressure->expires = tmpExpires; - - int64_t tmpPressure = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure); - if (tmpPressure < 0 || tmpPressure >= 65535) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - - if (!AddEventToTimeline(std::move(pressure))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Location: { - std::unique_ptr location = std::make_unique(); - location->timestamp = tmpTimestamp; - location->eventType = static_cast(tmpEventType); - location->expires = tmpExpires; - - UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here? - QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf); - if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - location->location = std::string(static_cast(stringBuf.ptr), stringBuf.len); - - int64_t tmpAltitude = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude); - if (tmpAltitude < -32768 || tmpAltitude >= 32767) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - location->altitude = static_cast(tmpAltitude); - - int64_t tmpLatitude = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude); - if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - location->latitude = static_cast(tmpLatitude); - - int64_t tmpLongitude = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude); - if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - location->latitude = static_cast(tmpLongitude); - - if (!AddEventToTimeline(std::move(location))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Clouds: { - std::unique_ptr clouds = std::make_unique(); - clouds->timestamp = tmpTimestamp; - clouds->eventType = static_cast(tmpEventType); - clouds->expires = tmpExpires; - - int64_t tmpAmount = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); - if (tmpAmount < 0 || tmpAmount > 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - clouds->amount = static_cast(tmpAmount); - - if (!AddEventToTimeline(std::move(clouds))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - case WeatherData::eventtype::Humidity: { - std::unique_ptr humidity = std::make_unique(); - humidity->timestamp = tmpTimestamp; - humidity->eventType = static_cast(tmpEventType); - humidity->expires = tmpExpires; - - int64_t tmpType = 0; - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType); - if (tmpType < 0 || tmpType >= 255) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - humidity->humidity = static_cast(tmpType); - - if (!AddEventToTimeline(std::move(humidity))) { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - break; - } - default: { - CleanUpQcbor(&decodeContext); - return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; - } - } - - QCBORDecode_ExitMap(&decodeContext); - GetTimelineLength(); - TidyTimeline(); - - if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) { - return BLE_ATT_ERR_INSUFFICIENT_RES; - } - } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { - // Encode - uint8_t buffer[64]; - QCBOREncodeContext encodeContext; - /* TODO: This is very much still a test endpoint - * it needs a characteristic UUID check - * and actual implementations that show - * what actually has to be read. - * WARN: Consider commands not part of the API for now! - */ - QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer)); - QCBOREncode_OpenMap(&encodeContext); - QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test")); - QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul); - QCBOREncode_CloseMap(&encodeContext); - - UsefulBufC encodedEvent; - auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent); - if (uErr != 0) { - return BLE_ATT_ERR_INSUFFICIENT_RES; - } - auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer)); - if (res == 0) { - return BLE_ATT_ERR_INSUFFICIENT_RES; - } - - return 0; - } - return 0; - } - - std::unique_ptr& WeatherService::GetCurrentClouds() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Clouds && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentObscuration() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Obscuration && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentPrecipitation() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Precipitation && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentWind() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Wind && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentTemperature() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Temperature && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentHumidity() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Humidity && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentPressure() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Pressure && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentLocation() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Location && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - std::unique_ptr& WeatherService::GetCurrentQuality() { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::AirQuality && currentTimestamp >= header->timestamp && - IsEventStillValid(header, currentTimestamp)) { - return reinterpret_cast&>(header); - } - } - - return reinterpret_cast&>(*this->nullHeader); - } - - size_t WeatherService::GetTimelineLength() const { - return timeline.size(); - } - - bool WeatherService::AddEventToTimeline(std::unique_ptr event) { - if (timeline.size() == timeline.max_size()) { - return false; - } - - timeline.push_back(std::move(event)); - return true; - } - - bool WeatherService::HasTimelineEventOfType(const WeatherData::eventtype type) const { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - for (auto&& header : timeline) { - if (header->eventType == type && IsEventStillValid(header, currentTimestamp)) { - return true; - } - } - return false; - } - - void WeatherService::TidyTimeline() { - uint64_t timeCurrent = GetCurrentUnixTimestamp(); - timeline.erase(std::remove_if(std::begin(timeline), - std::end(timeline), - [&](std::unique_ptr const& header) { - return !IsEventStillValid(header, timeCurrent); - }), - std::end(timeline)); - - std::sort(std::begin(timeline), std::end(timeline), CompareTimelineEvents); - } - - bool WeatherService::CompareTimelineEvents(const std::unique_ptr& first, - const std::unique_ptr& second) { - return first->timestamp > second->timestamp; - } - - bool WeatherService::IsEventStillValid(const std::unique_ptr& uniquePtr, const uint64_t timestamp) { - // Not getting timestamp in isEventStillValid for more speed - return uniquePtr->timestamp + uniquePtr->expires >= timestamp; - } - - uint64_t WeatherService::GetCurrentUnixTimestamp() const { - return std::chrono::duration_cast(dateTimeController.CurrentDateTime().time_since_epoch()).count(); - } - - int16_t WeatherService::GetTodayMinTemp() const { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) + - ((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds()); - uint64_t currentDayStart = currentDayEnd - 86400; - int16_t result = -32768; - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart && - header->timestamp < currentDayEnd && - reinterpret_cast&>(header)->temperature != -32768) { - int16_t temperature = reinterpret_cast&>(header)->temperature; - if (result == -32768) { - result = temperature; - } else if (result > temperature) { - result = temperature; - } else { - // The temperature in this item is higher than the lowest we've found - } - } - } - - return result; - } - - int16_t WeatherService::GetTodayMaxTemp() const { - uint64_t currentTimestamp = GetCurrentUnixTimestamp(); - uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) + - ((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds()); - uint64_t currentDayStart = currentDayEnd - 86400; - int16_t result = -32768; - for (auto&& header : this->timeline) { - if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart && - header->timestamp < currentDayEnd && - reinterpret_cast&>(header)->temperature != -32768) { - int16_t temperature = reinterpret_cast&>(header)->temperature; - if (result == -32768) { - result = temperature; - } else if (result < temperature) { - result = temperature; - } else { - // The temperature in this item is lower than the highest we've found - } - } - } - - return result; - } - - void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) { - QCBORDecode_ExitMap(decodeContext); - QCBORDecode_Finish(decodeContext); - } - } -} diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h deleted file mode 100644 index 609e8760..00000000 --- a/src/components/ble/weather/WeatherService.h +++ /dev/null @@ -1,169 +0,0 @@ -/* Copyright (C) 2021 Avamander - - 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 -#include -#include - -#define min // workaround: nimble's min/max macros conflict with libstdc++ -#define max -#include -#include -#undef max -#undef min - -#include "WeatherData.h" -#include "libs/QCBOR/inc/qcbor/qcbor.h" -#include "components/datetime/DateTimeController.h" - -int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg); - -namespace Pinetime { - namespace Controllers { - - class WeatherService { - public: - explicit WeatherService(const DateTime& dateTimeController); - - void Init(); - - int OnCommand(struct ble_gatt_access_ctxt* ctxt); - - /* - * Helper functions for quick access to currently valid data - */ - std::unique_ptr& GetCurrentLocation(); - std::unique_ptr& GetCurrentClouds(); - std::unique_ptr& GetCurrentObscuration(); - std::unique_ptr& GetCurrentPrecipitation(); - std::unique_ptr& GetCurrentWind(); - std::unique_ptr& GetCurrentTemperature(); - std::unique_ptr& GetCurrentHumidity(); - std::unique_ptr& GetCurrentPressure(); - std::unique_ptr& GetCurrentQuality(); - - /** - * Searches for the current day's maximum temperature - * @return -32768 if there's no data, degrees Celsius times 100 otherwise - */ - int16_t GetTodayMaxTemp() const; - /** - * Searches for the current day's minimum temperature - * @return -32768 if there's no data, degrees Celsius times 100 otherwise - */ - int16_t GetTodayMinTemp() const; - - /* - * Management functions - */ - /** - * Adds an event to the timeline - * @return - */ - bool AddEventToTimeline(std::unique_ptr event); - /** - * Gets the current timeline length - */ - size_t GetTimelineLength() const; - /** - * Checks if an event of a certain type exists in the timeline - */ - bool HasTimelineEventOfType(WeatherData::eventtype type) const; - - private: - // 00040000-78fc-48fe-8e23-433b3a1942d0 - static constexpr ble_uuid128_t BaseUuid() { - return CharUuid(0x00, 0x00); - } - - // 0004yyxx-78fc-48fe-8e23-433b3a1942d0 - static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) { - return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128}, - .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}}; - } - - ble_uuid128_t weatherUuid {BaseUuid()}; - - /** - * Just write timeline data here. - * - * See {@link WeatherData.h} for more information. - */ - ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)}; - /** - * This doesn't take timeline data, provides some control over it. - * - * NOTE: Currently not supported. Companion app implementer feedback required. - * There's very little point in solidifying an API before we know the needs. - */ - ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)}; - - const struct ble_gatt_chr_def characteristicDefinition[3] = { - {.uuid = &weatherDataCharUuid.u, - .access_cb = WeatherCallback, - .arg = this, - .flags = BLE_GATT_CHR_F_WRITE, - .val_handle = &eventHandle}, - {.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ}, - {nullptr}}; - const struct ble_gatt_svc_def serviceDefinition[2] = { - {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition}, - {0}}; - - uint16_t eventHandle {}; - - const Pinetime::Controllers::DateTime& dateTimeController; - - std::vector> timeline; - std::unique_ptr nullTimelineheader = std::make_unique(); - std::unique_ptr* nullHeader; - - /** - * Cleans up the timeline of expired events - */ - void TidyTimeline(); - - /** - * Compares two timeline events - */ - static bool CompareTimelineEvents(const std::unique_ptr& first, - const std::unique_ptr& second); - - /** - * Returns current UNIX timestamp - */ - uint64_t GetCurrentUnixTimestamp() const; - - /** - * Checks if the event hasn't gone past and expired - * - * @param header timeline event to check - * @param currentTimestamp what's the time right now - * @return if the event is valid - */ - static bool IsEventStillValid(const std::unique_ptr& uniquePtr, const uint64_t timestamp); - - /** - * This is a helper function that closes a QCBOR map and decoding context cleanly - */ - void CleanUpQcbor(QCBORDecodeContext* decodeContext); - }; - } -} diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h index df6b2284..9992426c 100644 --- a/src/displayapp/Controllers.h +++ b/src/displayapp/Controllers.h @@ -20,7 +20,7 @@ namespace Pinetime { class MotionController; class AlarmController; class BrightnessController; - class WeatherService; + class SimpleWeatherService; class FS; class Timer; class MusicService; @@ -43,7 +43,7 @@ namespace Pinetime { Pinetime::Controllers::MotionController& motionController; Pinetime::Controllers::AlarmController& alarmController; Pinetime::Controllers::BrightnessController& brightnessController; - Pinetime::Controllers::WeatherService* weatherController; + Pinetime::Controllers::SimpleWeatherService* weatherController; Pinetime::Controllers::FS& filesystem; Pinetime::Controllers::Timer& timer; Pinetime::System::SystemTask* systemTask; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 3b34d7b8..28ce0bab 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -28,7 +28,6 @@ #include "displayapp/screens/Steps.h" #include "displayapp/screens/PassKey.h" #include "displayapp/screens/Error.h" -#include "displayapp/screens/Weather.h" #include "drivers/Cst816s.h" #include "drivers/St7789.h" @@ -607,7 +606,7 @@ void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { this->controllers.systemTask = systemTask; } -void DisplayApp::Register(Pinetime::Controllers::WeatherService* weatherService) { +void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) { this->controllers.weatherController = weatherService; } diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 7dbac850..349ca014 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -39,6 +39,7 @@ namespace Pinetime { class HeartRateController; class MotionController; class TouchHandler; + class SimpleWeatherService; } namespace System { @@ -74,7 +75,7 @@ namespace Pinetime { void SetFullRefresh(FullRefreshDirections direction); void Register(Pinetime::System::SystemTask* systemTask); - void Register(Pinetime::Controllers::WeatherService* weatherService); + void Register(Pinetime::Controllers::SimpleWeatherService* weatherService); void Register(Pinetime::Controllers::MusicService* musicService); void Register(Pinetime::Controllers::NavigationService* NavigationService); diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp index f0359da4..bdb3fde6 100644 --- a/src/displayapp/screens/StopWatch.cpp +++ b/src/displayapp/screens/StopWatch.cpp @@ -5,6 +5,8 @@ using namespace Pinetime::Applications::Screens; +constexpr int Pinetime::Applications::Screens::StopWatch::maxLapCount; + namespace { TimeSeparated_t convertTicksToTimeSegments(const TickType_t timeElapsed) { // Centiseconds diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index 250a745c..65122493 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -33,7 +33,7 @@ #include "components/motion/MotionController.h" #include "components/settings/Settings.h" #include "displayapp/DisplayApp.h" -#include "components/ble/weather/WeatherService.h" +#include "components/ble/SimpleWeatherService.h" using namespace Pinetime::Applications::Screens; @@ -42,6 +42,21 @@ namespace { auto* screen = static_cast(obj->user_data); screen->UpdateSelected(obj, event); } + + const char* GetIcon(const Pinetime::Controllers::SimpleWeatherService::Icons icon) { + switch (icon) { + case Pinetime::Controllers::SimpleWeatherService::Icons::Sun: return Symbols::sun; break; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun: return Symbols::cloudSun; break; + case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds: return Symbols::cloud; break; + case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds: return Symbols::cloud; break; // TODO missing symbol + case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm: return Symbols::cloud; break; // TODO missing symbol + case Pinetime::Controllers::SimpleWeatherService::Icons::Snow: return Symbols::cloud; break; // TODO missing symbol + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy: return Symbols::cloudShowersHeavy; break; + case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain: return Symbols::cloudSunRain; break; + case Pinetime::Controllers::SimpleWeatherService::Icons::Smog: return Symbols::smog; break; + default: return Symbols::ban; break; + } + } } WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeController, @@ -50,7 +65,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, - Controllers::WeatherService& weatherService) + Controllers::SimpleWeatherService& weatherService) : currentDateTime {{}}, batteryIcon(false), dateTimeController {dateTimeController}, @@ -537,29 +552,18 @@ void WatchFacePineTimeStyle::Refresh() { } } - if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 && - weatherService.GetCurrentPrecipitation()->timestamp != 0) { - nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100); - clouds = (weatherService.GetCurrentClouds()->amount); - precip = (weatherService.GetCurrentPrecipitation()->amount); - if (nowTemp.IsUpdated()) { - lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get()); - if ((clouds <= 30) && (precip == 0)) { - lv_label_set_text(weatherIcon, Symbols::sun); - } else if ((clouds >= 70) && (clouds <= 90) && (precip == 1)) { - lv_label_set_text(weatherIcon, Symbols::cloudSunRain); - } else if ((clouds > 90) && (precip == 0)) { - lv_label_set_text(weatherIcon, Symbols::cloud); - } else if ((clouds > 70) && (precip >= 2)) { - lv_label_set_text(weatherIcon, Symbols::cloudShowersHeavy); - } else { - lv_label_set_text(weatherIcon, Symbols::cloudSun); - }; + currentWeather = weatherService.Current(); + + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + lv_label_set_text_fmt(temperature, "%d°", optCurrentWeather->temperature); + lv_label_set_text(weatherIcon, GetIcon(optCurrentWeather->iconId)); lv_obj_realign(temperature); lv_obj_realign(weatherIcon); } } else { - lv_label_set_text_static(temperature, "--"); + lv_label_set_text(temperature, "--"); lv_label_set_text(weatherIcon, Symbols::ban); lv_obj_realign(temperature); lv_obj_realign(weatherIcon); diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.h b/src/displayapp/screens/WatchFacePineTimeStyle.h index dd079fed..72537095 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.h +++ b/src/displayapp/screens/WatchFacePineTimeStyle.h @@ -9,7 +9,7 @@ #include "displayapp/screens/BatteryIcon.h" #include "displayapp/Colors.h" #include "components/datetime/DateTimeController.h" -#include "components/ble/weather/WeatherService.h" +#include "components/ble/SimpleWeatherService.h" #include "components/ble/BleController.h" #include "utility/DirtyValue.h" @@ -33,7 +33,7 @@ namespace Pinetime { Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, - Controllers::WeatherService& weather); + Controllers::SimpleWeatherService& weather); ~WatchFacePineTimeStyle() override; bool OnTouchEvent(TouchEvents event) override; @@ -61,9 +61,7 @@ namespace Pinetime { Utility::DirtyValue> currentDateTime {}; Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; - Utility::DirtyValue nowTemp {}; - int16_t clouds = 0; - int16_t precip = 0; + Utility::DirtyValue> currentWeather {}; static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color); static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color); @@ -114,7 +112,7 @@ namespace Pinetime { Controllers::NotificationManager& notificationManager; Controllers::Settings& settingsController; Controllers::MotionController& motionController; - Controllers::WeatherService& weatherService; + Controllers::SimpleWeatherService& weatherService; void SetBatteryIcon(); void CloseMenu(); diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 4921174c..dfeb1d41 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -17,7 +17,7 @@ */ #include "Weather.h" #include -#include +#include #include "Label.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h index 84177ea6..6b2599c8 100644 --- a/src/displayapp/screens/Weather.h +++ b/src/displayapp/screens/Weather.h @@ -1,7 +1,7 @@ #pragma once #include -#include "components/ble/weather/WeatherService.h" +#include "components/ble/weather/SimpleWeatherService.h" #include "Screen.h" #include "ScreenList.h" #include "displayapp/Apps.h"