Merge branch 'main' into wb/wadokei-merge

This commit is contained in:
2024-12-17 23:25:20 -08:00
110 changed files with 1366 additions and 554 deletions
+88 -13
View File
@@ -19,11 +19,13 @@
#include "systemtask/SystemTask.h"
#include "task.h"
#include <chrono>
#include <libraries/log/nrf_log.h>
using namespace Pinetime::Controllers;
using namespace std::chrono_literals;
AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} {
AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
: dateTimeController {dateTimeController}, fs {fs} {
}
namespace {
@@ -36,11 +38,28 @@ namespace {
void AlarmController::Init(System::SystemTask* systemTask) {
this->systemTask = systemTask;
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm);
LoadSettingsFromFile();
if (alarm.isEnabled) {
NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling");
ScheduleAlarm();
}
}
void AlarmController::SaveAlarm() {
// verify if it is necessary to save
if (alarmChanged) {
SaveSettingsToFile();
}
alarmChanged = false;
}
void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
hours = alarmHr;
minutes = alarmMin;
if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
return;
}
alarm.hours = alarmHr;
alarm.minutes = alarmMin;
alarmChanged = true;
}
void AlarmController::ScheduleAlarm() {
@@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
tm* tmAlarmTime = std::localtime(&ttAlarmTime);
// If the time being set has already passed today,the alarm should be set for tomorrow
if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) {
if (alarm.hours < dateTimeController.Hours() ||
(alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) {
tmAlarmTime->tm_mday += 1;
// tm_wday doesn't update automatically
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
}
tmAlarmTime->tm_hour = hours;
tmAlarmTime->tm_min = minutes;
tmAlarmTime->tm_hour = alarm.hours;
tmAlarmTime->tm_min = alarm.minutes;
tmAlarmTime->tm_sec = 0;
// if alarm is in weekday-only mode, make sure it shifts to the next weekday
if (recurrence == RecurType::Weekdays) {
if (alarm.recurrence == RecurType::Weekdays) {
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
tmAlarmTime->tm_mday += 1;
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
@@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
xTimerStart(alarmTimer, 0);
state = AlarmState::Set;
if (!alarm.isEnabled) {
alarm.isEnabled = true;
alarmChanged = true;
}
}
uint32_t AlarmController::SecondsToAlarm() const {
@@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const {
void AlarmController::DisableAlarm() {
xTimerStop(alarmTimer, 0);
state = AlarmState::Not_Set;
isAlerting = false;
if (alarm.isEnabled) {
alarm.isEnabled = false;
alarmChanged = true;
}
}
void AlarmController::SetOffAlarmNow() {
state = AlarmState::Alerting;
isAlerting = true;
systemTask->PushMessage(System::Messages::SetOffAlarm);
}
void AlarmController::StopAlerting() {
// Alarm state is off unless this is a recurring alarm
if (recurrence == RecurType::None) {
state = AlarmState::Not_Set;
isAlerting = false;
// Disable alarm unless it is recurring
if (alarm.recurrence == RecurType::None) {
alarm.isEnabled = false;
alarmChanged = true;
} else {
// set next instance
ScheduleAlarm();
}
}
void AlarmController::SetRecurrence(RecurType recurrence) {
if (alarm.recurrence != recurrence) {
alarm.recurrence = recurrence;
alarmChanged = true;
}
}
void AlarmController::LoadSettingsFromFile() {
lfs_file_t alarmFile;
AlarmSettings alarmBuffer;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file");
return;
}
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&alarmBuffer), sizeof(alarmBuffer));
fs.FileClose(&alarmFile);
if (alarmBuffer.version != alarmFormatVersion) {
NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding",
alarmBuffer.version,
alarmFormatVersion);
return;
}
alarm = alarmBuffer;
NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file");
}
void AlarmController::SaveSettingsToFile() const {
lfs_dir systemDir;
if (fs.DirOpen("/.system", &systemDir) != LFS_ERR_OK) {
fs.DirCreate("/.system");
}
fs.DirClose(&systemDir);
lfs_file_t alarmFile;
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) {
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file for saving");
return;
}
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(&alarm), sizeof(alarm));
fs.FileClose(&alarmFile);
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
}
+32 -14
View File
@@ -30,47 +30,65 @@ namespace Pinetime {
namespace Controllers {
class AlarmController {
public:
AlarmController(Controllers::DateTime& dateTimeController);
AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
void Init(System::SystemTask* systemTask);
void SaveAlarm();
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
void ScheduleAlarm();
void DisableAlarm();
void SetOffAlarmNow();
uint32_t SecondsToAlarm() const;
void StopAlerting();
enum class AlarmState { Not_Set, Set, Alerting };
enum class RecurType { None, Daily, Weekdays };
uint8_t Hours() const {
return hours;
return alarm.hours;
}
uint8_t Minutes() const {
return minutes;
return alarm.minutes;
}
AlarmState State() const {
return state;
bool IsAlerting() const {
return isAlerting;
}
bool IsEnabled() const {
return alarm.isEnabled;
}
RecurType Recurrence() const {
return recurrence;
return alarm.recurrence;
}
void SetRecurrence(RecurType recurType) {
recurrence = recurType;
}
void SetRecurrence(RecurType recurrence);
private:
// Versions 255 is reserved for now, so the version field can be made
// bigger, should it ever be needed.
static constexpr uint8_t alarmFormatVersion = 1;
struct AlarmSettings {
uint8_t version = alarmFormatVersion;
uint8_t hours = 7;
uint8_t minutes = 0;
RecurType recurrence = RecurType::None;
bool isEnabled = false;
};
bool isAlerting = false;
bool alarmChanged = false;
Controllers::DateTime& dateTimeController;
Controllers::FS& fs;
System::SystemTask* systemTask = nullptr;
TimerHandle_t alarmTimer;
uint8_t hours = 7;
uint8_t minutes = 0;
AlarmSettings alarm;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
AlarmState state = AlarmState::Not_Set;
RecurType recurrence = RecurType::None;
void LoadSettingsFromFile();
void SaveSettingsToFile() const;
};
}
}
+5 -3
View File
@@ -124,9 +124,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
bootloaderSize,
applicationSize);
// wait until SystemTask has finished waking up all devices
while (systemTask.IsSleeping()) {
vTaskDelay(50); // 50ms
// Wait until SystemTask has disabled sleeping
// This isn't quite correct, as we don't actually know
// if BleFirmwareUpdateStarted has been received yet
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
}
dfuImage.Erase();
+4 -4
View File
@@ -77,6 +77,10 @@ namespace Pinetime {
uint16_t ComputeCrc(uint8_t const* p_data, uint32_t size, uint16_t const* p_crc);
};
static constexpr ble_uuid128_t serviceUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
private:
Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController;
@@ -90,10 +94,6 @@ namespace Pinetime {
uint16_t revision {0x0008};
static constexpr ble_uuid128_t serviceUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
static constexpr ble_uuid128_t packetCharacteristicUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}};
+3 -3
View File
@@ -21,14 +21,14 @@ namespace Pinetime {
void SubscribeNotification(uint16_t attributeHandle);
void UnsubscribeNotification(uint16_t attributeHandle);
static constexpr uint16_t heartRateServiceId {0x180D};
static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
private:
NimbleController& nimble;
Controllers::HeartRateController& heartRateController;
static constexpr uint16_t heartRateServiceId {0x180D};
static constexpr uint16_t heartRateMeasurementId {0x2A37};
static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId};
struct ble_gatt_chr_def characteristicDefinition[2];
+2
View File
@@ -18,6 +18,8 @@
#include "components/ble/MusicService.h"
#include "components/ble/NimbleController.h"
#include <cstring>
#include <FreeRTOS.h>
#include <task.h>
namespace {
// 0000yyxx-78fc-48fe-8e23-433b3a1942d0
+1
View File
@@ -25,6 +25,7 @@
#include <host/ble_uuid.h>
#undef max
#undef min
#include <FreeRTOS.h>
namespace Pinetime {
namespace Controllers {
+12 -3
View File
@@ -158,7 +158,10 @@ void NimbleController::StartAdvertising() {
}
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.uuids128 = &dfuServiceUuid;
fields.uuids16 = &HeartRateService::heartRateServiceUuid;
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
fields.uuids128 = &DfuService::serviceUuid;
fields.num_uuids128 = 1;
fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
@@ -451,9 +454,15 @@ void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
/* Wakeup Spi and SpiNorFlash before accessing the file system
* This should be fixed in the FS driver
*/
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
vTaskDelay(10);
// This isn't quite correct
// SystemTask could receive EnableSleeping right after passing this check
// We need some guarantee that the SystemTask has processed the above message
// before we can continue
while (!systemTask.IsSleepDisabled()) {
vTaskDelay(pdMS_TO_TICKS(5));
}
lfs_file_t file_p;
-4
View File
@@ -112,10 +112,6 @@ namespace Pinetime {
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
uint8_t fastAdvCount = 0;
uint8_t bondId[16] = {0};
ble_uuid128_t dfuServiceUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
};
static NimbleController* nptr;
+13 -13
View File
@@ -42,9 +42,9 @@ namespace {
std::memcpy(cityName.data(), &dataBuffer[16], 32);
cityName[32] = '\0';
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
ToInt16(&dataBuffer[10]),
ToInt16(&dataBuffer[12]),
ToInt16(&dataBuffer[14]),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])),
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])),
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
std::move(cityName));
}
@@ -52,12 +52,12 @@ namespace {
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
std::array<SimpleWeatherService::Forecast::Day, SimpleWeatherService::MaxNbForecastDays> days;
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 {ToInt16(&dataBuffer[11 + (i * 5)]),
ToInt16(&dataBuffer[13 + (i * 5)]),
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};
@@ -80,7 +80,7 @@ int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble
return static_cast<Pinetime::Controllers::SimpleWeatherService*>(arg)->OnCommand(ctxt);
}
SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
SimpleWeatherService::SimpleWeatherService(DateTime& dateTimeController) : dateTimeController(dateTimeController) {
}
void SimpleWeatherService::Init() {
@@ -98,9 +98,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
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->temperature.PreciseCelsius(),
currentWeather->minTemperature.PreciseCelsius(),
currentWeather->maxTemperature.PreciseCelsius(),
currentWeather->iconId,
currentWeather->location.data());
}
@@ -112,9 +112,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
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);
forecast->days[i]->minTemperature.PreciseCelsius(),
forecast->days[i]->maxTemperature.PreciseCelsius(),
forecast->days[i]->iconId);
}
}
break;
+41 -16
View File
@@ -19,7 +19,7 @@
#include <cstdint>
#include <string>
#include <vector>
#include <array>
#include <memory>
#define min // workaround: nimble's min/max macros conflict with libstdc++
@@ -40,7 +40,7 @@ namespace Pinetime {
class SimpleWeatherService {
public:
explicit SimpleWeatherService(const DateTime& dateTimeController);
explicit SimpleWeatherService(DateTime& dateTimeController);
void Init();
@@ -61,13 +61,42 @@ namespace Pinetime {
Unknown = 255
};
class Temperature {
public:
explicit Temperature(int16_t raw) : raw {raw} {
}
[[nodiscard]] int16_t PreciseCelsius() const {
return raw;
}
[[nodiscard]] int16_t PreciseFahrenheit() const {
return raw * 9 / 5 + 3200;
}
[[nodiscard]] int16_t Celsius() const {
return (PreciseCelsius() + 50) / 100;
}
[[nodiscard]] int16_t Fahrenheit() const {
return (PreciseFahrenheit() + 50) / 100;
}
bool operator==(const Temperature& other) const {
return raw == other.raw;
}
private:
int16_t raw;
};
using Location = std::array<char, 33>; // 32 char + \0 (end of string)
struct CurrentWeather {
CurrentWeather(uint64_t timestamp,
int16_t temperature,
int16_t minTemperature,
int16_t maxTemperature,
Temperature temperature,
Temperature minTemperature,
Temperature maxTemperature,
Icons iconId,
Location&& location)
: timestamp {timestamp},
@@ -79,9 +108,9 @@ namespace Pinetime {
}
uint64_t timestamp;
int16_t temperature;
int16_t minTemperature;
int16_t maxTemperature;
Temperature temperature;
Temperature minTemperature;
Temperature maxTemperature;
Icons iconId;
Location location;
@@ -93,14 +122,14 @@ namespace Pinetime {
uint8_t nbDays;
struct Day {
int16_t minTemperature;
int16_t maxTemperature;
Temperature minTemperature;
Temperature maxTemperature;
Icons iconId;
bool operator==(const Day& other) const;
};
std::array<Day, MaxNbForecastDays> days;
std::array<std::optional<Day>, MaxNbForecastDays> days;
bool operator==(const Forecast& other) const;
};
@@ -108,10 +137,6 @@ namespace Pinetime {
std::optional<CurrentWeather> Current() const;
std::optional<Forecast> GetForecast() const;
static int16_t CelsiusToFahrenheit(int16_t celsius) {
return celsius * 9 / 5 + 3200;
}
private:
// 00050000-78fc-48fe-8e23-433b3a1942d0
static constexpr ble_uuid128_t BaseUuid() {
@@ -140,7 +165,7 @@ namespace Pinetime {
uint16_t eventHandle {};
const Pinetime::Controllers::DateTime& dateTimeController;
Pinetime::Controllers::DateTime& dateTimeController;
std::optional<CurrentWeather> currentWeather;
std::optional<Forecast> forecast;
@@ -2,38 +2,138 @@
#include <hal/nrf_gpio.h>
#include "displayapp/screens/Symbols.h"
#include "drivers/PinMap.h"
#include <libraries/delay/nrf_delay.h>
using namespace Pinetime::Controllers;
namespace {
// reinterpret_cast is not constexpr so this is the best we can do
static NRF_RTC_Type* const RTC = reinterpret_cast<NRF_RTC_Type*>(NRF_RTC2_BASE);
}
void BrightnessController::Init() {
nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
nrf_gpio_cfg_output(PinMap::LcdBacklightHigh);
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
static_assert(timerFrequency == 32768, "Change the prescaler below");
RTC->PRESCALER = 0;
// CC1 switches the backlight on (pin transitions from high to low) and resets the counter to 0
RTC->CC[1] = timerPeriod;
// Enable compare events for CC0,CC1
RTC->EVTEN = 0b0000'0000'0000'0011'0000'0000'0000'0000;
// Disable all interrupts
RTC->INTENCLR = 0b0000'0000'0000'1111'0000'0000'0000'0011;
Set(level);
}
void BrightnessController::ApplyBrightness(uint16_t rawBrightness) {
// The classic off, low, medium, high brightnesses are at {0, timerPeriod, timerPeriod*2, timerPeriod*3}
// These brightness levels do not use PWM: they only set/clear the corresponding pins
// Any brightness level between the above levels is achieved with efficient RTC based PWM on the next pin up
// E.g 2.5*timerPeriod corresponds to medium brightness with 50% PWM on the high pin
// Note: Raw brightness does not necessarily correspond to a linear perceived brightness
uint8_t pin;
if (rawBrightness > 2 * timerPeriod) {
rawBrightness -= 2 * timerPeriod;
pin = PinMap::LcdBacklightHigh;
} else if (rawBrightness > timerPeriod) {
rawBrightness -= timerPeriod;
pin = PinMap::LcdBacklightMedium;
} else {
pin = PinMap::LcdBacklightLow;
}
if (rawBrightness == timerPeriod || rawBrightness == 0) {
if (lastPin != UNSET) {
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
nrf_gpio_cfg_output(lastPin);
}
lastPin = UNSET;
if (rawBrightness == 0) {
nrf_gpio_pin_set(pin);
} else {
nrf_gpio_pin_clear(pin);
}
} else {
// If the pin on which we are doing PWM is changing
// Disable old PWM channel (if exists) and set up new one
if (lastPin != pin) {
if (lastPin != UNSET) {
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
nrf_gpio_cfg_output(lastPin);
}
nrfx_gpiote_out_config_t gpioteCfg = {.action = NRF_GPIOTE_POLARITY_TOGGLE,
.init_state = NRF_GPIOTE_INITIAL_VALUE_LOW,
.task_pin = true};
APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg));
nrfx_gpiote_out_task_enable(pin);
nrf_ppi_channel_endpoint_setup(ppiBacklightOff,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[0]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[1]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast<uint32_t>(&RTC->TASKS_CLEAR));
nrf_ppi_channel_enable(ppiBacklightOff);
nrf_ppi_channel_enable(ppiBacklightOn);
} else {
// If the pin used for PWM isn't changing, we only need to set the pin state to the initial value (low)
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
// Due to errata 20,179 and the intricacies of RTC timing, keep it simple: override the pin state
nrfx_gpiote_out_task_force(pin, false);
}
// CC0 switches the backlight off (pin transitions from low to high)
RTC->CC[0] = rawBrightness;
RTC->TASKS_CLEAR = 1;
RTC->TASKS_START = 1;
lastPin = pin;
}
switch (pin) {
case PinMap::LcdBacklightHigh:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
break;
case PinMap::LcdBacklightMedium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
break;
case PinMap::LcdBacklightLow:
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
}
}
void BrightnessController::Set(BrightnessController::Levels level) {
this->level = level;
switch (level) {
default:
case Levels::High:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
ApplyBrightness(3 * timerPeriod);
break;
case Levels::Medium:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
ApplyBrightness(2 * timerPeriod);
break;
case Levels::Low:
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
ApplyBrightness(timerPeriod);
break;
case Levels::AlwaysOn:
ApplyBrightness(timerPeriod / 10);
break;
case Levels::Off:
nrf_gpio_pin_set(PinMap::LcdBacklightLow);
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
ApplyBrightness(0);
break;
}
}
@@ -2,11 +2,14 @@
#include <cstdint>
#include "nrf_ppi.h"
#include "nrfx_gpiote.h"
namespace Pinetime {
namespace Controllers {
class BrightnessController {
public:
enum class Levels { Off, Low, Medium, High };
enum class Levels { Off, AlwaysOn, Low, Medium, High };
void Init();
void Set(Levels level);
@@ -20,6 +23,25 @@ namespace Pinetime {
private:
Levels level = Levels::High;
static constexpr uint8_t UNSET = UINT8_MAX;
uint8_t lastPin = UNSET;
// Maximum time (μs) it takes for the RTC to fully stop
static constexpr uint8_t rtcStopTime = 46;
// Frequency of timer used for PWM (Hz)
static constexpr uint16_t timerFrequency = 32768;
// Backlight PWM frequency (Hz)
static constexpr uint16_t pwmFreq = 1000;
// Wraparound point in timer ticks
// Defines the number of brightness levels between each pin
static constexpr uint16_t timerPeriod = timerFrequency / pwmFreq;
// Warning: nimble reserves some PPIs
// https://github.com/InfiniTimeOrg/InfiniTime/blob/034d83fe6baf1ab3875a34f8cee387e24410a824/src/libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c#L53
// SpiMaster uses PPI 0 for an erratum workaround
// Channel 1, 2 should be free to use
static constexpr nrf_ppi_channel_t ppiBacklightOn = NRF_PPI_CHANNEL1;
static constexpr nrf_ppi_channel_t ppiBacklightOff = NRF_PPI_CHANNEL2;
void ApplyBrightness(uint16_t val);
};
}
}
+51 -18
View File
@@ -1,22 +1,42 @@
#include "components/datetime/DateTimeController.h"
#include <libraries/log/nrf_log.h>
#include <systemtask/SystemTask.h>
#include <hal/nrf_rtc.h>
#include "nrf_assert.h"
using namespace Pinetime::Controllers;
namespace {
char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
constexpr const char* const DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
constexpr const char* const DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
constexpr const char* const MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
constexpr const char* const MonthsStringLow[] =
{"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
constexpr int compileTimeAtoi(const char* str) {
int result = 0;
while (*str >= '0' && *str <= '9') {
result = result * 10 + *str - '0';
str++;
}
return result;
}
}
DateTime::DateTime(Controllers::Settings& settingsController) : settingsController {settingsController} {
mutex = xSemaphoreCreateMutex();
ASSERT(mutex != nullptr);
xSemaphoreGive(mutex);
// __DATE__ is a string of the format "MMM DD YYYY", so an offset of 7 gives the start of the year
SetTime(compileTimeAtoi(&__DATE__[7]), 1, 1, 0, 0, 0);
}
void DateTime::SetCurrentTime(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> t) {
xSemaphoreTake(mutex, portMAX_DELAY);
this->currentDateTime = t;
UpdateTime(previousSystickCounter); // Update internal state without updating the time
UpdateTime(previousSystickCounter, true); // Update internal state without updating the time
xSemaphoreGive(mutex);
}
void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
@@ -29,15 +49,19 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour,
/* .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));
NRF_LOG_INFO("%d %d %d ", day, month, year);
NRF_LOG_INFO("%d %d %d ", hour, minute, second);
UpdateTime(previousSystickCounter);
tm.tm_isdst = -1; // Use DST value from local time zone
systemTask->PushMessage(System::Messages::OnNewTime);
xSemaphoreTake(mutex, portMAX_DELAY);
currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm));
UpdateTime(previousSystickCounter, true);
xSemaphoreGive(mutex);
if (systemTask != nullptr) {
systemTask->PushMessage(System::Messages::OnNewTime);
}
}
void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
@@ -45,25 +69,34 @@ void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
dstOffset = dst;
}
void DateTime::UpdateTime(uint32_t systickCounter) {
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> DateTime::CurrentDateTime() {
xSemaphoreTake(mutex, portMAX_DELAY);
UpdateTime(nrf_rtc_counter_get(portNRF_RTC_REG), false);
xSemaphoreGive(mutex);
return currentDateTime;
}
void DateTime::UpdateTime(uint32_t systickCounter, bool forceUpdate) {
// Handle systick counter overflow
uint32_t systickDelta = 0;
if (systickCounter < previousSystickCounter) {
systickDelta = 0xffffff - previousSystickCounter;
systickDelta = static_cast<uint32_t>(portNRF_RTC_MAXTICKS) - previousSystickCounter;
systickDelta += systickCounter + 1;
} else {
systickDelta = systickCounter - previousSystickCounter;
}
/*
* 1000 ms = 1024 ticks
*/
auto correctedDelta = systickDelta / 1024;
auto rest = systickDelta % 1024;
auto correctedDelta = systickDelta / configTICK_RATE_HZ;
// If a second hasn't passed, there is nothing to do
// If the time has been changed, set forceUpdate to trigger internal state updates
if (correctedDelta == 0 && !forceUpdate) {
return;
}
auto rest = systickDelta % configTICK_RATE_HZ;
if (systickCounter >= rest) {
previousSystickCounter = systickCounter - rest;
} else {
previousSystickCounter = 0xffffff - (rest - systickCounter);
previousSystickCounter = static_cast<uint32_t>(portNRF_RTC_MAXTICKS) - (rest - systickCounter - 1);
}
currentDateTime += std::chrono::seconds(correctedDelta);
+10 -8
View File
@@ -5,6 +5,8 @@
#include <ctime>
#include <string>
#include "components/settings/Settings.h"
#include <FreeRTOS.h>
#include <semphr.h>
namespace Pinetime {
namespace System {
@@ -39,14 +41,12 @@ namespace Pinetime {
*
* used to update difference between utc and local time (see UtcOffset())
*
* parameters are in quarters of an our. Following the BLE CTS specification,
* parameters are in quarters of an hour. Following the BLE CTS specification,
* timezone is expected to be constant over DST which will be reported in
* dst field.
*/
void SetTimeZone(int8_t timezone, int8_t dst);
void UpdateTime(uint32_t systickCounter);
uint16_t Year() const {
return 1900 + localTime.tm_year;
}
@@ -124,12 +124,10 @@ namespace Pinetime {
static const char* MonthShortToStringLow(Months month);
static const char* DayOfWeekShortToStringLow(Days day);
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
return currentDateTime;
}
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime();
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> UTCDateTime() const {
return currentDateTime - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60);
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> UTCDateTime() {
return CurrentDateTime() - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60);
}
std::chrono::seconds Uptime() const {
@@ -141,10 +139,14 @@ namespace Pinetime {
std::string FormattedTime();
private:
void UpdateTime(uint32_t systickCounter, bool forceUpdate);
std::tm localTime;
int8_t tzOffset = 0;
int8_t dstOffset = 0;
SemaphoreHandle_t mutex = nullptr;
uint32_t previousSystickCounter = 0;
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> currentDateTime;
std::chrono::seconds uptime {0};
+41
View File
@@ -0,0 +1,41 @@
# Refactoring needed
## Context
The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) highlighted some
limitations in the design of DateTimeController: the granularity of the time returned by `DateTime::CurrentDateTime()`
is limited by the frequency at which SystemTask calls `DateTime::UpdateTime()`, which is currently set to 100ms.
@mark9064 provided more details
in [this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041#issuecomment-2048528967).
The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) provided some changes
to `DateTime` controller that improves the granularity of the time returned by `DateTime::CurrentDateTime()`.
However, the review showed that `DateTime` cannot be `const` anymore, even when it's only used to get the current time,
since `DateTime::CurrentDateTime()` changes the internal state of the instance.
We tried to identify alternative implementation that would have maintained the "const correctness" but we eventually
figured that this would lead to a re-design of `DateTime` which was out of scope of the initial PR (Continuous time
updates and always on display).
So we decided to merge this PR #2041 and agree to fix/improve `DateTime` later on.
## What needs to be done?
Improve/redesign `DateTime` so that it
* provides a very granular (ideally down to the millisecond) date and time via `CurrentDateTime()`.
* can be declared/passed as `const` when it's only used to **get** the time.
* limits the use of mutex as much as possible (an ideal implementation would not use any mutex, but this might not be
possible).
* improves the design of `DateTime::Seconds()`, `DateTime::Minutes()`, `DateTime::Hours()`, etc as
explained [in this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054#pullrequestreview-2037033105).
Once this redesign is implemented, all instances/references to `DateTime` should be reviewed and updated to use `const`
where appropriate.
Please check the following PR to get more context about this redesign:
* [#2041 - Continuous time updates by @mark9064](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041)
* [#2054 - Continuous time update - Alternative implementation to #2041 by @JF002](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054)
+1 -1
View File
@@ -142,7 +142,7 @@ Ppg::Ppg() {
spectrum.fill(0.0f);
}
int8_t Ppg::Preprocess(uint32_t hrs, uint32_t als) {
int8_t Ppg::Preprocess(uint16_t hrs, uint16_t als) {
if (dataIndex < dataLength) {
dataHRS[dataIndex++] = hrs;
}
+1 -1
View File
@@ -14,7 +14,7 @@ namespace Pinetime {
class Ppg {
public:
Ppg();
int8_t Preprocess(uint32_t hrs, uint32_t als);
int8_t Preprocess(uint16_t hrs, uint16_t als);
int HeartRate();
void Reset(bool resetDaqBuffer);
static constexpr int deltaTms = 100;
+19 -2
View File
@@ -220,6 +220,21 @@ namespace Pinetime {
return settings.screenTimeOut;
};
bool GetAlwaysOnDisplay() const {
return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep;
};
void SetAlwaysOnDisplaySetting(bool state) {
if (state != settings.alwaysOnDisplay) {
settingsChanged = true;
}
settings.alwaysOnDisplay = state;
}
bool GetAlwaysOnDisplaySetting() const {
return settings.alwaysOnDisplay;
}
void SetShakeThreshold(uint16_t thresh) {
if (settings.shakeWakeThreshold != thresh) {
settings.shakeWakeThreshold = thresh;
@@ -307,14 +322,16 @@ namespace Pinetime {
private:
Pinetime::Controllers::FS& fs;
static constexpr uint32_t settingsVersion = 0x0007;
static constexpr uint32_t settingsVersion = 0x0008;
struct SettingsData {
uint32_t version = settingsVersion;
uint32_t stepsGoal = 10000;
uint32_t screenTimeOut = 15000;
ClockType clockType = ClockType::FUZZY;
bool alwaysOnDisplay = false;
ClockType clockType = ClockType::H24;
WeatherFormat weatherFormat = WeatherFormat::Metric;
Notification notificationStatus = Notification::On;