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.
This commit is contained in:
parent
088082d32d
commit
c94a59e7d3
68
doc/SimpleWeatherService.md
Normal file
68
doc/SimpleWeatherService.md
Normal file
|
@ -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
|
|
@ -92,7 +92,10 @@ The following custom services are implemented in InfiniTime:
|
||||||
|
|
||||||
- Since InfiniTime 1.8:
|
- 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`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -355,14 +355,6 @@ set(LVGL_SRC
|
||||||
libs/lvgl/src/lv_widgets/lv_win.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
|
list(APPEND IMAGE_FILES
|
||||||
displayapp/icons/battery/batteryicon.c
|
displayapp/icons/battery/batteryicon.c
|
||||||
)
|
)
|
||||||
|
@ -384,7 +376,6 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/Label.cpp
|
displayapp/screens/Label.cpp
|
||||||
displayapp/screens/FirmwareUpdate.cpp
|
displayapp/screens/FirmwareUpdate.cpp
|
||||||
displayapp/screens/Music.cpp
|
displayapp/screens/Music.cpp
|
||||||
displayapp/screens/Weather.cpp
|
|
||||||
displayapp/screens/Navigation.cpp
|
displayapp/screens/Navigation.cpp
|
||||||
displayapp/screens/Metronome.cpp
|
displayapp/screens/Metronome.cpp
|
||||||
displayapp/screens/Motion.cpp
|
displayapp/screens/Motion.cpp
|
||||||
|
@ -459,7 +450,7 @@ list(APPEND SOURCE_FILES
|
||||||
components/ble/CurrentTimeService.cpp
|
components/ble/CurrentTimeService.cpp
|
||||||
components/ble/AlertNotificationService.cpp
|
components/ble/AlertNotificationService.cpp
|
||||||
components/ble/MusicService.cpp
|
components/ble/MusicService.cpp
|
||||||
components/ble/weather/WeatherService.cpp
|
components/ble/SimpleWeatherService.cpp
|
||||||
components/ble/NavigationService.cpp
|
components/ble/NavigationService.cpp
|
||||||
components/ble/BatteryInformationService.cpp
|
components/ble/BatteryInformationService.cpp
|
||||||
components/ble/FSService.cpp
|
components/ble/FSService.cpp
|
||||||
|
@ -528,7 +519,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||||
components/ble/CurrentTimeService.cpp
|
components/ble/CurrentTimeService.cpp
|
||||||
components/ble/AlertNotificationService.cpp
|
components/ble/AlertNotificationService.cpp
|
||||||
components/ble/MusicService.cpp
|
components/ble/MusicService.cpp
|
||||||
components/ble/weather/WeatherService.cpp
|
components/ble/SimpleWeatherService.cpp
|
||||||
components/ble/BatteryInformationService.cpp
|
components/ble/BatteryInformationService.cpp
|
||||||
components/ble/FSService.cpp
|
components/ble/FSService.cpp
|
||||||
components/ble/ImmediateAlertService.cpp
|
components/ble/ImmediateAlertService.cpp
|
||||||
|
@ -655,7 +646,7 @@ set(INCLUDE_FILES
|
||||||
components/ble/BleClient.h
|
components/ble/BleClient.h
|
||||||
components/ble/HeartRateService.h
|
components/ble/HeartRateService.h
|
||||||
components/ble/MotionService.h
|
components/ble/MotionService.h
|
||||||
components/ble/weather/WeatherService.h
|
components/ble/SimpleWeatherService.h
|
||||||
components/settings/Settings.h
|
components/settings/Settings.h
|
||||||
components/timer/Timer.h
|
components/timer/Timer.h
|
||||||
components/alarm/AlarmController.h
|
components/alarm/AlarmController.h
|
||||||
|
@ -891,27 +882,6 @@ target_compile_options(lvgl PRIVATE
|
||||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_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
|
# LITTLEFS_SRC
|
||||||
add_library(littlefs STATIC ${LITTLEFS_SRC})
|
add_library(littlefs STATIC ${LITTLEFS_SRC})
|
||||||
target_include_directories(littlefs SYSTEM PUBLIC . ../)
|
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")
|
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
||||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
||||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
|
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
|
target_compile_options(${EXECUTABLE_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
${WARNING_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(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")
|
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
|
||||||
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
|
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})
|
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
|
@ -1006,7 +976,7 @@ endif()
|
||||||
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
||||||
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
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})
|
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})
|
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_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||||
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
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(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)
|
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})
|
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})
|
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_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||||
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
||||||
|
@ -1078,7 +1048,7 @@ endif()
|
||||||
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
|
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
|
||||||
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
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})
|
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})
|
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${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(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)
|
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})
|
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})
|
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#include "components/ble/NavigationService.h"
|
#include "components/ble/NavigationService.h"
|
||||||
#include "components/ble/ServiceDiscovery.h"
|
#include "components/ble/ServiceDiscovery.h"
|
||||||
#include "components/ble/MotionService.h"
|
#include "components/ble/MotionService.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
#include "components/fs/FS.h"
|
#include "components/fs/FS.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
@ -67,7 +67,7 @@ namespace Pinetime {
|
||||||
return anService;
|
return anService;
|
||||||
};
|
};
|
||||||
|
|
||||||
Pinetime::Controllers::WeatherService& weather() {
|
Pinetime::Controllers::SimpleWeatherService& weather() {
|
||||||
return weatherService;
|
return weatherService;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ namespace Pinetime {
|
||||||
AlertNotificationClient alertNotificationClient;
|
AlertNotificationClient alertNotificationClient;
|
||||||
CurrentTimeService currentTimeService;
|
CurrentTimeService currentTimeService;
|
||||||
MusicService musicService;
|
MusicService musicService;
|
||||||
WeatherService weatherService;
|
SimpleWeatherService weatherService;
|
||||||
NavigationService navService;
|
NavigationService navService;
|
||||||
BatteryInformationService batteryInformationService;
|
BatteryInformationService batteryInformationService;
|
||||||
ImmediateAlertService immediateAlertService;
|
ImmediateAlertService immediateAlertService;
|
||||||
|
|
153
src/components/ble/SimpleWeatherService.cpp
Normal file
153
src/components/ble/SimpleWeatherService.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
131
src/components/ble/SimpleWeatherService.h
Normal file
131
src/components/ble/SimpleWeatherService.h
Normal file
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,7 @@ namespace Pinetime {
|
||||||
class MotionController;
|
class MotionController;
|
||||||
class AlarmController;
|
class AlarmController;
|
||||||
class BrightnessController;
|
class BrightnessController;
|
||||||
class WeatherService;
|
class SimpleWeatherService;
|
||||||
class FS;
|
class FS;
|
||||||
class Timer;
|
class Timer;
|
||||||
class MusicService;
|
class MusicService;
|
||||||
|
@ -43,7 +43,7 @@ namespace Pinetime {
|
||||||
Pinetime::Controllers::MotionController& motionController;
|
Pinetime::Controllers::MotionController& motionController;
|
||||||
Pinetime::Controllers::AlarmController& alarmController;
|
Pinetime::Controllers::AlarmController& alarmController;
|
||||||
Pinetime::Controllers::BrightnessController& brightnessController;
|
Pinetime::Controllers::BrightnessController& brightnessController;
|
||||||
Pinetime::Controllers::WeatherService* weatherController;
|
Pinetime::Controllers::SimpleWeatherService* weatherController;
|
||||||
Pinetime::Controllers::FS& filesystem;
|
Pinetime::Controllers::FS& filesystem;
|
||||||
Pinetime::Controllers::Timer& timer;
|
Pinetime::Controllers::Timer& timer;
|
||||||
Pinetime::System::SystemTask* systemTask;
|
Pinetime::System::SystemTask* systemTask;
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include "displayapp/screens/Steps.h"
|
#include "displayapp/screens/Steps.h"
|
||||||
#include "displayapp/screens/PassKey.h"
|
#include "displayapp/screens/PassKey.h"
|
||||||
#include "displayapp/screens/Error.h"
|
#include "displayapp/screens/Error.h"
|
||||||
#include "displayapp/screens/Weather.h"
|
|
||||||
|
|
||||||
#include "drivers/Cst816s.h"
|
#include "drivers/Cst816s.h"
|
||||||
#include "drivers/St7789.h"
|
#include "drivers/St7789.h"
|
||||||
|
@ -607,7 +606,7 @@ void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
|
||||||
this->controllers.systemTask = systemTask;
|
this->controllers.systemTask = systemTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayApp::Register(Pinetime::Controllers::WeatherService* weatherService) {
|
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) {
|
||||||
this->controllers.weatherController = weatherService;
|
this->controllers.weatherController = weatherService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ namespace Pinetime {
|
||||||
class HeartRateController;
|
class HeartRateController;
|
||||||
class MotionController;
|
class MotionController;
|
||||||
class TouchHandler;
|
class TouchHandler;
|
||||||
|
class SimpleWeatherService;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace System {
|
namespace System {
|
||||||
|
@ -74,7 +75,7 @@ namespace Pinetime {
|
||||||
void SetFullRefresh(FullRefreshDirections direction);
|
void SetFullRefresh(FullRefreshDirections direction);
|
||||||
|
|
||||||
void Register(Pinetime::System::SystemTask* systemTask);
|
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::MusicService* musicService);
|
||||||
void Register(Pinetime::Controllers::NavigationService* NavigationService);
|
void Register(Pinetime::Controllers::NavigationService* NavigationService);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
constexpr int Pinetime::Applications::Screens::StopWatch::maxLapCount;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
TimeSeparated_t convertTicksToTimeSegments(const TickType_t timeElapsed) {
|
TimeSeparated_t convertTicksToTimeSegments(const TickType_t timeElapsed) {
|
||||||
// Centiseconds
|
// Centiseconds
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
#include "components/motion/MotionController.h"
|
#include "components/motion/MotionController.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "components/settings/Settings.h"
|
||||||
#include "displayapp/DisplayApp.h"
|
#include "displayapp/DisplayApp.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
@ -42,6 +42,21 @@ namespace {
|
||||||
auto* screen = static_cast<WatchFacePineTimeStyle*>(obj->user_data);
|
auto* screen = static_cast<WatchFacePineTimeStyle*>(obj->user_data);
|
||||||
screen->UpdateSelected(obj, event);
|
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,
|
WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeController,
|
||||||
|
@ -50,7 +65,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
||||||
Controllers::NotificationManager& notificationManager,
|
Controllers::NotificationManager& notificationManager,
|
||||||
Controllers::Settings& settingsController,
|
Controllers::Settings& settingsController,
|
||||||
Controllers::MotionController& motionController,
|
Controllers::MotionController& motionController,
|
||||||
Controllers::WeatherService& weatherService)
|
Controllers::SimpleWeatherService& weatherService)
|
||||||
: currentDateTime {{}},
|
: currentDateTime {{}},
|
||||||
batteryIcon(false),
|
batteryIcon(false),
|
||||||
dateTimeController {dateTimeController},
|
dateTimeController {dateTimeController},
|
||||||
|
@ -537,29 +552,18 @@ void WatchFacePineTimeStyle::Refresh() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 &&
|
currentWeather = weatherService.Current();
|
||||||
weatherService.GetCurrentPrecipitation()->timestamp != 0) {
|
|
||||||
nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100);
|
if (currentWeather.IsUpdated()) {
|
||||||
clouds = (weatherService.GetCurrentClouds()->amount);
|
auto optCurrentWeather = currentWeather.Get();
|
||||||
precip = (weatherService.GetCurrentPrecipitation()->amount);
|
if (optCurrentWeather) {
|
||||||
if (nowTemp.IsUpdated()) {
|
lv_label_set_text_fmt(temperature, "%d°", optCurrentWeather->temperature);
|
||||||
lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get());
|
lv_label_set_text(weatherIcon, GetIcon(optCurrentWeather->iconId));
|
||||||
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);
|
|
||||||
};
|
|
||||||
lv_obj_realign(temperature);
|
lv_obj_realign(temperature);
|
||||||
lv_obj_realign(weatherIcon);
|
lv_obj_realign(weatherIcon);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lv_label_set_text_static(temperature, "--");
|
lv_label_set_text(temperature, "--");
|
||||||
lv_label_set_text(weatherIcon, Symbols::ban);
|
lv_label_set_text(weatherIcon, Symbols::ban);
|
||||||
lv_obj_realign(temperature);
|
lv_obj_realign(temperature);
|
||||||
lv_obj_realign(weatherIcon);
|
lv_obj_realign(weatherIcon);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include "displayapp/screens/BatteryIcon.h"
|
#include "displayapp/screens/BatteryIcon.h"
|
||||||
#include "displayapp/Colors.h"
|
#include "displayapp/Colors.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "utility/DirtyValue.h"
|
#include "utility/DirtyValue.h"
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ namespace Pinetime {
|
||||||
Controllers::NotificationManager& notificationManager,
|
Controllers::NotificationManager& notificationManager,
|
||||||
Controllers::Settings& settingsController,
|
Controllers::Settings& settingsController,
|
||||||
Controllers::MotionController& motionController,
|
Controllers::MotionController& motionController,
|
||||||
Controllers::WeatherService& weather);
|
Controllers::SimpleWeatherService& weather);
|
||||||
~WatchFacePineTimeStyle() override;
|
~WatchFacePineTimeStyle() override;
|
||||||
|
|
||||||
bool OnTouchEvent(TouchEvents event) override;
|
bool OnTouchEvent(TouchEvents event) override;
|
||||||
|
@ -61,9 +61,7 @@ namespace Pinetime {
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
||||||
Utility::DirtyValue<uint32_t> stepCount {};
|
Utility::DirtyValue<uint32_t> stepCount {};
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
Utility::DirtyValue<int16_t> nowTemp {};
|
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
|
||||||
int16_t clouds = 0;
|
|
||||||
int16_t precip = 0;
|
|
||||||
|
|
||||||
static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color);
|
static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color);
|
||||||
static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color);
|
static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color);
|
||||||
|
@ -114,7 +112,7 @@ namespace Pinetime {
|
||||||
Controllers::NotificationManager& notificationManager;
|
Controllers::NotificationManager& notificationManager;
|
||||||
Controllers::Settings& settingsController;
|
Controllers::Settings& settingsController;
|
||||||
Controllers::MotionController& motionController;
|
Controllers::MotionController& motionController;
|
||||||
Controllers::WeatherService& weatherService;
|
Controllers::SimpleWeatherService& weatherService;
|
||||||
|
|
||||||
void SetBatteryIcon();
|
void SetBatteryIcon();
|
||||||
void CloseMenu();
|
void CloseMenu();
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
#include "Weather.h"
|
#include "Weather.h"
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <components/ble/weather/WeatherService.h>
|
#include <components/ble/weather/SimpleWeatherService.h>
|
||||||
#include "Label.h"
|
#include "Label.h"
|
||||||
#include "components/battery/BatteryController.h"
|
#include "components/battery/BatteryController.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/weather/SimpleWeatherService.h"
|
||||||
#include "Screen.h"
|
#include "Screen.h"
|
||||||
#include "ScreenList.h"
|
#include "ScreenList.h"
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/Apps.h"
|
||||||
|
|
Loading…
Reference in a new issue