From 38092fcb40695098702163ab64a06787b2dc2499 Mon Sep 17 00:00:00 2001 From: uli Date: Sat, 28 May 2022 14:33:04 +0200 Subject: [PATCH] add CTS local time characteristic and use it to provide UTC in DateTimeController --- src/components/ble/CurrentTimeClient.cpp | 20 +--- src/components/ble/CurrentTimeClient.h | 10 +- src/components/ble/CurrentTimeService.cpp | 97 +++++++++++++------ src/components/ble/CurrentTimeService.h | 27 ++++-- .../datetime/DateTimeController.cpp | 6 ++ src/components/datetime/DateTimeController.h | 53 ++++++++++ 6 files changed, 159 insertions(+), 54 deletions(-) diff --git a/src/components/ble/CurrentTimeClient.cpp b/src/components/ble/CurrentTimeClient.cpp index 53e98cb6..96134526 100644 --- a/src/components/ble/CurrentTimeClient.cpp +++ b/src/components/ble/CurrentTimeClient.cpp @@ -85,21 +85,11 @@ int CurrentTimeClient::OnCurrentTimeReadResult(uint16_t conn_handle, const ble_g // TODO check that attribute->handle equals the handle discovered in OnCharacteristicDiscoveryEvent CtsData result; os_mbuf_copydata(attribute->om, 0, sizeof(CtsData), &result); - NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", - result.year, - result.month, - result.dayofmonth, - result.hour, - result.minute, - result.second); - dateTimeController.SetTime(result.year, - result.month, - result.dayofmonth, - 0, - result.hour, - result.minute, - result.second, - nrf_rtc_counter_get(portNRF_RTC_REG)); + uint16_t year = ((uint16_t) result.year_MSO << 8) + result.year_LSO; + + NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", year, result.month, result.dayofmonth, result.hour, result.minute, result.second); + dateTimeController + .SetTime(year, result.month, result.dayofmonth, 0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG)); } else { NRF_LOG_INFO("Error retrieving current time: %d", error->status); } diff --git a/src/components/ble/CurrentTimeClient.h b/src/components/ble/CurrentTimeClient.h index 9e48be79..c718d2d9 100644 --- a/src/components/ble/CurrentTimeClient.h +++ b/src/components/ble/CurrentTimeClient.h @@ -29,14 +29,16 @@ namespace Pinetime { private: typedef struct __attribute__((packed)) { - uint16_t year; + uint8_t year_LSO; // explicit byte ordering to be independent of machine order + uint8_t year_MSO; // BLE GATT is little endian uint8_t month; uint8_t dayofmonth; uint8_t hour; uint8_t minute; uint8_t second; - uint8_t millis; - uint8_t reason; + uint8_t dayofweek; + uint8_t fractions256; // currently ignored + uint8_t reason; // currently ignored, not that any host would set it anyway } CtsData; static constexpr uint16_t ctsServiceId {0x1805}; @@ -55,4 +57,4 @@ namespace Pinetime { std::function onServiceDiscovered; }; } -} \ No newline at end of file +} diff --git a/src/components/ble/CurrentTimeService.cpp b/src/components/ble/CurrentTimeService.cpp index 8430d1bc..a264efba 100644 --- a/src/components/ble/CurrentTimeService.cpp +++ b/src/components/ble/CurrentTimeService.cpp @@ -5,11 +5,23 @@ using namespace Pinetime::Controllers; constexpr ble_uuid16_t CurrentTimeService::ctsUuid; -constexpr ble_uuid16_t CurrentTimeService::ctChrUuid; +constexpr ble_uuid16_t CurrentTimeService::ctsCtChrUuid; +constexpr ble_uuid16_t CurrentTimeService::ctsLtChrUuid; int CTSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) { auto cts = static_cast(arg); - return cts->OnTimeAccessed(conn_handle, attr_handle, ctxt); + + return cts->OnCurrentTimeServiceAccessed(conn_handle, attr_handle, ctxt); +} + +int CurrentTimeService::OnCurrentTimeServiceAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { + switch (ble_uuid_u16(ctxt->chr->uuid)) { + case ctsCurrentTimeCharId: + return OnCurrentTimeAccessed(conn_handle, attr_handle, ctxt); + case ctsLocalTimeCharId: + return OnLocalTimeAccessed(conn_handle, attr_handle, ctxt); + } + return -1; // Unknown characteristic } void CurrentTimeService::Init() { @@ -18,45 +30,69 @@ void CurrentTimeService::Init() { ASSERT(res == 0); res = ble_gatts_add_svcs(serviceDefinition); + ASSERT(res == 0); } -int CurrentTimeService::OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { +int CurrentTimeService::OnCurrentTimeAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { NRF_LOG_INFO("Setting time..."); if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { - CtsData result; - os_mbuf_copydata(ctxt->om, 0, sizeof(CtsData), &result); + CtsCurrentTimeData result; + int res = os_mbuf_copydata(ctxt->om, 0, sizeof(CtsCurrentTimeData), &result); + if (res < 0) { + NRF_LOG_ERROR("Error reading BLE Data writing to CTS Current Time (too little data)") + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } - NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", - result.year, - result.month, - result.dayofmonth, - result.hour, - result.minute, - result.second); + uint16_t year = ((uint16_t) result.year_MSO << 8) + result.year_LSO; - m_dateTimeController.SetTime(result.year, - result.month, - result.dayofmonth, - 0, - result.hour, - result.minute, - result.second, - nrf_rtc_counter_get(portNRF_RTC_REG)); + NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", year, result.month, result.dayofmonth, result.hour, result.minute, result.second); + + m_dateTimeController + .SetTime(year, result.month, result.dayofmonth, 0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG)); } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { - CtsData currentDateTime; - currentDateTime.year = m_dateTimeController.Year(); + CtsCurrentTimeData currentDateTime; + currentDateTime.year_LSO = m_dateTimeController.Year() & 0xff; + currentDateTime.year_MSO = (m_dateTimeController.Year() >> 8) & 0xff; currentDateTime.month = static_cast(m_dateTimeController.Month()); currentDateTime.dayofmonth = m_dateTimeController.Day(); currentDateTime.hour = m_dateTimeController.Hours(); currentDateTime.minute = m_dateTimeController.Minutes(); currentDateTime.second = m_dateTimeController.Seconds(); - currentDateTime.millis = 0; + currentDateTime.fractions256 = 0; - int res = os_mbuf_append(ctxt->om, ¤tDateTime, sizeof(CtsData)); + int res = os_mbuf_append(ctxt->om, ¤tDateTime, sizeof(CtsCurrentTimeData)); + return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + return 0; +} + +int CurrentTimeService::OnLocalTimeAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { + NRF_LOG_INFO("Setting timezone..."); + + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + CtsLocalTimeData result; + int res = os_mbuf_copydata(ctxt->om, 0, sizeof(CtsLocalTimeData), &result); + + if (res < 0) { + NRF_LOG_ERROR("Error reading BLE Data writing to CTS Local Time (too little data)") + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + NRF_LOG_INFO("Received data: %d %d", result.timezone, result.dst); + + m_dateTimeController.SetTimeZone(result.timezone, result.dst); + + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + CtsLocalTimeData currentTimezone; + currentTimezone.timezone = m_dateTimeController.TzOffset(); + currentTimezone.dst = m_dateTimeController.DstOffset(); + + int res = os_mbuf_append(ctxt->om, ¤tTimezone, sizeof(currentTimezone)); return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } @@ -64,12 +100,19 @@ int CurrentTimeService::OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handl } CurrentTimeService::CurrentTimeService(DateTime& dateTimeController) - : characteristicDefinition {{.uuid = &ctChrUuid.u, - .access_cb = CTSCallback, + : characteristicDefinition { + {.uuid = &ctsLtChrUuid.u, + .access_cb = CTSCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ}, - {0}}, + + {.uuid = &ctsCtChrUuid.u, + .access_cb = CTSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ}, + + {0}}, serviceDefinition { {/* Device Information Service */ .type = BLE_GATT_SVC_TYPE_PRIMARY, diff --git a/src/components/ble/CurrentTimeService.h b/src/components/ble/CurrentTimeService.h index ca87d970..91544314 100644 --- a/src/components/ble/CurrentTimeService.h +++ b/src/components/ble/CurrentTimeService.h @@ -16,29 +16,40 @@ namespace Pinetime { CurrentTimeService(DateTime& dateTimeController); void Init(); - int OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt); + int OnCurrentTimeServiceAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt); + int OnCurrentTimeAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt); + int OnLocalTimeAccessed(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt); private: static constexpr uint16_t ctsId {0x1805}; - static constexpr uint16_t ctsCharId {0x2a2b}; + static constexpr uint16_t ctsCurrentTimeCharId {0x2a2b}; + static constexpr uint16_t ctsLocalTimeCharId {0x2a0f}; static constexpr ble_uuid16_t ctsUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ctsId}; - static constexpr ble_uuid16_t ctChrUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ctsCharId}; + static constexpr ble_uuid16_t ctsCtChrUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ctsCurrentTimeCharId}; + static constexpr ble_uuid16_t ctsLtChrUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ctsLocalTimeCharId}; - struct ble_gatt_chr_def characteristicDefinition[2]; + struct ble_gatt_chr_def characteristicDefinition[3]; struct ble_gatt_svc_def serviceDefinition[2]; typedef struct __attribute__((packed)) { - uint16_t year; + uint8_t year_LSO; // BLE GATT is little endian + uint8_t year_MSO; uint8_t month; uint8_t dayofmonth; uint8_t hour; uint8_t minute; uint8_t second; - uint8_t millis; - uint8_t reason; - } CtsData; + uint8_t dayofweek; + uint8_t fractions256; // currently ignored + uint8_t reason; // currently ignored, not that any host would set it anyway + } CtsCurrentTimeData; + + typedef struct __attribute__((packed)) { + uint8_t timezone; + uint8_t dst; + } CtsLocalTimeData; DateTime& m_dateTimeController; }; diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp index 4dc16329..8aab49ec 100644 --- a/src/components/datetime/DateTimeController.cpp +++ b/src/components/datetime/DateTimeController.cpp @@ -36,6 +36,7 @@ void DateTime::SetTime(uint16_t year, /* .tm_mon = */ month - 1, /* .tm_year = */ year - 1900, }; + tm.tm_isdst = -1; // Use DST value from local time zone currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm)); @@ -50,6 +51,11 @@ void DateTime::SetTime(uint16_t year, systemTask->PushMessage(System::Messages::OnNewTime); } +void DateTime::SetTimeZone(uint8_t timezone, uint8_t dst) { + tzOffset = timezone; + dstOffset = dst; +} + void DateTime::UpdateTime(uint32_t systickCounter) { // Handle systick counter overflow uint32_t systickDelta = 0; diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h index 81319d15..681bafbf 100644 --- a/src/components/datetime/DateTimeController.h +++ b/src/components/datetime/DateTimeController.h @@ -38,6 +38,18 @@ namespace Pinetime { uint8_t minute, uint8_t second, uint32_t systickCounter); + + /* + * setter corresponding to the BLE Set Local Time characteristic. + * + * used to update difference between utc and local time (see UtcOffset()) + * + * parameters are in quarters of an our. Following the BLE CTS specification, + * timezone is expected to be constant over DST which will be reported in + * dst field. + */ + void SetTimeZone(uint8_t timezone, uint8_t dst); + void UpdateTime(uint32_t systickCounter); uint16_t Year() const { return year; @@ -61,6 +73,42 @@ namespace Pinetime { return second; } + /* + * returns the offset between local time and UTC in quarters of an hour + * + * Availability of this field depends on wether the companion app + * supports the BLE CTS Local Time Characteristic. Expect it to be 0 + * if not. + */ + uint8_t UtcOffset() const { + return tzOffset + dstOffset; + } + + /* + * returns the offset between the (dst independent) local time zone and UTC + * in quarters of an hour + * + * Availability of this field depends on wether the companion app + * supports the BLE CTS Local Time Characteristic. Expect it to be 0 + * if not. + */ + uint8_t TzOffset() const { + return tzOffset; + } + + /* + * returns the offset between the local time zone and local time + * in quarters of an hour + * if != 0, DST is in effect, if == 0 not. + * + * Availability of this field depends on wether the companion app + * supports the BLE CTS Local Time Characteristic. Expect it to be 0 + * if not. + */ + uint8_t DstOffset() const { + return dstOffset; + } + const char* MonthShortToString() const; const char* DayOfWeekShortToString() const; static const char* MonthShortToStringLow(Months month); @@ -69,6 +117,9 @@ namespace Pinetime { std::chrono::time_point CurrentDateTime() const { return currentDateTime; } + std::chrono::time_point UTCDateTime() const { + return currentDateTime - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60); + } std::chrono::seconds Uptime() const { return uptime; } @@ -85,6 +136,8 @@ namespace Pinetime { uint8_t hour = 0; uint8_t minute = 0; uint8_t second = 0; + uint8_t tzOffset = 0; + uint8_t dstOffset = 0; uint32_t previousSystickCounter = 0; std::chrono::time_point currentDateTime;