Merge branch 'main' into wb/wadokei-merge
This commit is contained in:
+3
-1
@@ -480,6 +480,7 @@ list(APPEND SOURCE_FILES
|
||||
|
||||
systemtask/SystemTask.cpp
|
||||
systemtask/SystemMonitor.cpp
|
||||
systemtask/WakeLock.cpp
|
||||
drivers/TwiMaster.cpp
|
||||
|
||||
heartratetask/HeartRateTask.cpp
|
||||
@@ -544,6 +545,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||
|
||||
systemtask/SystemTask.cpp
|
||||
systemtask/SystemMonitor.cpp
|
||||
systemtask/WakeLock.cpp
|
||||
drivers/TwiMaster.cpp
|
||||
components/rle/RleDecoder.cpp
|
||||
components/heartrate/HeartRateController.cpp
|
||||
@@ -656,6 +658,7 @@ set(INCLUDE_FILES
|
||||
displayapp/InfiniTimeTheme.h
|
||||
systemtask/SystemTask.h
|
||||
systemtask/SystemMonitor.h
|
||||
systemtask/WakeLock.h
|
||||
displayapp/screens/Symbols.h
|
||||
drivers/TwiMaster.h
|
||||
heartratetask/HeartRateTask.h
|
||||
@@ -856,7 +859,6 @@ target_compile_options(nrf-sdk PRIVATE
|
||||
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
-O3
|
||||
)
|
||||
|
||||
# NimBLE
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <host/ble_uuid.h>
|
||||
#undef max
|
||||
#undef min
|
||||
#include <FreeRTOS.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+140
-42
@@ -83,7 +83,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
||||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::FS& filesystem)
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash)
|
||||
: lcd {lcd},
|
||||
touchPanel {touchPanel},
|
||||
batteryController {batteryController},
|
||||
@@ -99,6 +100,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
||||
brightnessController {brightnessController},
|
||||
touchHandler {touchHandler},
|
||||
filesystem {filesystem},
|
||||
spiNorFlash {spiNorFlash},
|
||||
lvgl {lcd, filesystem},
|
||||
timer(this, TimerCallback),
|
||||
controllers {batteryController,
|
||||
@@ -156,6 +158,44 @@ void DisplayApp::InitHw() {
|
||||
lcd.Init();
|
||||
}
|
||||
|
||||
TickType_t DisplayApp::CalculateSleepTime() {
|
||||
// Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame
|
||||
// Next frame time is frame count * refresh period (ms) * tick rate
|
||||
|
||||
auto RoundedDiv = [](uint32_t a, uint32_t b) {
|
||||
return ((a + (b / 2)) / b);
|
||||
};
|
||||
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
|
||||
// in this case around 9 hours (=overflow frame count / always on refresh period)
|
||||
constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
|
||||
|
||||
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
|
||||
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
|
||||
// to increase the number of ticks (frames) before the overflow tick is reached
|
||||
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);
|
||||
|
||||
// Assumptions
|
||||
|
||||
// Tick rate is multiple of 8
|
||||
// Needed for division trick above
|
||||
static_assert(configTICK_RATE_HZ % 8 == 0);
|
||||
|
||||
// Frame count must always wraparound more often than the system tick count does
|
||||
// Always on overflow time (ms) < system tick overflow time (ms)
|
||||
// Using 64bit ints here to avoid overflow
|
||||
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
|
||||
|
||||
if (alwaysOnFrameCount == overflowFrameCount) {
|
||||
alwaysOnFrameCount = 0;
|
||||
alwaysOnStartTime = xTaskGetTickCount();
|
||||
}
|
||||
if (targetRenderTick > ticksElapsed) {
|
||||
return targetRenderTick - ticksElapsed;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayApp::Refresh() {
|
||||
auto LoadPreviousScreen = [this]() {
|
||||
FullRefreshDirections returnDirection;
|
||||
@@ -179,21 +219,6 @@ void DisplayApp::Refresh() {
|
||||
LoadScreen(returnAppStack.Pop(), returnDirection);
|
||||
};
|
||||
|
||||
auto DimScreen = [this]() {
|
||||
if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
||||
isDimmed = true;
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
}
|
||||
};
|
||||
|
||||
auto RestoreBrightness = [this]() {
|
||||
if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
||||
isDimmed = false;
|
||||
lv_disp_trig_activity(nullptr);
|
||||
ApplyBrightness();
|
||||
}
|
||||
};
|
||||
|
||||
auto IsPastDimTime = [this]() -> bool {
|
||||
return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000);
|
||||
};
|
||||
@@ -207,6 +232,27 @@ void DisplayApp::Refresh() {
|
||||
case States::Idle:
|
||||
queueTimeout = portMAX_DELAY;
|
||||
break;
|
||||
case States::AOD:
|
||||
if (!currentScreen->IsRunning()) {
|
||||
LoadPreviousScreen();
|
||||
}
|
||||
// Check we've slept long enough
|
||||
// Might not be true if the loop received an event
|
||||
// If not true, then wait that amount of time
|
||||
queueTimeout = CalculateSleepTime();
|
||||
if (queueTimeout == 0) {
|
||||
// Only advance the tick count when LVGL is done
|
||||
// Otherwise keep running the task handler while it still has things to draw
|
||||
// Note: under high graphics load, LVGL will always have more work to do
|
||||
if (lv_task_handler() > 0) {
|
||||
// Drop frames that we've missed if drawing/event handling took way longer than expected
|
||||
while (queueTimeout == 0) {
|
||||
alwaysOnFrameCount += 1;
|
||||
queueTimeout = CalculateSleepTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case States::Running:
|
||||
if (!currentScreen->IsRunning()) {
|
||||
LoadPreviousScreen();
|
||||
@@ -215,14 +261,27 @@ void DisplayApp::Refresh() {
|
||||
|
||||
if (!systemTask->IsSleepDisabled() && IsPastDimTime()) {
|
||||
if (!isDimmed) {
|
||||
DimScreen();
|
||||
isDimmed = true;
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
}
|
||||
if (IsPastSleepTime()) {
|
||||
systemTask->PushMessage(System::Messages::GoToSleep);
|
||||
state = States::Idle;
|
||||
if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) {
|
||||
PushMessageToSystemTask(System::Messages::GoToSleep);
|
||||
// Can't set state to Idle here, something may send
|
||||
// DisableSleeping before this GoToSleep arrives
|
||||
// Instead we check we have no messages queued before sending GoToSleep
|
||||
// This works as the SystemTask is higher priority than DisplayApp
|
||||
// As soon as we send GoToSleep, SystemTask pre-empts DisplayApp
|
||||
// Whenever DisplayApp is running again, it is guaranteed that
|
||||
// SystemTask has handled the message
|
||||
// If it responded, we will have a GoToSleep waiting in the queue
|
||||
// By checking that there are no messages in the queue, we avoid
|
||||
// resending GoToSleep when we already have a response
|
||||
// SystemTask is resilient to duplicate messages, this is an
|
||||
// optimisation to reduce pressure on the message queues
|
||||
}
|
||||
} else if (isDimmed) {
|
||||
RestoreBrightness();
|
||||
isDimmed = false;
|
||||
ApplyBrightness();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -238,30 +297,73 @@ void DisplayApp::Refresh() {
|
||||
uint8_t myChimes = 9-(((int)floor(myHour/2))%6);
|
||||
|
||||
switch (msg) {
|
||||
case Messages::DimScreen:
|
||||
DimScreen();
|
||||
break;
|
||||
case Messages::RestoreBrightness:
|
||||
RestoreBrightness();
|
||||
break;
|
||||
case Messages::GoToSleep:
|
||||
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
||||
case Messages::GoToAOD:
|
||||
// Checking if SystemTask is sleeping is purely an optimisation.
|
||||
// If it's no longer sleeping since it sent GoToSleep, it has
|
||||
// cancelled the sleep and transitioned directly from
|
||||
// GoingToSleep->Running, so we are about to receive GoToRunning
|
||||
// and can ignore this message. If it wasn't ignored, DisplayApp
|
||||
// would go to sleep and then immediately re-wake
|
||||
if (state != States::Running || !systemTask->IsSleeping()) {
|
||||
break;
|
||||
}
|
||||
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
|
||||
brightnessController.Lower();
|
||||
vTaskDelay(100);
|
||||
}
|
||||
lcd.Sleep();
|
||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
||||
state = States::Idle;
|
||||
// Turn brightness down (or set to AlwaysOn mode)
|
||||
if (msg == Messages::GoToAOD) {
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
|
||||
} else {
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
|
||||
}
|
||||
// Since the active screen is not really an app, go back to Clock.
|
||||
if (currentApp == Apps::Launcher || currentApp == Apps::Notifications || currentApp == Apps::QuickSettings ||
|
||||
currentApp == Apps::Settings) {
|
||||
LoadScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
// Wait for the clock app to load before moving on.
|
||||
while (!lv_task_handler()) {
|
||||
};
|
||||
}
|
||||
// Clear any ongoing touch pressed events
|
||||
// Without this LVGL gets stuck in the pressed state and will keep refreshing the
|
||||
// display activity timer causing the screen to never sleep after timeout
|
||||
lvgl.ClearTouchState();
|
||||
if (msg == Messages::GoToAOD) {
|
||||
lcd.LowPowerOn();
|
||||
// Record idle entry time
|
||||
alwaysOnFrameCount = 0;
|
||||
alwaysOnStartTime = xTaskGetTickCount();
|
||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
|
||||
state = States::AOD;
|
||||
} else {
|
||||
lcd.Sleep();
|
||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
||||
state = States::Idle;
|
||||
}
|
||||
break;
|
||||
case Messages::NotifyDeviceActivity:
|
||||
lv_disp_trig_activity(nullptr);
|
||||
break;
|
||||
case Messages::GoToRunning:
|
||||
lcd.Wakeup();
|
||||
// If SystemTask is sleeping, the GoToRunning message is old
|
||||
// and must be ignored. Otherwise DisplayApp will use SPI
|
||||
// that is powered down and cause bad behaviour
|
||||
if (state == States::Running || systemTask->IsSleeping()) {
|
||||
break;
|
||||
}
|
||||
if (state == States::AOD) {
|
||||
lcd.LowPowerOff();
|
||||
} else {
|
||||
lcd.Wakeup();
|
||||
}
|
||||
lv_disp_trig_activity(nullptr);
|
||||
ApplyBrightness();
|
||||
state = States::Running;
|
||||
break;
|
||||
case Messages::UpdateBleConnection:
|
||||
// clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected :
|
||||
// Screens::Clock::BleConnectionStates::NotConnected);
|
||||
// Only used for recovery firmware
|
||||
break;
|
||||
case Messages::NewNotification:
|
||||
LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down);
|
||||
@@ -376,10 +478,6 @@ void DisplayApp::Refresh() {
|
||||
case Messages::BleRadioEnableToggle:
|
||||
PushMessageToSystemTask(System::Messages::BleRadioEnableToggle);
|
||||
break;
|
||||
case Messages::UpdateDateTime:
|
||||
// Added to remove warning
|
||||
// What should happen here?
|
||||
break;
|
||||
case Messages::Chime:
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
|
||||
@@ -417,13 +515,12 @@ void DisplayApp::Refresh() {
|
||||
}
|
||||
break;
|
||||
case Messages::OnChargingEvent:
|
||||
RestoreBrightness();
|
||||
motorController.RunForDuration(15);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (touchHandler.IsTouching()) {
|
||||
if (state == States::Running && touchHandler.IsTouching()) {
|
||||
currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY());
|
||||
}
|
||||
|
||||
@@ -546,7 +643,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
|
||||
break;
|
||||
case Apps::SettingDisplay:
|
||||
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
|
||||
currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
|
||||
break;
|
||||
case Apps::SettingSteps:
|
||||
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);
|
||||
@@ -577,7 +674,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||
bleController,
|
||||
watchdog,
|
||||
motionController,
|
||||
touchPanel);
|
||||
touchPanel,
|
||||
spiNorFlash);
|
||||
break;
|
||||
case Apps::FlashLight:
|
||||
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Pinetime {
|
||||
namespace Applications {
|
||||
class DisplayApp {
|
||||
public:
|
||||
enum class States { Idle, Running };
|
||||
enum class States { Idle, Running, AOD };
|
||||
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
|
||||
|
||||
DisplayApp(Drivers::St7789& lcd,
|
||||
@@ -66,7 +66,8 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||
void Start(System::BootErrors error);
|
||||
void PushMessage(Display::Messages msg);
|
||||
|
||||
@@ -96,6 +97,7 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::BrightnessController& brightnessController;
|
||||
Pinetime::Controllers::TouchHandler& touchHandler;
|
||||
Pinetime::Controllers::FS& filesystem;
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
|
||||
|
||||
Pinetime::Controllers::FirmwareValidator validator;
|
||||
Pinetime::Components::LittleVgl lvgl;
|
||||
@@ -135,6 +137,13 @@ namespace Pinetime {
|
||||
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
|
||||
|
||||
bool isDimmed = false;
|
||||
|
||||
TickType_t CalculateSleepTime();
|
||||
TickType_t alwaysOnFrameCount;
|
||||
TickType_t alwaysOnStartTime;
|
||||
// If this is to be changed, make sure the actual always on refresh rate is changed
|
||||
// by configuring the LCD refresh timings
|
||||
static constexpr uint32_t alwaysOnRefreshPeriod = 500;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
||||
Pinetime::Controllers::AlarmController& /*alarmController*/,
|
||||
Pinetime::Controllers::BrightnessController& /*brightnessController*/,
|
||||
Pinetime::Controllers::TouchHandler& /*touchHandler*/,
|
||||
Pinetime::Controllers::FS& /*filesystem*/)
|
||||
Pinetime::Controllers::FS& /*filesystem*/,
|
||||
Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/)
|
||||
: lcd {lcd}, bleController {bleController} {
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Pinetime {
|
||||
class St7789;
|
||||
class Cst816S;
|
||||
class Watchdog;
|
||||
class SpiNorFlash;
|
||||
}
|
||||
|
||||
namespace Controllers {
|
||||
@@ -59,7 +60,8 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||
void Start();
|
||||
|
||||
void Start(Pinetime::System::BootErrors) {
|
||||
|
||||
@@ -248,6 +248,8 @@ void LittleVgl::SetNewTouchPoint(int16_t x, int16_t y, bool contact) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel an ongoing tap
|
||||
// Signifies that LVGL should not handle the current tap
|
||||
void LittleVgl::CancelTap() {
|
||||
if (tapped) {
|
||||
isCancelled = true;
|
||||
@@ -255,6 +257,13 @@ void LittleVgl::CancelTap() {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the current tapped state
|
||||
// Signifies that touch input processing is suspended
|
||||
void LittleVgl::ClearTouchState() {
|
||||
touchPoint = {-1, -1};
|
||||
tapped = false;
|
||||
}
|
||||
|
||||
bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
|
||||
ptr->point.x = touchPoint.x;
|
||||
ptr->point.y = touchPoint.y;
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Pinetime {
|
||||
void SetFullRefresh(FullRefreshDirections direction);
|
||||
void SetNewTouchPoint(int16_t x, int16_t y, bool contact);
|
||||
void CancelTap();
|
||||
void ClearTouchState();
|
||||
|
||||
bool GetFullRefresh() {
|
||||
bool returnValue = fullRefresh;
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace Pinetime {
|
||||
namespace Display {
|
||||
enum class Messages : uint8_t {
|
||||
GoToSleep,
|
||||
GoToAOD,
|
||||
GoToRunning,
|
||||
UpdateDateTime,
|
||||
UpdateBleConnection,
|
||||
TouchEvent,
|
||||
ButtonPushed,
|
||||
@@ -17,8 +17,9 @@ namespace Pinetime {
|
||||
NewNotification,
|
||||
TimerDone,
|
||||
BleFirmwareUpdateStarted,
|
||||
DimScreen,
|
||||
RestoreBrightness,
|
||||
// Resets the screen timeout timer when awake
|
||||
// Does nothing when asleep
|
||||
NotifyDeviceActivity,
|
||||
ShowPairingKey,
|
||||
AlarmTriggered,
|
||||
Chime,
|
||||
|
||||
@@ -11,6 +11,7 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/jetbrains_mono_bold_20.c_M.patch
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
# FindPython3 module introduces with CMake 3.12
|
||||
# https://cmake.org/cmake/help/latest/module/FindPython3.html
|
||||
set(Python3_FIND_STRATEGY LOCATION) # https://discourse.cmake.org/t/find-package-python3-is-not-finding-the-correct-python/10563
|
||||
find_package(Python3 REQUIRED)
|
||||
else()
|
||||
set(Python3_EXECUTABLE "python")
|
||||
|
||||
@@ -48,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
|
||||
Controllers::Settings::ClockType clockType,
|
||||
System::SystemTask& systemTask,
|
||||
Controllers::MotorController& motorController)
|
||||
: alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} {
|
||||
: alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} {
|
||||
|
||||
hourCounter.Create();
|
||||
lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
|
||||
@@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
|
||||
|
||||
UpdateAlarmTime();
|
||||
|
||||
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) {
|
||||
if (alarmController.IsAlerting()) {
|
||||
SetAlerting();
|
||||
} else {
|
||||
SetSwitchState(LV_ANIM_OFF);
|
||||
@@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
|
||||
}
|
||||
|
||||
Alarm::~Alarm() {
|
||||
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
|
||||
if (alarmController.IsAlerting()) {
|
||||
StopAlerting();
|
||||
}
|
||||
lv_obj_clean(lv_scr_act());
|
||||
alarmController.SaveAlarm();
|
||||
}
|
||||
|
||||
void Alarm::DisableAlarm() {
|
||||
if (alarmController.State() == AlarmController::AlarmState::Set) {
|
||||
if (alarmController.IsEnabled()) {
|
||||
alarmController.DisableAlarm();
|
||||
lv_switch_off(enableSwitch, LV_ANIM_ON);
|
||||
}
|
||||
@@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
|
||||
HideInfo();
|
||||
return true;
|
||||
}
|
||||
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
|
||||
if (alarmController.IsAlerting()) {
|
||||
StopAlerting();
|
||||
return true;
|
||||
}
|
||||
@@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
|
||||
|
||||
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
// Don't allow closing the screen by swiping while the alarm is alerting
|
||||
return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown;
|
||||
return alarmController.IsAlerting() && event == TouchEvents::SwipeDown;
|
||||
}
|
||||
|
||||
void Alarm::OnValueChanged() {
|
||||
@@ -205,7 +206,7 @@ void Alarm::SetAlerting() {
|
||||
lv_obj_set_hidden(btnStop, false);
|
||||
taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this);
|
||||
motorController.StartRinging();
|
||||
systemTask.PushMessage(System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
}
|
||||
|
||||
void Alarm::StopAlerting() {
|
||||
@@ -216,21 +217,16 @@ void Alarm::StopAlerting() {
|
||||
lv_task_del(taskStopAlarm);
|
||||
taskStopAlarm = nullptr;
|
||||
}
|
||||
systemTask.PushMessage(System::Messages::EnableSleeping);
|
||||
wakeLock.Release();
|
||||
lv_obj_set_hidden(enableSwitch, false);
|
||||
lv_obj_set_hidden(btnStop, true);
|
||||
}
|
||||
|
||||
void Alarm::SetSwitchState(lv_anim_enable_t anim) {
|
||||
switch (alarmController.State()) {
|
||||
case AlarmController::AlarmState::Set:
|
||||
lv_switch_on(enableSwitch, anim);
|
||||
break;
|
||||
case AlarmController::AlarmState::Not_Set:
|
||||
lv_switch_off(enableSwitch, anim);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (alarmController.IsEnabled()) {
|
||||
lv_switch_on(enableSwitch, anim);
|
||||
} else {
|
||||
lv_switch_off(enableSwitch, anim);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +243,7 @@ void Alarm::ShowInfo() {
|
||||
txtMessage = lv_label_create(btnMessage, nullptr);
|
||||
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY);
|
||||
|
||||
if (alarmController.State() == AlarmController::AlarmState::Set) {
|
||||
if (alarmController.IsEnabled()) {
|
||||
auto timeToAlarm = alarmController.SecondsToAlarm();
|
||||
|
||||
auto daysToAlarm = timeToAlarm / 86400;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
@@ -43,7 +44,7 @@ namespace Pinetime {
|
||||
|
||||
private:
|
||||
Controllers::AlarmController& alarmController;
|
||||
System::SystemTask& systemTask;
|
||||
System::WakeLock wakeLock;
|
||||
Controllers::MotorController& motorController;
|
||||
|
||||
lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch;
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace {
|
||||
FirmwareValidation::FirmwareValidation(Pinetime::Controllers::FirmwareValidator& validator) : validator {validator} {
|
||||
labelVersion = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_fmt(labelVersion,
|
||||
"Version : %lu.%lu.%lu\n"
|
||||
"ShortRef : %s",
|
||||
"Version: %lu.%lu.%lu\n"
|
||||
"ShortRef: %s",
|
||||
Version::Major(),
|
||||
Version::Minor(),
|
||||
Version::Patch(),
|
||||
|
||||
@@ -15,8 +15,9 @@ namespace {
|
||||
}
|
||||
|
||||
FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController)
|
||||
: systemTask {systemTask}, brightnessController {brightnessController} {
|
||||
: wakeLock(systemTask), brightnessController {brightnessController} {
|
||||
|
||||
previousBrightnessLevel = brightnessController.Level();
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
|
||||
flashLight = lv_label_create(lv_scr_act(), nullptr);
|
||||
@@ -46,13 +47,13 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo
|
||||
backgroundAction->user_data = this;
|
||||
lv_obj_set_event_cb(backgroundAction, EventHandler);
|
||||
|
||||
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
}
|
||||
|
||||
FlashLight::~FlashLight() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
|
||||
brightnessController.Set(previousBrightnessLevel);
|
||||
}
|
||||
|
||||
void FlashLight::SetColors() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/brightness/BrightnessController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
#include <cstdint>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
@@ -23,10 +24,11 @@ namespace Pinetime {
|
||||
void SetIndicators();
|
||||
void SetColors();
|
||||
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Pinetime::System::WakeLock wakeLock;
|
||||
Controllers::BrightnessController& brightnessController;
|
||||
|
||||
Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High;
|
||||
Controllers::BrightnessController::Levels previousBrightnessLevel;
|
||||
|
||||
lv_obj_t* flashLight;
|
||||
lv_obj_t* backgroundAction;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace {
|
||||
}
|
||||
|
||||
HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, System::SystemTask& systemTask)
|
||||
: heartRateController {heartRateController}, systemTask {systemTask} {
|
||||
: heartRateController {heartRateController}, wakeLock(systemTask) {
|
||||
bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
|
||||
label_hr = lv_label_create(lv_scr_act(), nullptr);
|
||||
|
||||
@@ -63,7 +63,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
|
||||
label_startStop = lv_label_create(btn_startStop, nullptr);
|
||||
UpdateStartStopButton(isHrRunning);
|
||||
if (isHrRunning) {
|
||||
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
}
|
||||
|
||||
taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this);
|
||||
@@ -72,7 +72,6 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
|
||||
HeartRate::~HeartRate() {
|
||||
lv_task_del(taskRefresh);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
|
||||
}
|
||||
|
||||
void HeartRate::Refresh() {
|
||||
@@ -101,12 +100,12 @@ void HeartRate::OnStartStopEvent(lv_event_t event) {
|
||||
if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) {
|
||||
heartRateController.Start();
|
||||
UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped);
|
||||
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::highlight);
|
||||
} else {
|
||||
heartRateController.Stop();
|
||||
UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped);
|
||||
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
|
||||
wakeLock.Release();
|
||||
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <chrono>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
#include "Symbols.h"
|
||||
#include <lvgl/src/lv_core/lv_style.h>
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
@@ -27,7 +28,7 @@ namespace Pinetime {
|
||||
|
||||
private:
|
||||
Controllers::HeartRateController& heartRateController;
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Pinetime::System::WakeLock wakeLock;
|
||||
void UpdateStartStopButton(bool isRunning);
|
||||
lv_obj_t* label_hr;
|
||||
lv_obj_t* label_bpm;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace {
|
||||
}
|
||||
|
||||
Metronome::Metronome(Controllers::MotorController& motorController, System::SystemTask& systemTask)
|
||||
: motorController {motorController}, systemTask {systemTask} {
|
||||
: motorController {motorController}, wakeLock(systemTask) {
|
||||
|
||||
bpmArc = lv_arc_create(lv_scr_act(), nullptr);
|
||||
bpmArc->user_data = this;
|
||||
@@ -72,7 +72,6 @@ Metronome::Metronome(Controllers::MotorController& motorController, System::Syst
|
||||
|
||||
Metronome::~Metronome() {
|
||||
lv_task_del(taskRefresh);
|
||||
systemTask.PushMessage(System::Messages::EnableSleeping);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
@@ -128,12 +127,12 @@ void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
metronomeStarted = !metronomeStarted;
|
||||
if (metronomeStarted) {
|
||||
lv_label_set_text_static(lblPlayPause, Symbols::pause);
|
||||
systemTask.PushMessage(System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
startTime = xTaskGetTickCount();
|
||||
counter = 1;
|
||||
} else {
|
||||
lv_label_set_text_static(lblPlayPause, Symbols::play);
|
||||
systemTask.PushMessage(System::Messages::EnableSleeping);
|
||||
wakeLock.Release();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "Symbols.h"
|
||||
@@ -21,7 +22,7 @@ namespace Pinetime {
|
||||
TickType_t startTime = 0;
|
||||
TickType_t tappedTime = 0;
|
||||
Controllers::MotorController& motorController;
|
||||
System::SystemTask& systemTask;
|
||||
System::WakeLock wakeLock;
|
||||
int16_t bpm = 120;
|
||||
uint8_t bpb = 4;
|
||||
uint8_t counter = 1;
|
||||
|
||||
@@ -20,7 +20,7 @@ Notifications::Notifications(DisplayApp* app,
|
||||
notificationManager {notificationManager},
|
||||
alertNotificationService {alertNotificationService},
|
||||
motorController {motorController},
|
||||
systemTask {systemTask},
|
||||
wakeLock(systemTask),
|
||||
mode {mode} {
|
||||
|
||||
notificationManager.ClearNewNotificationFlag();
|
||||
@@ -40,7 +40,7 @@ Notifications::Notifications(DisplayApp* app,
|
||||
validDisplay = false;
|
||||
}
|
||||
if (mode == Modes::Preview) {
|
||||
systemTask.PushMessage(System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) {
|
||||
motorController.StartRinging();
|
||||
} else {
|
||||
@@ -65,7 +65,6 @@ Notifications::~Notifications() {
|
||||
lv_task_del(taskRefresh);
|
||||
// make sure we stop any vibrations before exiting
|
||||
motorController.StopRinging();
|
||||
systemTask.PushMessage(System::Messages::EnableSleeping);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
@@ -82,7 +81,6 @@ void Notifications::Refresh() {
|
||||
|
||||
} else if (mode == Modes::Preview && dismissingNotification) {
|
||||
running = false;
|
||||
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController);
|
||||
|
||||
} else if (dismissingNotification) {
|
||||
dismissingNotification = false;
|
||||
@@ -113,15 +111,15 @@ void Notifications::Refresh() {
|
||||
alertNotificationService,
|
||||
motorController);
|
||||
} else {
|
||||
currentItem = std::make_unique<NotificationItem>(alertNotificationService, motorController);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
running = currentItem->IsRunning() && running;
|
||||
running = running && currentItem->IsRunning();
|
||||
}
|
||||
|
||||
void Notifications::OnPreviewInteraction() {
|
||||
systemTask.PushMessage(System::Messages::EnableSleeping);
|
||||
wakeLock.Release();
|
||||
motorController.StopRinging();
|
||||
if (timeoutLine != nullptr) {
|
||||
lv_obj_del(timeoutLine);
|
||||
@@ -173,7 +171,9 @@ bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
} else if (nextMessage.valid) {
|
||||
currentId = nextMessage.id;
|
||||
} else {
|
||||
// don't update id, won't be found be refresh and try to load latest message or no message box
|
||||
// don't update id, notification manager will try to fetch
|
||||
// but not find it. Refresh will try to load latest message
|
||||
// or dismiss to watchface
|
||||
}
|
||||
DismissToBlack();
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "components/ble/NotificationManager.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
@@ -73,7 +74,7 @@ namespace Pinetime {
|
||||
Pinetime::Controllers::NotificationManager& notificationManager;
|
||||
Pinetime::Controllers::AlertNotificationService& alertNotificationService;
|
||||
Pinetime::Controllers::MotorController& motorController;
|
||||
System::SystemTask& systemTask;
|
||||
System::WakeLock wakeLock;
|
||||
Modes mode = Modes::Normal;
|
||||
std::unique_ptr<NotificationItem> currentItem;
|
||||
Pinetime::Controllers::NotificationManager::Notification::Id currentId;
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace {
|
||||
constexpr TickType_t blinkInterval = pdMS_TO_TICKS(1000);
|
||||
}
|
||||
|
||||
StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} {
|
||||
StopWatch::StopWatch(System::SystemTask& systemTask) : wakeLock(systemTask) {
|
||||
static constexpr uint8_t btnWidth = 115;
|
||||
static constexpr uint8_t btnHeight = 80;
|
||||
btnPlayPause = lv_btn_create(lv_scr_act(), nullptr);
|
||||
@@ -79,7 +79,6 @@ StopWatch::StopWatch(System::SystemTask& systemTask) : systemTask {systemTask} {
|
||||
|
||||
StopWatch::~StopWatch() {
|
||||
lv_task_del(taskRefresh);
|
||||
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
@@ -135,7 +134,7 @@ void StopWatch::Start() {
|
||||
SetInterfaceRunning();
|
||||
startTime = xTaskGetTickCount();
|
||||
currentState = States::Running;
|
||||
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
}
|
||||
|
||||
void StopWatch::Pause() {
|
||||
@@ -145,7 +144,7 @@ void StopWatch::Pause() {
|
||||
oldTimeElapsed = laps[lapsDone];
|
||||
blinkTime = xTaskGetTickCount() + blinkInterval;
|
||||
currentState = States::Halted;
|
||||
systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
|
||||
wakeLock.Release();
|
||||
}
|
||||
|
||||
void StopWatch::Refresh() {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "portmacro_cmsis.h"
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
@@ -43,7 +44,7 @@ namespace Pinetime {
|
||||
void Start();
|
||||
void Pause();
|
||||
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Pinetime::System::WakeLock wakeLock;
|
||||
States currentState = States::Init;
|
||||
TickType_t startTime;
|
||||
TickType_t oldTimeElapsed = 0;
|
||||
|
||||
@@ -38,15 +38,16 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
|
||||
const Pinetime::Controllers::Ble& bleController,
|
||||
const Pinetime::Drivers::Watchdog& watchdog,
|
||||
Pinetime::Controllers::MotionController& motionController,
|
||||
const Pinetime::Drivers::Cst816S& touchPanel)
|
||||
: app {app},
|
||||
dateTimeController {dateTimeController},
|
||||
const Pinetime::Drivers::Cst816S& touchPanel,
|
||||
const Pinetime::Drivers::SpiNorFlash& spiNorFlash)
|
||||
: dateTimeController {dateTimeController},
|
||||
batteryController {batteryController},
|
||||
brightnessController {brightnessController},
|
||||
bleController {bleController},
|
||||
watchdog {watchdog},
|
||||
motionController {motionController},
|
||||
touchPanel {touchPanel},
|
||||
spiNorFlash {spiNorFlash},
|
||||
screens {app,
|
||||
0,
|
||||
{[this]() -> std::unique_ptr<Screen> {
|
||||
@@ -186,10 +187,12 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
|
||||
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_recolor(label, true);
|
||||
const auto& bleAddr = bleController.Address();
|
||||
auto spiFlashId = spiNorFlash.GetIdentification();
|
||||
lv_label_set_text_fmt(label,
|
||||
"#808080 BLE MAC#\n"
|
||||
" %02x:%02x:%02x:%02x:%02x:%02x"
|
||||
" %02x:%02x:%02x:%02x:%02x:%02x\n"
|
||||
"\n"
|
||||
"#808080 SPI Flash# %02x-%02x-%02x\n"
|
||||
"\n"
|
||||
"#808080 Memory heap#\n"
|
||||
" #808080 Free# %d\n"
|
||||
@@ -202,6 +205,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
|
||||
bleAddr[2],
|
||||
bleAddr[1],
|
||||
bleAddr[0],
|
||||
spiFlashId.manufacturer,
|
||||
spiFlashId.type,
|
||||
spiFlashId.density,
|
||||
xPortGetFreeHeapSize(),
|
||||
xPortGetMinimumEverFreeHeapSize(),
|
||||
mallocFailedCount,
|
||||
|
||||
@@ -29,12 +29,12 @@ namespace Pinetime {
|
||||
const Pinetime::Controllers::Ble& bleController,
|
||||
const Pinetime::Drivers::Watchdog& watchdog,
|
||||
Pinetime::Controllers::MotionController& motionController,
|
||||
const Pinetime::Drivers::Cst816S& touchPanel);
|
||||
const Pinetime::Drivers::Cst816S& touchPanel,
|
||||
const Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||
~SystemInfo() override;
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
||||
private:
|
||||
DisplayApp* app;
|
||||
Pinetime::Controllers::DateTime& dateTimeController;
|
||||
const Pinetime::Controllers::Battery& batteryController;
|
||||
Pinetime::Controllers::BrightnessController& brightnessController;
|
||||
@@ -42,6 +42,7 @@ namespace Pinetime {
|
||||
const Pinetime::Drivers::Watchdog& watchdog;
|
||||
Pinetime::Controllers::MotionController& motionController;
|
||||
const Pinetime::Drivers::Cst816S& touchPanel;
|
||||
const Pinetime::Drivers::SpiNorFlash& spiNorFlash;
|
||||
|
||||
ScreenList<5> screens;
|
||||
|
||||
|
||||
@@ -104,9 +104,7 @@ void Timer::UpdateMask() {
|
||||
|
||||
void Timer::Refresh() {
|
||||
if (timer.IsRunning()) {
|
||||
auto secondsRemaining = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining());
|
||||
minuteCounter.SetValue(secondsRemaining.count() / 60);
|
||||
secondCounter.SetValue(secondsRemaining.count() % 60);
|
||||
DisplayTime();
|
||||
} else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) {
|
||||
lv_label_set_text_static(txtPlayPause, "Reset");
|
||||
maskPosition += 15;
|
||||
@@ -119,6 +117,14 @@ void Timer::Refresh() {
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::DisplayTime() {
|
||||
displaySeconds = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining());
|
||||
if (displaySeconds.IsUpdated()) {
|
||||
minuteCounter.SetValue(displaySeconds.Get().count() / 60);
|
||||
secondCounter.SetValue(displaySeconds.Get().count() % 60);
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::SetTimerRunning() {
|
||||
minuteCounter.HideControls();
|
||||
secondCounter.HideControls();
|
||||
@@ -133,9 +139,7 @@ void Timer::SetTimerStopped() {
|
||||
|
||||
void Timer::ToggleRunning() {
|
||||
if (timer.IsRunning()) {
|
||||
auto secondsRemaining = std::chrono::duration_cast<std::chrono::seconds>(timer.GetTimeRemaining());
|
||||
minuteCounter.SetValue(secondsRemaining.count() / 60);
|
||||
secondCounter.SetValue(secondsRemaining.count() % 60);
|
||||
DisplayTime();
|
||||
timer.StopTimer();
|
||||
SetTimerStopped();
|
||||
} else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) {
|
||||
@@ -147,7 +151,6 @@ void Timer::ToggleRunning() {
|
||||
}
|
||||
|
||||
void Timer::Reset() {
|
||||
minuteCounter.SetValue(0);
|
||||
secondCounter.SetValue(0);
|
||||
DisplayTime();
|
||||
SetTimerStopped();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "displayapp/LittleVgl.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "utility/DirtyValue.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include "components/timer/Timer.h"
|
||||
@@ -26,6 +26,7 @@ namespace Pinetime::Applications {
|
||||
void SetTimerRunning();
|
||||
void SetTimerStopped();
|
||||
void UpdateMask();
|
||||
void DisplayTime();
|
||||
Pinetime::Controllers::Timer& timer;
|
||||
|
||||
lv_obj_t* btnPlayPause;
|
||||
@@ -43,6 +44,7 @@ namespace Pinetime::Applications {
|
||||
bool buttonPressing = false;
|
||||
lv_coord_t maskPosition = 0;
|
||||
TickType_t pressTime = 0;
|
||||
Utility::DirtyValue<std::chrono::seconds> displaySeconds;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Pinetime {
|
||||
int16_t minutesNighttime;
|
||||
int16_t minutesBeforeSunset;
|
||||
|
||||
const Controllers::DateTime& dateTimeController;
|
||||
Controllers::DateTime& dateTimeController;
|
||||
const Controllers::Battery& batteryController;
|
||||
const Controllers::Ble& bleController;
|
||||
Controllers::NotificationManager& notificationManager;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include "displayapp/screens/NotificationIcon.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/screens/WeatherSymbols.h"
|
||||
@@ -137,13 +138,12 @@ void WatchFaceFuzzy::Refresh() {
|
||||
if (currentWeather.IsUpdated()) {
|
||||
auto optCurrentWeather = currentWeather.Get();
|
||||
if (optCurrentWeather) {
|
||||
int16_t temp = optCurrentWeather->temperature;
|
||||
int16_t temp = optCurrentWeather->temperature.Celsius();
|
||||
char tempUnit = 'C';
|
||||
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
|
||||
temp = optCurrentWeather->temperature.Fahrenheit();
|
||||
tempUnit = 'F';
|
||||
}
|
||||
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
|
||||
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "displayapp/screens/WatchFacePineTimeStyle.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <cstdio>
|
||||
#include <displayapp/Colors.h>
|
||||
#include "displayapp/Colors.h"
|
||||
#include "displayapp/screens/BatteryIcon.h"
|
||||
#include "displayapp/screens/BleIcon.h"
|
||||
#include "displayapp/screens/NotificationIcon.h"
|
||||
@@ -543,11 +543,10 @@ void WatchFacePineTimeStyle::Refresh() {
|
||||
if (currentWeather.IsUpdated()) {
|
||||
auto optCurrentWeather = currentWeather.Get();
|
||||
if (optCurrentWeather) {
|
||||
int16_t temp = optCurrentWeather->temperature;
|
||||
int16_t temp = optCurrentWeather->temperature.Celsius();
|
||||
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
|
||||
temp = optCurrentWeather->temperature.Fahrenheit();
|
||||
}
|
||||
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||
lv_label_set_text_fmt(temperature, "%d°", temp);
|
||||
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "displayapp/screens/Weather.h"
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include "components/ble/SimpleWeatherService.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/settings/Settings.h"
|
||||
@@ -10,31 +12,27 @@
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
lv_color_t TemperatureColor(int16_t temperature) {
|
||||
if (temperature <= 0) { // freezing
|
||||
lv_color_t TemperatureColor(Pinetime::Controllers::SimpleWeatherService::Temperature temp) {
|
||||
if (temp.Celsius() <= 0) { // freezing
|
||||
return Colors::blue;
|
||||
} else if (temperature <= 400) { // ice
|
||||
} else if (temp.Celsius() <= 4) { // ice
|
||||
return LV_COLOR_CYAN;
|
||||
} else if (temperature >= 2700) { // hot
|
||||
} else if (temp.Celsius() >= 27) { // hot
|
||||
return Colors::deepOrange;
|
||||
}
|
||||
return Colors::orange; // normal
|
||||
}
|
||||
|
||||
uint8_t TemperatureStyle(int16_t temperature) {
|
||||
if (temperature <= 0) { // freezing
|
||||
uint8_t TemperatureStyle(Pinetime::Controllers::SimpleWeatherService::Temperature temp) {
|
||||
if (temp.Celsius() <= 0) { // freezing
|
||||
return LV_TABLE_PART_CELL3;
|
||||
} else if (temperature <= 400) { // ice
|
||||
} else if (temp.Celsius() <= 4) { // ice
|
||||
return LV_TABLE_PART_CELL4;
|
||||
} else if (temperature >= 2700) { // hot
|
||||
} else if (temp.Celsius() >= 27) { // hot
|
||||
return LV_TABLE_PART_CELL6;
|
||||
}
|
||||
return LV_TABLE_PART_CELL5; // normal
|
||||
}
|
||||
|
||||
int16_t RoundTemperature(int16_t temp) {
|
||||
return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
|
||||
@@ -120,22 +118,25 @@ void Weather::Refresh() {
|
||||
if (currentWeather.IsUpdated()) {
|
||||
auto optCurrentWeather = currentWeather.Get();
|
||||
if (optCurrentWeather) {
|
||||
int16_t temp = optCurrentWeather->temperature;
|
||||
int16_t minTemp = optCurrentWeather->minTemperature;
|
||||
int16_t maxTemp = optCurrentWeather->maxTemperature;
|
||||
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp));
|
||||
int16_t temp = optCurrentWeather->temperature.Celsius();
|
||||
int16_t minTemp = optCurrentWeather->minTemperature.Celsius();
|
||||
int16_t maxTemp = optCurrentWeather->maxTemperature.Celsius();
|
||||
char tempUnit = 'C';
|
||||
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
|
||||
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
|
||||
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
|
||||
temp = optCurrentWeather->temperature.Fahrenheit();
|
||||
minTemp = optCurrentWeather->minTemperature.Fahrenheit();
|
||||
maxTemp = optCurrentWeather->maxTemperature.Fahrenheit();
|
||||
tempUnit = 'F';
|
||||
}
|
||||
lv_obj_set_style_local_text_color(temperature,
|
||||
LV_LABEL_PART_MAIN,
|
||||
LV_STATE_DEFAULT,
|
||||
TemperatureColor(optCurrentWeather->temperature));
|
||||
lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||
lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
|
||||
lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
|
||||
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
|
||||
lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp));
|
||||
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
|
||||
lv_label_set_text_fmt(minTemperature, "%d°", minTemp);
|
||||
lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp);
|
||||
} else {
|
||||
lv_label_set_text(icon, "");
|
||||
lv_label_set_text(condition, "");
|
||||
@@ -152,24 +153,22 @@ void Weather::Refresh() {
|
||||
if (optCurrentForecast) {
|
||||
std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
|
||||
|
||||
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
|
||||
int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
|
||||
int16_t minTemp = optCurrentForecast->days[i].minTemperature;
|
||||
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
|
||||
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp));
|
||||
for (int i = 0; i < optCurrentForecast->nbDays; i++) {
|
||||
int16_t minTemp = optCurrentForecast->days[i]->minTemperature.Celsius();
|
||||
int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius();
|
||||
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
|
||||
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
|
||||
minTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit();
|
||||
maxTemp = optCurrentForecast->days[i]->minTemperature.Fahrenheit();
|
||||
}
|
||||
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(optCurrentForecast->days[i]->maxTemperature));
|
||||
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(optCurrentForecast->days[i]->minTemperature));
|
||||
uint8_t wday = localTime.tm_wday + i + 1;
|
||||
if (wday > 7) {
|
||||
wday -= 7;
|
||||
}
|
||||
maxTemp = RoundTemperature(maxTemp);
|
||||
minTemp = RoundTemperature(minTemp);
|
||||
const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
|
||||
lv_table_set_cell_value(forecast, 0, i, dayOfWeek);
|
||||
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId));
|
||||
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId));
|
||||
// Pad cells based on the largest number of digits on each column
|
||||
char maxPadding[3] = " ";
|
||||
char minPadding[3] = " ";
|
||||
|
||||
@@ -9,16 +9,22 @@
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
void event_handler(lv_obj_t* obj, lv_event_t event) {
|
||||
void TimeoutEventHandler(lv_obj_t* obj, lv_event_t event) {
|
||||
auto* screen = static_cast<SettingDisplay*>(obj->user_data);
|
||||
screen->UpdateSelected(obj, event);
|
||||
}
|
||||
|
||||
void AlwaysOnEventHandler(lv_obj_t* obj, lv_event_t event) {
|
||||
if (event == LV_EVENT_VALUE_CHANGED) {
|
||||
auto* screen = static_cast<SettingDisplay*>(obj->user_data);
|
||||
screen->ToggleAlwaysOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<uint16_t, 6> SettingDisplay::options;
|
||||
|
||||
SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
|
||||
: app {app}, settingsController {settingsController} {
|
||||
SettingDisplay::SettingDisplay(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} {
|
||||
|
||||
lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr);
|
||||
|
||||
@@ -49,13 +55,20 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime
|
||||
snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000);
|
||||
lv_checkbox_set_text(cbOption[i], buffer);
|
||||
cbOption[i]->user_data = this;
|
||||
lv_obj_set_event_cb(cbOption[i], event_handler);
|
||||
lv_obj_set_event_cb(cbOption[i], TimeoutEventHandler);
|
||||
SetRadioButtonStyle(cbOption[i]);
|
||||
|
||||
if (settingsController.GetScreenTimeOut() == options[i]) {
|
||||
lv_checkbox_set_checked(cbOption[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
alwaysOnCheckbox = lv_checkbox_create(container1, nullptr);
|
||||
lv_checkbox_set_text(alwaysOnCheckbox, "Always On");
|
||||
lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting());
|
||||
lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT);
|
||||
alwaysOnCheckbox->user_data = this;
|
||||
lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler);
|
||||
}
|
||||
|
||||
SettingDisplay::~SettingDisplay() {
|
||||
@@ -63,6 +76,11 @@ SettingDisplay::~SettingDisplay() {
|
||||
settingsController.SaveSettings();
|
||||
}
|
||||
|
||||
void SettingDisplay::ToggleAlwaysOn() {
|
||||
settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting());
|
||||
lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting());
|
||||
}
|
||||
|
||||
void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
|
||||
if (event == LV_EVENT_CLICKED) {
|
||||
for (unsigned int i = 0; i < options.size(); i++) {
|
||||
|
||||
@@ -14,17 +14,18 @@ namespace Pinetime {
|
||||
|
||||
class SettingDisplay : public Screen {
|
||||
public:
|
||||
SettingDisplay(DisplayApp* app, Pinetime::Controllers::Settings& settingsController);
|
||||
SettingDisplay(Pinetime::Controllers::Settings& settingsController);
|
||||
~SettingDisplay() override;
|
||||
|
||||
void UpdateSelected(lv_obj_t* object, lv_event_t event);
|
||||
void ToggleAlwaysOn();
|
||||
|
||||
private:
|
||||
DisplayApp* app;
|
||||
static constexpr std::array<uint16_t, 6> options = {5000, 7000, 10000, 15000, 20000, 30000};
|
||||
|
||||
Controllers::Settings& settingsController;
|
||||
lv_obj_t* cbOption[options.size()];
|
||||
lv_obj_t* alwaysOnCheckbox;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ bool SettingSetDateTime::OnTouchEvent(Pinetime::Applications::TouchEvents event)
|
||||
SettingSetDateTime::SettingSetDateTime(Pinetime::Applications::DisplayApp* app,
|
||||
Pinetime::Controllers::DateTime& dateTimeController,
|
||||
Pinetime::Controllers::Settings& settingsController)
|
||||
: app {app},
|
||||
dateTimeController {dateTimeController},
|
||||
: dateTimeController {dateTimeController},
|
||||
settingsController {settingsController},
|
||||
screens {app,
|
||||
0,
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Pinetime {
|
||||
void Quit();
|
||||
|
||||
private:
|
||||
DisplayApp* app;
|
||||
Controllers::DateTime& dateTimeController;
|
||||
Controllers::Settings& settingsController;
|
||||
|
||||
|
||||
@@ -54,8 +54,7 @@ SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
|
||||
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
||||
Pinetime::Controllers::Settings& settingsController,
|
||||
Pinetime::Controllers::FS& filesystem)
|
||||
: app {app},
|
||||
watchfaceItems {std::move(watchfaceItems)},
|
||||
: watchfaceItems {std::move(watchfaceItems)},
|
||||
settingsController {settingsController},
|
||||
filesystem {filesystem},
|
||||
screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
||||
|
||||
@@ -34,7 +34,6 @@ namespace Pinetime {
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
||||
private:
|
||||
DisplayApp* app;
|
||||
auto CreateScreenList() const;
|
||||
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Pinetime {
|
||||
{Symbols::home, "Watch face", Apps::SettingWatchFace},
|
||||
|
||||
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
||||
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
|
||||
{Symbols::clock, "Date & Time", Apps::SettingSetDateTime},
|
||||
{Symbols::map, "Location", Apps::SettingLocation},
|
||||
{Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
|
||||
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
||||
|
||||
+28
-30
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "drivers/Hrs3300.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <nrf_gpio.h>
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
@@ -67,40 +68,37 @@ void Hrs3300::Disable() {
|
||||
WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0);
|
||||
}
|
||||
|
||||
uint32_t Hrs3300::ReadHrs() {
|
||||
auto m = ReadRegister(static_cast<uint8_t>(Registers::C0DataM));
|
||||
auto h = ReadRegister(static_cast<uint8_t>(Registers::C0DataH));
|
||||
auto l = ReadRegister(static_cast<uint8_t>(Registers::C0dataL));
|
||||
return ((l & 0x30) << 12) | (m << 8) | ((h & 0x0f) << 4) | (l & 0x0f);
|
||||
}
|
||||
Hrs3300::PackedHrsAls Hrs3300::ReadHrsAls() {
|
||||
constexpr Registers dataRegisters[] =
|
||||
{Registers::C1dataM, Registers::C0DataM, Registers::C0DataH, Registers::C1dataH, Registers::C1dataL, Registers::C0dataL};
|
||||
// Calculate smallest register address
|
||||
constexpr uint8_t baseOffset = static_cast<uint8_t>(*std::min_element(std::begin(dataRegisters), std::end(dataRegisters)));
|
||||
// Calculate largest address to determine length of read needed
|
||||
// Add one to largest relative index to find the length
|
||||
constexpr uint8_t length = static_cast<uint8_t>(*std::max_element(std::begin(dataRegisters), std::end(dataRegisters))) - baseOffset + 1;
|
||||
|
||||
uint32_t Hrs3300::ReadAls() {
|
||||
auto m = ReadRegister(static_cast<uint8_t>(Registers::C1dataM));
|
||||
auto h = ReadRegister(static_cast<uint8_t>(Registers::C1dataH));
|
||||
auto l = ReadRegister(static_cast<uint8_t>(Registers::C1dataL));
|
||||
return ((h & 0x3f) << 11) | (m << 3) | (l & 0x07);
|
||||
}
|
||||
|
||||
void Hrs3300::SetGain(uint8_t gain) {
|
||||
constexpr uint8_t maxGain = 64U;
|
||||
gain = std::min(gain, maxGain);
|
||||
uint8_t hgain = 0;
|
||||
while ((1 << hgain) < gain) {
|
||||
++hgain;
|
||||
Hrs3300::PackedHrsAls res;
|
||||
uint8_t buf[length];
|
||||
auto ret = twiMaster.Read(twiAddress, baseOffset, buf, length);
|
||||
if (ret != TwiMaster::ErrorCodes::NoError) {
|
||||
NRF_LOG_INFO("READ ERROR");
|
||||
}
|
||||
// hrs
|
||||
uint8_t m = static_cast<uint8_t>(Registers::C0DataM) - baseOffset;
|
||||
uint8_t h = static_cast<uint8_t>(Registers::C0DataH) - baseOffset;
|
||||
uint8_t l = static_cast<uint8_t>(Registers::C0dataL) - baseOffset;
|
||||
// There are two extra bits (17 and 18) but they are not read here
|
||||
// as resolutions >16bit aren't practically useful (too slow) and
|
||||
// all hrs values throughout InfiniTime are 16bit
|
||||
res.hrs = (buf[m] << 8) | ((buf[h] & 0x0f) << 4) | (buf[l] & 0x0f);
|
||||
|
||||
WriteRegister(static_cast<uint8_t>(Registers::Hgain), hgain << 2);
|
||||
}
|
||||
// als
|
||||
m = static_cast<uint8_t>(Registers::C1dataM) - baseOffset;
|
||||
h = static_cast<uint8_t>(Registers::C1dataH) - baseOffset;
|
||||
l = static_cast<uint8_t>(Registers::C1dataL) - baseOffset;
|
||||
res.als = ((buf[h] & 0x3f) << 11) | (buf[m] << 3) | (buf[l] & 0x07);
|
||||
|
||||
void Hrs3300::SetDrive(uint8_t drive) {
|
||||
auto en = ReadRegister(static_cast<uint8_t>(Registers::Enable));
|
||||
auto pd = ReadRegister(static_cast<uint8_t>(Registers::PDriver));
|
||||
|
||||
en = (en & 0xf7) | ((drive & 2) << 2);
|
||||
pd = (pd & 0xbf) | ((drive & 1) << 6);
|
||||
|
||||
WriteRegister(static_cast<uint8_t>(Registers::Enable), en);
|
||||
WriteRegister(static_cast<uint8_t>(Registers::PDriver), pd);
|
||||
return res;
|
||||
}
|
||||
|
||||
void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) {
|
||||
|
||||
@@ -21,6 +21,11 @@ namespace Pinetime {
|
||||
Hgain = 0x17
|
||||
};
|
||||
|
||||
struct PackedHrsAls {
|
||||
uint16_t hrs;
|
||||
uint16_t als;
|
||||
};
|
||||
|
||||
Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress);
|
||||
Hrs3300(const Hrs3300&) = delete;
|
||||
Hrs3300& operator=(const Hrs3300&) = delete;
|
||||
@@ -30,10 +35,7 @@ namespace Pinetime {
|
||||
void Init();
|
||||
void Enable();
|
||||
void Disable();
|
||||
uint32_t ReadHrs();
|
||||
uint32_t ReadAls();
|
||||
void SetGain(uint8_t gain);
|
||||
void SetDrive(uint8_t drive);
|
||||
PackedHrsAls ReadHrsAls();
|
||||
|
||||
private:
|
||||
TwiMaster& twiMaster;
|
||||
|
||||
@@ -10,7 +10,7 @@ SpiNorFlash::SpiNorFlash(Spi& spi) : spi {spi} {
|
||||
}
|
||||
|
||||
void SpiNorFlash::Init() {
|
||||
device_id = ReadIdentificaion();
|
||||
device_id = ReadIdentification();
|
||||
NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d",
|
||||
device_id.manufacturer,
|
||||
device_id.type,
|
||||
@@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() {
|
||||
uint8_t cmd[cmdSize] = {static_cast<uint8_t>(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03};
|
||||
uint8_t id = 0;
|
||||
spi.Read(reinterpret_cast<uint8_t*>(&cmd), cmdSize, &id, 1);
|
||||
auto devId = device_id = ReadIdentificaion();
|
||||
auto devId = device_id = ReadIdentification();
|
||||
if (devId.type != device_id.type) {
|
||||
NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed");
|
||||
} else {
|
||||
@@ -41,7 +41,7 @@ void SpiNorFlash::Wakeup() {
|
||||
NRF_LOG_INFO("[SpiNorFlash] Wakeup")
|
||||
}
|
||||
|
||||
SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() {
|
||||
SpiNorFlash::Identification SpiNorFlash::ReadIdentification() {
|
||||
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
|
||||
Identification identification;
|
||||
spi.Read(&cmd, 1, reinterpret_cast<uint8_t*>(&identification), sizeof(Identification));
|
||||
@@ -145,3 +145,7 @@ void SpiNorFlash::Write(uint32_t address, const uint8_t* buffer, size_t size) {
|
||||
len -= toWrite;
|
||||
}
|
||||
}
|
||||
|
||||
SpiNorFlash::Identification SpiNorFlash::GetIdentification() const {
|
||||
return device_id;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Pinetime {
|
||||
uint8_t density = 0;
|
||||
};
|
||||
|
||||
Identification ReadIdentificaion();
|
||||
uint8_t ReadStatusRegister();
|
||||
bool WriteInProgress();
|
||||
bool WriteEnabled();
|
||||
@@ -33,6 +32,8 @@ namespace Pinetime {
|
||||
bool ProgramFailed();
|
||||
bool EraseFailed();
|
||||
|
||||
Identification GetIdentification() const;
|
||||
|
||||
void Init();
|
||||
void Uninit();
|
||||
|
||||
@@ -40,6 +41,8 @@ namespace Pinetime {
|
||||
void Wakeup();
|
||||
|
||||
private:
|
||||
Identification ReadIdentification();
|
||||
|
||||
enum class Commands : uint8_t {
|
||||
PageProgram = 0x02,
|
||||
Read = 0x03,
|
||||
|
||||
+124
-31
@@ -1,3 +1,4 @@
|
||||
#include <cstring>
|
||||
#include "drivers/St7789.h"
|
||||
#include <hal/nrf_gpio.h>
|
||||
#include <nrfx_log.h>
|
||||
@@ -15,17 +16,22 @@ void St7789::Init() {
|
||||
nrf_gpio_pin_set(pinReset);
|
||||
HardwareReset();
|
||||
SoftwareReset();
|
||||
Command2Enable();
|
||||
SleepOut();
|
||||
ColMod();
|
||||
PixelFormat();
|
||||
MemoryDataAccessControl();
|
||||
ColumnAddressSet();
|
||||
RowAddressSet();
|
||||
SetAddrWindow(0, 0, Width, Height);
|
||||
// P8B Mirrored version does not need display inversion.
|
||||
#ifndef DRIVER_DISPLAY_MIRROR
|
||||
DisplayInversionOn();
|
||||
#endif
|
||||
PorchSet();
|
||||
FrameRateNormalSet();
|
||||
IdleFrameRateOff();
|
||||
NormalModeOn();
|
||||
SetVdv();
|
||||
PowerControl();
|
||||
GateControl();
|
||||
DisplayOn();
|
||||
}
|
||||
|
||||
@@ -63,6 +69,17 @@ void St7789::SoftwareReset() {
|
||||
vTaskDelay(pdMS_TO_TICKS(125));
|
||||
}
|
||||
|
||||
void St7789::Command2Enable() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::Command2Enable));
|
||||
constexpr uint8_t args[] = {
|
||||
0x5a, // Constant
|
||||
0x69, // Constant
|
||||
0x02, // Constant
|
||||
0x01, // Enable
|
||||
};
|
||||
WriteData(args, sizeof(args));
|
||||
}
|
||||
|
||||
void St7789::SleepOut() {
|
||||
if (!sleepIn) {
|
||||
return;
|
||||
@@ -97,8 +114,9 @@ void St7789::SleepIn() {
|
||||
sleepIn = true;
|
||||
}
|
||||
|
||||
void St7789::ColMod() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::ColMod));
|
||||
void St7789::PixelFormat() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::PixelFormat));
|
||||
// 65K colours, 16-bit per pixel
|
||||
WriteData(0x55);
|
||||
}
|
||||
|
||||
@@ -118,22 +136,6 @@ void St7789::MemoryDataAccessControl() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void St7789::ColumnAddressSet() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet));
|
||||
WriteData(0x00);
|
||||
WriteData(0x00);
|
||||
WriteData(Width >> 8u);
|
||||
WriteData(Width & 0xffu);
|
||||
}
|
||||
|
||||
void St7789::RowAddressSet() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::RowAddressSet));
|
||||
WriteData(0x00);
|
||||
WriteData(0x00);
|
||||
WriteData(320u >> 8u);
|
||||
WriteData(320u & 0xffu);
|
||||
}
|
||||
|
||||
void St7789::DisplayInversionOn() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
|
||||
}
|
||||
@@ -142,22 +144,97 @@ void St7789::NormalModeOn() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
|
||||
}
|
||||
|
||||
void St7789::IdleModeOn() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOn));
|
||||
}
|
||||
|
||||
void St7789::IdleModeOff() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOff));
|
||||
}
|
||||
|
||||
void St7789::PorchSet() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::Porch));
|
||||
constexpr uint8_t args[] = {
|
||||
0x02, // Normal mode front porch
|
||||
0x03, // Normal mode back porch
|
||||
0x01, // Porch control enable
|
||||
0xed, // Idle mode front:back porch
|
||||
0xed, // Partial mode front:back porch (partial mode unused but set anyway)
|
||||
};
|
||||
WriteData(args, sizeof(args));
|
||||
}
|
||||
|
||||
void St7789::FrameRateNormalSet() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::FrameRateNormal));
|
||||
// Note that the datasheet table is imprecise - see formula below table
|
||||
WriteData(0x0a);
|
||||
}
|
||||
|
||||
void St7789::IdleFrameRateOn() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
|
||||
// According to the datasheet, these controls should apply only to partial/idle mode
|
||||
// However they appear to apply to normal mode, so we have to enable/disable
|
||||
// every time we enter/exit always on
|
||||
constexpr uint8_t args[] = {
|
||||
0x12, // Enable frame rate control for partial/idle mode, 4x frame divider
|
||||
0x1e, // Idle mode frame rate
|
||||
0x1e, // Partial mode frame rate (unused)
|
||||
};
|
||||
WriteData(args, sizeof(args));
|
||||
}
|
||||
|
||||
void St7789::IdleFrameRateOff() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
|
||||
constexpr uint8_t args[] = {
|
||||
0x00, // Disable frame rate control and divider
|
||||
0x0a, // Idle mode frame rate (normal)
|
||||
0x0a, // Partial mode frame rate (normal, unused)
|
||||
};
|
||||
WriteData(args, sizeof(args));
|
||||
}
|
||||
|
||||
void St7789::DisplayOn() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::DisplayOn));
|
||||
}
|
||||
|
||||
void St7789::PowerControl() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::PowerControl1));
|
||||
constexpr uint8_t args[] = {
|
||||
0xa4, // Constant
|
||||
0x00, // Lowest possible voltages
|
||||
};
|
||||
WriteData(args, sizeof(args));
|
||||
|
||||
WriteCommand(static_cast<uint8_t>(Commands::PowerControl2));
|
||||
// Lowest possible boost circuit clocks
|
||||
WriteData(0xb3);
|
||||
}
|
||||
|
||||
void St7789::GateControl() {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::GateControl));
|
||||
// Lowest possible VGL/VGH
|
||||
WriteData(0x00);
|
||||
}
|
||||
|
||||
void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
|
||||
WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet));
|
||||
WriteData(x0 >> 8);
|
||||
WriteData(x0 & 0xff);
|
||||
WriteData(x1 >> 8);
|
||||
WriteData(x1 & 0xff);
|
||||
uint8_t colArgs[] = {
|
||||
static_cast<uint8_t>(x0 >> 8), // x start MSB
|
||||
static_cast<uint8_t>(x0), // x start LSB
|
||||
static_cast<uint8_t>(x1 >> 8), // x end MSB
|
||||
static_cast<uint8_t>(x1) // x end LSB
|
||||
};
|
||||
WriteData(colArgs, sizeof(colArgs));
|
||||
|
||||
WriteCommand(static_cast<uint8_t>(Commands::RowAddressSet));
|
||||
WriteData(y0 >> 8);
|
||||
WriteData(y0 & 0xff);
|
||||
WriteData(y1 >> 8);
|
||||
WriteData(y1 & 0xff);
|
||||
uint8_t rowArgs[] = {
|
||||
static_cast<uint8_t>(y0 >> 8), // y start MSB
|
||||
static_cast<uint8_t>(y0), // y start LSB
|
||||
static_cast<uint8_t>(y1 >> 8), // y end MSB
|
||||
static_cast<uint8_t>(y1) // y end LSB
|
||||
};
|
||||
memcpy(addrWindowArgs, rowArgs, sizeof(rowArgs));
|
||||
WriteData(addrWindowArgs, sizeof(addrWindowArgs));
|
||||
}
|
||||
|
||||
void St7789::WriteToRam(const uint8_t* data, size_t size) {
|
||||
@@ -179,8 +256,12 @@ void St7789::DisplayOff() {
|
||||
void St7789::VerticalScrollStartAddress(uint16_t line) {
|
||||
verticalScrollingStartAddress = line;
|
||||
WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollStartAddress));
|
||||
WriteData(line >> 8u);
|
||||
WriteData(line & 0x00ffu);
|
||||
uint8_t args[] = {
|
||||
static_cast<uint8_t>(line >> 8), // Frame memory line pointer MSB
|
||||
static_cast<uint8_t>(line) // Frame memory line pointer LSB
|
||||
};
|
||||
memcpy(verticalScrollArgs, args, sizeof(args));
|
||||
WriteData(verticalScrollArgs, sizeof(verticalScrollArgs));
|
||||
}
|
||||
|
||||
void St7789::Uninit() {
|
||||
@@ -202,6 +283,18 @@ void St7789::HardwareReset() {
|
||||
vTaskDelay(pdMS_TO_TICKS(125));
|
||||
}
|
||||
|
||||
void St7789::LowPowerOn() {
|
||||
IdleModeOn();
|
||||
IdleFrameRateOn();
|
||||
NRF_LOG_INFO("[LCD] Low power mode");
|
||||
}
|
||||
|
||||
void St7789::LowPowerOff() {
|
||||
IdleModeOff();
|
||||
IdleFrameRateOff();
|
||||
NRF_LOG_INFO("[LCD] Normal power mode");
|
||||
}
|
||||
|
||||
void St7789::Sleep() {
|
||||
SleepIn();
|
||||
nrf_gpio_cfg_default(pinDataCommand);
|
||||
|
||||
+25
-4
@@ -24,6 +24,8 @@ namespace Pinetime {
|
||||
|
||||
void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
|
||||
|
||||
void LowPowerOn();
|
||||
void LowPowerOff();
|
||||
void Sleep();
|
||||
void Wakeup();
|
||||
|
||||
@@ -37,16 +39,25 @@ namespace Pinetime {
|
||||
|
||||
void HardwareReset();
|
||||
void SoftwareReset();
|
||||
void Command2Enable();
|
||||
void SleepOut();
|
||||
void EnsureSleepOutPostDelay();
|
||||
void SleepIn();
|
||||
void ColMod();
|
||||
void PixelFormat();
|
||||
void MemoryDataAccessControl();
|
||||
void DisplayInversionOn();
|
||||
void NormalModeOn();
|
||||
void WriteToRam(const uint8_t* data, size_t size);
|
||||
void IdleModeOn();
|
||||
void IdleModeOff();
|
||||
void FrameRateNormalSet();
|
||||
void IdleFrameRateOff();
|
||||
void IdleFrameRateOn();
|
||||
void DisplayOn();
|
||||
void DisplayOff();
|
||||
void PowerControl();
|
||||
void GateControl();
|
||||
void PorchSet();
|
||||
|
||||
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
||||
void SetVdv();
|
||||
@@ -68,16 +79,26 @@ namespace Pinetime {
|
||||
MemoryDataAccessControl = 0x36,
|
||||
VerticalScrollDefinition = 0x33,
|
||||
VerticalScrollStartAddress = 0x37,
|
||||
ColMod = 0x3a,
|
||||
IdleModeOff = 0x38,
|
||||
IdleModeOn = 0x39,
|
||||
PixelFormat = 0x3a,
|
||||
FrameRateIdle = 0xb3,
|
||||
FrameRateNormal = 0xc6,
|
||||
VdvSet = 0xc4,
|
||||
Command2Enable = 0xdf,
|
||||
PowerControl1 = 0xd0,
|
||||
PowerControl2 = 0xe8,
|
||||
GateControl = 0xb7,
|
||||
Porch = 0xb2,
|
||||
};
|
||||
void WriteData(uint8_t data);
|
||||
void WriteData(const uint8_t* data, size_t size);
|
||||
void ColumnAddressSet();
|
||||
|
||||
static constexpr uint16_t Width = 240;
|
||||
static constexpr uint16_t Height = 320;
|
||||
void RowAddressSet();
|
||||
|
||||
uint8_t addrWindowArgs[4];
|
||||
uint8_t verticalScrollArgs[2];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ void HeartRateTask::Work() {
|
||||
}
|
||||
|
||||
if (measurementStarted) {
|
||||
int8_t ambient = ppg.Preprocess(heartRateSensor.ReadHrs(), heartRateSensor.ReadAls());
|
||||
auto sensorData = heartRateSensor.ReadHrsAls();
|
||||
int8_t ambient = ppg.Preprocess(sensorData.hrs, sensorData.als);
|
||||
int bpm = ppg.HeartRate();
|
||||
|
||||
// If ambient light detected or a reset requested (bpm < 0)
|
||||
|
||||
Submodule src/libs/QCBOR deleted from 56b17bf9f7
+4
-3
@@ -104,7 +104,7 @@ Pinetime::Controllers::DateTime dateTimeController {settingsController};
|
||||
Pinetime::Drivers::Watchdog watchdog;
|
||||
Pinetime::Controllers::NotificationManager notificationManager;
|
||||
Pinetime::Controllers::MotionController motionController;
|
||||
Pinetime::Controllers::AlarmController alarmController {dateTimeController};
|
||||
Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs};
|
||||
Pinetime::Controllers::TouchHandler touchHandler;
|
||||
Pinetime::Controllers::ButtonHandler buttonHandler;
|
||||
Pinetime::Controllers::BrightnessController brightnessController {};
|
||||
@@ -123,7 +123,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
|
||||
alarmController,
|
||||
brightnessController,
|
||||
touchHandler,
|
||||
fs);
|
||||
fs,
|
||||
spiNorFlash);
|
||||
|
||||
Pinetime::System::SystemTask systemTask(spi,
|
||||
spiNorFlash,
|
||||
@@ -167,7 +168,7 @@ std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> NoI
|
||||
|
||||
void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
|
||||
if (pin == Pinetime::PinMap::Cst816sIrq) {
|
||||
systemTask.OnTouchEvent();
|
||||
systemTask.PushMessage(Pinetime::System::Messages::OnTouchEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ message(STATUS "Using ${LV_IMG_CONV} to generate font files")
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
# FindPython3 module introduces with CMake 3.12
|
||||
# https://cmake.org/cmake/help/latest/module/FindPython3.html
|
||||
set(Python3_FIND_STRATEGY LOCATION) # https://discourse.cmake.org/t/find-package-python3-is-not-finding-the-correct-python/10563
|
||||
find_package(Python3 REQUIRED)
|
||||
else()
|
||||
set(Python3_EXECUTABLE "python")
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Pinetime {
|
||||
enum class Messages : uint8_t {
|
||||
GoToSleep,
|
||||
GoToRunning,
|
||||
TouchWakeUp,
|
||||
OnNewTime,
|
||||
OnNewNotification,
|
||||
OnNewCall,
|
||||
@@ -17,6 +16,7 @@ namespace Pinetime {
|
||||
HandleButtonEvent,
|
||||
HandleButtonTimerEvent,
|
||||
OnDisplayTaskSleeping,
|
||||
OnDisplayTaskAOD,
|
||||
EnableSleeping,
|
||||
DisableSleeping,
|
||||
OnNewDay,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "systemtask/SystemTask.h"
|
||||
#if configUSE_TRACE_FACILITY == 1
|
||||
#if NRF_LOG_ENABLED
|
||||
// FreeRtosMonitor
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
|
||||
+138
-153
@@ -102,7 +102,9 @@ void SystemTask::Work() {
|
||||
watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
|
||||
watchdog.Start();
|
||||
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason()));
|
||||
APP_GPIOTE_INIT(2);
|
||||
if (!nrfx_gpiote_is_init()) {
|
||||
nrfx_gpiote_init();
|
||||
}
|
||||
|
||||
spi.Init();
|
||||
spiNorFlash.Init();
|
||||
@@ -187,37 +189,71 @@ void SystemTask::Work() {
|
||||
if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) {
|
||||
switch (msg) {
|
||||
case Messages::EnableSleeping:
|
||||
// Make sure that exiting an app doesn't enable sleeping,
|
||||
// if the exiting was caused by a firmware update
|
||||
if (!bleController.IsFirmwareUpdating()) {
|
||||
doNotGoToSleep = false;
|
||||
}
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
|
||||
wakeLocksHeld--;
|
||||
break;
|
||||
case Messages::DisableSleeping:
|
||||
doNotGoToSleep = true;
|
||||
GoToRunning();
|
||||
wakeLocksHeld++;
|
||||
break;
|
||||
case Messages::GoToRunning:
|
||||
spi.Wakeup();
|
||||
|
||||
// Double Tap needs the touch screen to be in normal mode
|
||||
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
|
||||
touchPanel.Wakeup();
|
||||
}
|
||||
|
||||
spiNorFlash.Wakeup();
|
||||
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
|
||||
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
|
||||
|
||||
if (bleController.IsRadioEnabled() && !bleController.IsConnected()) {
|
||||
nimbleController.RestartFastAdv();
|
||||
}
|
||||
|
||||
state = SystemTaskState::Running;
|
||||
GoToRunning();
|
||||
break;
|
||||
case Messages::TouchWakeUp: {
|
||||
if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) {
|
||||
case Messages::GoToSleep:
|
||||
GoToSleep();
|
||||
break;
|
||||
case Messages::OnNewTime:
|
||||
if (alarmController.IsEnabled()) {
|
||||
alarmController.ScheduleAlarm();
|
||||
}
|
||||
break;
|
||||
case Messages::OnNewNotification:
|
||||
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) {
|
||||
if (IsSleeping()) {
|
||||
GoToRunning();
|
||||
}
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
|
||||
}
|
||||
break;
|
||||
case Messages::SetOffAlarm:
|
||||
GoToRunning();
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
|
||||
break;
|
||||
case Messages::BleConnected:
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
|
||||
isBleDiscoveryTimerRunning = true;
|
||||
bleDiscoveryTimer = 5;
|
||||
break;
|
||||
case Messages::BleFirmwareUpdateStarted:
|
||||
GoToRunning();
|
||||
wakeLocksHeld++;
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
|
||||
break;
|
||||
case Messages::BleFirmwareUpdateFinished:
|
||||
if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
wakeLocksHeld--;
|
||||
break;
|
||||
case Messages::StartFileTransfer:
|
||||
NRF_LOG_INFO("[systemtask] FS Started");
|
||||
GoToRunning();
|
||||
wakeLocksHeld++;
|
||||
// TODO add intent of fs access icon or something
|
||||
break;
|
||||
case Messages::StopFileTransfer:
|
||||
NRF_LOG_INFO("[systemtask] FS Stopped");
|
||||
wakeLocksHeld--;
|
||||
// TODO add intent of fs access icon or something
|
||||
break;
|
||||
case Messages::OnTouchEvent:
|
||||
// Finish immediately if no new events
|
||||
if (!touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) {
|
||||
break;
|
||||
}
|
||||
if (state == SystemTaskState::Running) {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
|
||||
} else {
|
||||
// If asleep, check for touch panel wake triggers
|
||||
auto gesture = touchHandler.GestureGet();
|
||||
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
||||
gesture != Pinetime::Applications::TouchEvents::None &&
|
||||
@@ -229,75 +265,6 @@ void SystemTask::Work() {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Messages::GoToSleep:
|
||||
if (doNotGoToSleep) {
|
||||
break;
|
||||
}
|
||||
state = SystemTaskState::GoingToSleep; // Already set in PushMessage()
|
||||
NRF_LOG_INFO("[systemtask] Going to sleep");
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
|
||||
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
|
||||
break;
|
||||
case Messages::OnNewTime:
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
|
||||
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
|
||||
alarmController.ScheduleAlarm();
|
||||
}
|
||||
break;
|
||||
case Messages::OnNewNotification:
|
||||
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) {
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
} else {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
|
||||
}
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
|
||||
}
|
||||
break;
|
||||
case Messages::SetOffAlarm:
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
}
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
|
||||
break;
|
||||
case Messages::BleConnected:
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
|
||||
isBleDiscoveryTimerRunning = true;
|
||||
bleDiscoveryTimer = 5;
|
||||
break;
|
||||
case Messages::BleFirmwareUpdateStarted:
|
||||
doNotGoToSleep = true;
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
}
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
|
||||
break;
|
||||
case Messages::BleFirmwareUpdateFinished:
|
||||
if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
doNotGoToSleep = false;
|
||||
break;
|
||||
case Messages::StartFileTransfer:
|
||||
NRF_LOG_INFO("[systemtask] FS Started");
|
||||
doNotGoToSleep = true;
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
}
|
||||
// TODO add intent of fs access icon or something
|
||||
break;
|
||||
case Messages::StopFileTransfer:
|
||||
NRF_LOG_INFO("[systemtask] FS Stopped");
|
||||
doNotGoToSleep = false;
|
||||
// TODO add intent of fs access icon or something
|
||||
break;
|
||||
case Messages::OnTouchEvent:
|
||||
if (touchHandler.ProcessTouchInfo(touchPanel.GetTouchInfo())) {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
|
||||
}
|
||||
break;
|
||||
case Messages::HandleButtonEvent: {
|
||||
Controllers::ButtonActions action = Controllers::ButtonActions::None;
|
||||
if (nrf_gpio_pin_read(Pinetime::PinMap::Button) == 0) {
|
||||
@@ -318,19 +285,35 @@ void SystemTask::Work() {
|
||||
HandleButtonAction(action);
|
||||
} break;
|
||||
case Messages::OnDisplayTaskSleeping:
|
||||
case Messages::OnDisplayTaskAOD:
|
||||
// The state was set to GoingToSleep when GoToSleep() was called
|
||||
// If the state is no longer GoingToSleep, we have since transitioned back to Running
|
||||
// In this case absorb the OnDisplayTaskSleeping/AOD
|
||||
// as DisplayApp is about to receive GoToRunning
|
||||
if (state != SystemTaskState::GoingToSleep) {
|
||||
break;
|
||||
}
|
||||
if (BootloaderVersion::IsValid()) {
|
||||
// First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH
|
||||
// if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions.
|
||||
spiNorFlash.Sleep();
|
||||
}
|
||||
spi.Sleep();
|
||||
|
||||
// Must keep SPI awake when still updating the display for always on
|
||||
if (msg == Messages::OnDisplayTaskSleeping) {
|
||||
spi.Sleep();
|
||||
}
|
||||
|
||||
// Double Tap needs the touch screen to be in normal mode
|
||||
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
|
||||
touchPanel.Sleep();
|
||||
}
|
||||
|
||||
state = SystemTaskState::Sleeping;
|
||||
if (msg == Messages::OnDisplayTaskSleeping) {
|
||||
state = SystemTaskState::Sleeping;
|
||||
} else {
|
||||
state = SystemTaskState::AODSleeping;
|
||||
}
|
||||
break;
|
||||
case Messages::OnNewDay:
|
||||
// We might be sleeping (with TWI device disabled.
|
||||
@@ -340,39 +323,23 @@ void SystemTask::Work() {
|
||||
case Messages::OnNewHour:
|
||||
using Pinetime::Controllers::AlarmController;
|
||||
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
||||
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
|
||||
alarmController.State() != AlarmController::AlarmState::Alerting) {
|
||||
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
|
||||
// request running first and repush the chime message
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
PushMessage(msg);
|
||||
} else {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
||||
}
|
||||
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours && !alarmController.IsAlerting()) {
|
||||
GoToRunning();
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
||||
}
|
||||
break;
|
||||
case Messages::OnNewHalfHour:
|
||||
using Pinetime::Controllers::AlarmController;
|
||||
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
||||
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
|
||||
alarmController.State() != AlarmController::AlarmState::Alerting) {
|
||||
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
|
||||
// request running first and repush the chime message
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
PushMessage(msg);
|
||||
} else {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
||||
}
|
||||
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours && !alarmController.IsAlerting()) {
|
||||
GoToRunning();
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
||||
}
|
||||
break;
|
||||
case Messages::OnChargingEvent:
|
||||
batteryController.ReadPowerState();
|
||||
GoToRunning();
|
||||
displayApp.PushMessage(Applications::Display::Messages::OnChargingEvent);
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
}
|
||||
break;
|
||||
case Messages::MeasureBatteryTimerExpired:
|
||||
batteryController.MeasureVoltage();
|
||||
@@ -381,9 +348,7 @@ void SystemTask::Work() {
|
||||
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
|
||||
break;
|
||||
case Messages::OnPairing:
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
GoToRunning();
|
||||
}
|
||||
GoToRunning();
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey);
|
||||
break;
|
||||
case Messages::BleRadioEnableToggle:
|
||||
@@ -410,8 +375,6 @@ void SystemTask::Work() {
|
||||
}
|
||||
|
||||
monitor.Process();
|
||||
uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG);
|
||||
dateTimeController.UpdateTime(systick_counter);
|
||||
NoInit_BackUpTime = dateTimeController.CurrentDateTime();
|
||||
if (nrf_gpio_pin_read(PinMap::Button) == 0) {
|
||||
watchdog.Reload();
|
||||
@@ -420,11 +383,55 @@ void SystemTask::Work() {
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void SystemTask::UpdateMotion() {
|
||||
if (state == SystemTaskState::GoingToSleep || state == SystemTaskState::WakingUp) {
|
||||
void SystemTask::GoToRunning() {
|
||||
if (state == SystemTaskState::Running) {
|
||||
return;
|
||||
}
|
||||
if (state == SystemTaskState::Sleeping || state == SystemTaskState::AODSleeping) {
|
||||
// SPI only switched off when entering Sleeping, not AOD or GoingToSleep
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
spi.Wakeup();
|
||||
}
|
||||
|
||||
// Double Tap needs the touch screen to be in normal mode
|
||||
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
|
||||
touchPanel.Wakeup();
|
||||
}
|
||||
|
||||
spiNorFlash.Wakeup();
|
||||
}
|
||||
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
|
||||
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
|
||||
|
||||
if (bleController.IsRadioEnabled() && !bleController.IsConnected()) {
|
||||
nimbleController.RestartFastAdv();
|
||||
}
|
||||
|
||||
state = SystemTaskState::Running;
|
||||
};
|
||||
|
||||
void SystemTask::GoToSleep() {
|
||||
if (IsSleeping()) {
|
||||
return;
|
||||
}
|
||||
if (IsSleepDisabled()) {
|
||||
return;
|
||||
}
|
||||
NRF_LOG_INFO("[systemtask] Going to sleep");
|
||||
if (settingsController.GetAlwaysOnDisplay()) {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToAOD);
|
||||
} else {
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
|
||||
}
|
||||
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
|
||||
|
||||
state = SystemTaskState::GoingToSleep;
|
||||
};
|
||||
|
||||
void SystemTask::UpdateMotion() {
|
||||
// Only consider disabling motion updates specifically in the Sleeping state
|
||||
// AOD needs motion on to show up to date step counts
|
||||
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
|
||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
|
||||
motionController.GetService()->IsMotionNotificationSubscribed())) {
|
||||
@@ -450,7 +457,7 @@ void SystemTask::UpdateMotion() {
|
||||
}
|
||||
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running &&
|
||||
motionController.ShouldLowerSleep()) {
|
||||
PushMessage(Messages::GoToSleep);
|
||||
GoToSleep();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,14 +466,14 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
|
||||
return;
|
||||
}
|
||||
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
|
||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
|
||||
|
||||
using Actions = Controllers::ButtonActions;
|
||||
|
||||
switch (action) {
|
||||
case Actions::Click:
|
||||
// If the first action after fast wakeup is a click, it should be ignored.
|
||||
if (!fastWakeUpDone && state != SystemTaskState::GoingToSleep) {
|
||||
if (!fastWakeUpDone) {
|
||||
displayApp.PushMessage(Applications::Display::Messages::ButtonPushed);
|
||||
}
|
||||
break;
|
||||
@@ -486,29 +493,7 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
|
||||
fastWakeUpDone = false;
|
||||
}
|
||||
|
||||
void SystemTask::GoToRunning() {
|
||||
if (state == SystemTaskState::Sleeping) {
|
||||
state = SystemTaskState::WakingUp;
|
||||
PushMessage(Messages::GoToRunning);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTask::OnTouchEvent() {
|
||||
if (state == SystemTaskState::Running) {
|
||||
PushMessage(Messages::OnTouchEvent);
|
||||
} else if (state == SystemTaskState::Sleeping) {
|
||||
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or
|
||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
|
||||
PushMessage(Messages::TouchWakeUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTask::PushMessage(System::Messages msg) {
|
||||
if (msg == Messages::GoToSleep && !doNotGoToSleep) {
|
||||
state = SystemTaskState::GoingToSleep;
|
||||
}
|
||||
|
||||
if (in_isr()) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Pinetime {
|
||||
namespace System {
|
||||
class SystemTask {
|
||||
public:
|
||||
enum class SystemTaskState { Sleeping, Running, GoingToSleep, WakingUp };
|
||||
enum class SystemTaskState { Sleeping, Running, GoingToSleep, AODSleeping };
|
||||
SystemTask(Drivers::SpiMaster& spi,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
||||
Drivers::TwiMaster& twiMaster,
|
||||
@@ -77,13 +77,8 @@ namespace Pinetime {
|
||||
void Start();
|
||||
void PushMessage(Messages msg);
|
||||
|
||||
void OnTouchEvent();
|
||||
|
||||
void OnIdle();
|
||||
void OnDim();
|
||||
|
||||
bool IsSleepDisabled() {
|
||||
return doNotGoToSleep;
|
||||
return wakeLocksHeld > 0;
|
||||
}
|
||||
|
||||
Pinetime::Controllers::NimbleController& nimble() {
|
||||
@@ -91,7 +86,7 @@ namespace Pinetime {
|
||||
};
|
||||
|
||||
bool IsSleeping() const {
|
||||
return state == SystemTaskState::Sleeping || state == SystemTaskState::WakingUp;
|
||||
return state != SystemTaskState::Running;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -127,13 +122,14 @@ namespace Pinetime {
|
||||
bool isBleDiscoveryTimerRunning = false;
|
||||
uint8_t bleDiscoveryTimer = 0;
|
||||
TimerHandle_t measureBatteryTimer;
|
||||
bool doNotGoToSleep = false;
|
||||
uint8_t wakeLocksHeld = 0;
|
||||
SystemTaskState state = SystemTaskState::Running;
|
||||
|
||||
void HandleButtonAction(Controllers::ButtonActions action);
|
||||
bool fastWakeUpDone = false;
|
||||
|
||||
void GoToRunning();
|
||||
void GoToSleep();
|
||||
void UpdateMotion();
|
||||
bool stepCounterMustBeReset = false;
|
||||
static constexpr TickType_t batteryMeasurementPeriod = pdMS_TO_TICKS(10 * 60 * 1000);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "systemtask/WakeLock.h"
|
||||
|
||||
using namespace Pinetime::System;
|
||||
|
||||
WakeLock::WakeLock(SystemTask& systemTask) : systemTask {systemTask} {
|
||||
lockHeld = false;
|
||||
}
|
||||
|
||||
WakeLock::~WakeLock() {
|
||||
Release();
|
||||
}
|
||||
|
||||
void WakeLock::Lock() {
|
||||
if (lockHeld) {
|
||||
return;
|
||||
}
|
||||
systemTask.PushMessage(Messages::DisableSleeping);
|
||||
lockHeld = true;
|
||||
}
|
||||
|
||||
void WakeLock::Release() {
|
||||
if (!lockHeld) {
|
||||
return;
|
||||
}
|
||||
systemTask.PushMessage(Messages::EnableSleeping);
|
||||
lockHeld = false;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace System {
|
||||
class WakeLock {
|
||||
public:
|
||||
WakeLock(SystemTask& systemTask);
|
||||
~WakeLock();
|
||||
void Lock();
|
||||
void Release();
|
||||
|
||||
private:
|
||||
bool lockHeld;
|
||||
SystemTask& systemTask;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user