InfiniTime.git

commit c94a59e7d3e0f9929171263412033a56872c168a

Author: Jean-François Milants <jf@codingfield.com>

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 
 src/displayapp/screens/WatchFacePineTimeStyle.cpp | 46 
 src/displayapp/screens/WatchFacePineTimeStyle.h | 10 
 src/displayapp/screens/Weather.cpp | 2 
 src/displayapp/screens/Weather.h | 2 


diff --git a/doc/SimpleWeatherService.md b/doc/SimpleWeatherService.md
new file mode 100644
index 0000000000000000000000000000000000000000..c510e4288c25ded058bba38edc2f8ed8b4320be4
--- /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 9c170418caa096a3bd1a45f8e9e298c73b5390cc..724344087f26a7e36994e4aa54bbb91e6197eafc 100644
--- a/doc/ble.md
+++ b/doc/ble.md
@@ -92,7 +92,10 @@   - [Motion Service](MotionService.md): `00030000-78fc-48fe-8e23-433b3a1942d0`
 
 - 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 9a993e0281ede3a9f20c714a15c6c186f89c1237..bb7b90c006f07e1c356b8f85e28508d6e5282d48 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -355,14 +355,6 @@         libs/lvgl/src/lv_widgets/lv_tileview.c
         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 @@         displayapp/screens/SystemInfo.cpp
         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 @@         components/ble/DfuService.cpp
         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 @@         components/ble/DfuService.cpp
         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 @@         components/ble/ServiceDiscovery.h
         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 @@         $<$: ${CXX_FLAGS}>
         $<$<COMPILE_LANGUAGE:ASM>: ${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}
-        $<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
-        $<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
-        $<$<COMPILE_LANGUAGE:ASM>: ${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_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 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_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
 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 @@ # InfiniTime recovery firmware (autonomous)
 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-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 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 @@ # Build binary that writes the recovery image into the SPI flash memory
 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_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 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 8f1dfed7cda83e8ad6e6de5f62c4addf81857b77..29a395ea51f540d1b95b220a0ace02da1c501947 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -21,7 +21,7 @@ #include "components/ble/MusicService.h"
 #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 @@       Pinetime::Controllers::AlertNotificationService& alertService() {
         return anService;
       };
 
-      Pinetime::Controllers::WeatherService& weather() {
+      Pinetime::Controllers::SimpleWeatherService& weather() {
         return weatherService;
       };
 
@@ -99,7 +99,7 @@       AlertNotificationService anService;
       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 0000000000000000000000000000000000000000..fa0ce1987a28e1bebf16f8c0a8bf77adb1b84dc2
--- /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 <https://www.gnu.org/licenses/>.
+*/
+#include <algorithm>
+#include "SimpleWeatherService.h"
+#include <cstring>
+#include <nrf_log.h>
+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<uint64_t>(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<SimpleWeatherService::Forecast::Day, 5> 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<Pinetime::Controllers::SimpleWeatherService*>(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::CurrentWeather> 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<std::chrono::seconds>(weatherTpSecond);
+    auto delta = currentTime - weatherTp;
+
+    if(delta < std::chrono::hours{24}) {
+      return currentWeather;
+    }
+  }
+  return {};
+}
+
+std::optional<SimpleWeatherService::Forecast> 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<std::chrono::seconds>(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 0000000000000000000000000000000000000000..46d5e187f331e9aca34794805bb1914195d42e05
--- /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 <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <memory>
+
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#include <host/ble_uuid.h>
+#include <optional>
+#include <cstring>
+#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<Day, 5> days;
+      };
+
+      std::optional<CurrentWeather> Current() const;
+      std::optional<Forecast> 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> currentWeather;
+      std::optional<Forecast> forecast;
+    };
+  }
+}




diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h
deleted file mode 100644
index 1a995eb96f4c589ec37ec570d713b1ca83c49444..0000000000000000000000000000000000000000
--- 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 <https://www.gnu.org/licenses/>.
-*/
-#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 b9a6af556c1e4757dc4b05661e9b40100824b261..0000000000000000000000000000000000000000
--- 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 <https://www.gnu.org/licenses/>.
-*/
-#include <algorithm>
-#include <qcbor/qcbor_spiffy_decode.h>
-#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<Pinetime::Controllers::WeatherService*>(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<int64_t>(WeatherData::eventtype::Length)) {
-          CleanUpQcbor(&decodeContext);
-          return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-        }
-
-        switch (static_cast<WeatherData::eventtype>(tmpEventType)) {
-          case WeatherData::eventtype::AirQuality: {
-            std::unique_ptr<WeatherData::AirQuality> airquality = std::make_unique<WeatherData::AirQuality>();
-            airquality->timestamp = tmpTimestamp;
-            airquality->eventType = static_cast<WeatherData::eventtype>(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<const char*>(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<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>();
-            obscuration->timestamp = tmpTimestamp;
-            obscuration->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            obscuration->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
-            if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::obscurationtype::Length)) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            obscuration->type = static_cast<WeatherData::obscurationtype>(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<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>();
-            precipitation->timestamp = tmpTimestamp;
-            precipitation->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            precipitation->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
-            if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::precipitationtype::Length)) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            precipitation->type = static_cast<WeatherData::precipitationtype>(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<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>();
-            wind->timestamp = tmpTimestamp;
-            wind->eventType = static_cast<WeatherData::eventtype>(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<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>();
-            temperature->timestamp = tmpTimestamp;
-            temperature->eventType = static_cast<WeatherData::eventtype>(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<int16_t>(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<int16_t>(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<WeatherData::Special> special = std::make_unique<WeatherData::Special>();
-            special->timestamp = tmpTimestamp;
-            special->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            special->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
-            if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            special->type = static_cast<WeatherData::specialtype>(tmpType);
-
-            if (!AddEventToTimeline(std::move(special))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Pressure: {
-            std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>();
-            pressure->timestamp = tmpTimestamp;
-            pressure->eventType = static_cast<WeatherData::eventtype>(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<WeatherData::Location> location = std::make_unique<WeatherData::Location>();
-            location->timestamp = tmpTimestamp;
-            location->eventType = static_cast<WeatherData::eventtype>(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<const char*>(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<int16_t>(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<int32_t>(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<int32_t>(tmpLongitude);
-
-            if (!AddEventToTimeline(std::move(location))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Clouds: {
-            std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>();
-            clouds->timestamp = tmpTimestamp;
-            clouds->eventType = static_cast<WeatherData::eventtype>(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<uint8_t>(tmpAmount);
-
-            if (!AddEventToTimeline(std::move(clouds))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Humidity: {
-            std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>();
-            humidity->timestamp = tmpTimestamp;
-            humidity->eventType = static_cast<WeatherData::eventtype>(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<uint8_t>(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<WeatherData::Clouds>& 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<std::unique_ptr<WeatherData::Clouds>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Obscuration>& 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<std::unique_ptr<WeatherData::Obscuration>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Precipitation>& 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<std::unique_ptr<WeatherData::Precipitation>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Wind>& 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<std::unique_ptr<WeatherData::Wind>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Temperature>& 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<std::unique_ptr<WeatherData::Temperature>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Humidity>& 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<std::unique_ptr<WeatherData::Humidity>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Pressure>& 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<std::unique_ptr<WeatherData::Pressure>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Location>& 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<std::unique_ptr<WeatherData::Location>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::AirQuality>& 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<std::unique_ptr<WeatherData::AirQuality>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(*this->nullHeader);
-    }
-
-    size_t WeatherService::GetTimelineLength() const {
-      return timeline.size();
-    }
-
-    bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> 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<WeatherData::TimelineHeader> 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<WeatherData::TimelineHeader>& first,
-                                               const std::unique_ptr<WeatherData::TimelineHeader>& second) {
-      return first->timestamp > second->timestamp;
-    }
-
-    bool WeatherService::IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& 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<std::chrono::seconds>(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<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
-          int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(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<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
-          int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(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 609e8760d60021c564a9489430f67191272e5380..0000000000000000000000000000000000000000
--- 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 <https://www.gnu.org/licenses/>.
-*/
-#pragma once
-
-#include <cstdint>
-#include <string>
-#include <vector>
-#include <memory>
-
-#define min // workaround: nimble's min/max macros conflict with libstdc++
-#define max
-#include <host/ble_gap.h>
-#include <host/ble_uuid.h>
-#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<WeatherData::Location>& GetCurrentLocation();
-      std::unique_ptr<WeatherData::Clouds>& GetCurrentClouds();
-      std::unique_ptr<WeatherData::Obscuration>& GetCurrentObscuration();
-      std::unique_ptr<WeatherData::Precipitation>& GetCurrentPrecipitation();
-      std::unique_ptr<WeatherData::Wind>& GetCurrentWind();
-      std::unique_ptr<WeatherData::Temperature>& GetCurrentTemperature();
-      std::unique_ptr<WeatherData::Humidity>& GetCurrentHumidity();
-      std::unique_ptr<WeatherData::Pressure>& GetCurrentPressure();
-      std::unique_ptr<WeatherData::AirQuality>& 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<WeatherData::TimelineHeader> 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<std::unique_ptr<WeatherData::TimelineHeader>> timeline;
-      std::unique_ptr<WeatherData::TimelineHeader> nullTimelineheader = std::make_unique<WeatherData::TimelineHeader>();
-      std::unique_ptr<WeatherData::TimelineHeader>* nullHeader;
-
-      /**
-       * Cleans up the timeline of expired events
-       */
-      void TidyTimeline();
-
-      /**
-       * Compares two timeline events
-       */
-      static bool CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
-                                        const std::unique_ptr<WeatherData::TimelineHeader>& 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<WeatherData::TimelineHeader>& 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 df6b2284b4cc81e14d53105e571853641a345be3..9992426c5dbb5d4aa6f5bd282dc8a985c0fe9212 100644
--- a/src/displayapp/Controllers.h
+++ b/src/displayapp/Controllers.h
@@ -20,7 +20,7 @@     class MotorController;
     class MotionController;
     class AlarmController;
     class BrightnessController;
-    class WeatherService;
+    class SimpleWeatherService;
     class FS;
     class Timer;
     class MusicService;
@@ -43,7 +43,7 @@       Pinetime::Controllers::MotorController& motorController;
       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 3b34d7b8730ef358d0ba54cf4963c6caaded14d7..28ce0babd74d35c992402de40a7d12bd4a718be9 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -28,7 +28,6 @@ #include "displayapp/screens/BatteryInfo.h"
 #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 @@   this->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 7dbac850aaeeab162082e7ca7cc2a330617a923a..349ca0142437af14353cdb87bd1f3aaab32a718e 100644
--- a/src/displayapp/DisplayApp.h
+++ b/src/displayapp/DisplayApp.h
@@ -39,6 +39,7 @@     class NotificationManager;
     class HeartRateController;
     class MotionController;
     class TouchHandler;
+    class SimpleWeatherService;
   }
 
   namespace System {
@@ -74,7 +75,7 @@
       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 f0359da4d468003dc4b072268c7674aeed7c06a6..bdb3fde697da3bc18e04e6469569b79a7dd2f407 100644
--- a/src/displayapp/screens/StopWatch.cpp
+++ b/src/displayapp/screens/StopWatch.cpp
@@ -5,6 +5,8 @@ #include "displayapp/InfiniTimeTheme.h"
 
 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 250a745c1551f4cb64dbb47dd595ebf95ff919c8..6512249341ffe744d7fb36e5b4e898c1952cc373 100644
--- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp
+++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp
@@ -33,7 +33,7 @@ #include "components/ble/NotificationManager.h"
 #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 @@   void event_handler(lv_obj_t* obj, lv_event_t event) {
     auto* screen = static_cast<WatchFacePineTimeStyle*>(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 @@                                                const Controllers::Ble& bleController,
                                                Controllers::NotificationManager& notificationManager,
                                                Controllers::Settings& settingsController,
                                                Controllers::MotionController& motionController,
-                                               Controllers::WeatherService& weatherService)
+                                               Controllers::SimpleWeatherService& weatherService)
   : currentDateTime {{}},
     batteryIcon(false),
     dateTimeController {dateTimeController},
@@ -537,29 +552,18 @@       lv_obj_set_style_local_scale_grad_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
     }
   }
 
-  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 dd079fed8bfef99b767dd0a104f43a00b25145bd..72537095f9a5fb11cc48c5ee0f2b55e01ac7510d 100644
--- a/src/displayapp/screens/WatchFacePineTimeStyle.h
+++ b/src/displayapp/screens/WatchFacePineTimeStyle.h
@@ -9,7 +9,7 @@ #include "displayapp/screens/Screen.h"
 #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 @@                                const Controllers::Ble& bleController,
                                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 @@         Utility::DirtyValue bleRadioEnabled {};
         Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
         Utility::DirtyValue<uint32_t> stepCount {};
         Utility::DirtyValue<bool> notificationState {};
-        Utility::DirtyValue<int16_t> nowTemp {};
-        int16_t clouds = 0;
-        int16_t precip = 0;
+        Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> 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 @@         const Controllers::Ble& bleController;
         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 4921174c7a840a28407cd612676ba5081f634207..dfeb1d413ff9b02fdc663a6afaad057ac81298d0 100644
--- a/src/displayapp/screens/Weather.cpp
+++ b/src/displayapp/screens/Weather.cpp
@@ -17,7 +17,7 @@     along with this program.  If not, see .
 */
 #include "Weather.h"
 #include <lvgl/lvgl.h>
-#include <components/ble/weather/WeatherService.h>
+#include <components/ble/weather/SimpleWeatherService.h>
 #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 84177ea6fd37b74813ad73b3fce01041f7d1de4a..6b2599c816ce574a557006f643a86e7da5a533fb 100644
--- a/src/displayapp/screens/Weather.h
+++ b/src/displayapp/screens/Weather.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <memory>
-#include "components/ble/weather/WeatherService.h"
+#include "components/ble/weather/SimpleWeatherService.h"
 #include "Screen.h"
 #include "ScreenList.h"
 #include "displayapp/Apps.h"