aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/ble/SimpleWeatherService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/ble/SimpleWeatherService.cpp')
-rw-r--r--src/components/ble/SimpleWeatherService.cpp173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp
new file mode 100644
index 00000000..51baf543
--- /dev/null
+++ b/src/components/ble/SimpleWeatherService.cpp
@@ -0,0 +1,173 @@
+/* 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 "components/ble/SimpleWeatherService.h"
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <nrf_log.h>
+
+using namespace Pinetime::Controllers;
+
+namespace {
+ enum class MessageType : uint8_t { CurrentWeather, Forecast, Unknown };
+
+ uint64_t ToUInt64(const uint8_t* data) {
+ return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24) + (static_cast<uint64_t>(data[4]) << 32) +
+ (static_cast<uint64_t>(data[5]) << 40) + (static_cast<uint64_t>(data[6]) << 48) + (static_cast<uint64_t>(data[7]) << 56);
+ }
+
+ int16_t ToInt16(const uint8_t* data) {
+ return data[0] + (data[1] << 8);
+ }
+
+ SimpleWeatherService::CurrentWeather CreateCurrentWeather(const uint8_t* dataBuffer) {
+ SimpleWeatherService::Location cityName;
+ std::memcpy(cityName.data(), &dataBuffer[16], 32);
+ cityName[32] = '\0';
+ return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
+ SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])),
+ SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])),
+ SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])),
+ SimpleWeatherService::Icons {dataBuffer[16 + 32]},
+ std::move(cityName));
+ }
+
+ SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
+ auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
+
+ std::array<std::optional<SimpleWeatherService::Forecast::Day>, SimpleWeatherService::MaxNbForecastDays> days;
+ const uint8_t nbDaysInBuffer = dataBuffer[10];
+ const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer);
+ for (int i = 0; i < nbDays; i++) {
+ days[i] = SimpleWeatherService::Forecast::Day {SimpleWeatherService::Temperature(ToInt16(&dataBuffer[11 + (i * 5)])),
+ SimpleWeatherService::Temperature(ToInt16(&dataBuffer[13 + (i * 5)])),
+ SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}};
+ }
+ return SimpleWeatherService::Forecast {timestamp, nbDays, days};
+ }
+
+ MessageType GetMessageType(const uint8_t* data) {
+ auto messageType = static_cast<MessageType>(*data);
+ if (messageType > MessageType::Unknown) {
+ return MessageType::Unknown;
+ }
+ return messageType;
+ }
+
+ 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(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.PreciseCelsius(),
+ currentWeather->minTemperature.PreciseCelsius(),
+ currentWeather->maxTemperature.PreciseCelsius(),
+ currentWeather->iconId,
+ currentWeather->location.data());
+ }
+ 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.PreciseCelsius(),
+ forecast->days[i]->maxTemperature.PreciseCelsius(),
+ forecast->days[i]->iconId);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+std::optional<SimpleWeatherService::CurrentWeather> SimpleWeatherService::Current() const {
+ if (currentWeather) {
+ auto currentTime = dateTimeController.CurrentDateTime().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.CurrentDateTime().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 &&
+ std::strcmp(this->location.data(), other.location.data()) == 0;
+}
+
+bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
+ return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature;
+}
+
+bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {
+ for (int i = 0; i < this->nbDays; i++) {
+ if (this->days[i] != other.days[i]) {
+ return false;
+ }
+ }
+ return this->timestamp == other.timestamp && this->nbDays == other.nbDays;
+}