diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2b69b8b02..b971a5333f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -412,6 +412,7 @@ list(APPEND SOURCE_FILES displayapp/screens/settings/SettingWakeUp.cpp displayapp/screens/settings/SettingDisplay.cpp displayapp/screens/settings/SettingSteps.cpp + displayapp/screens/settings/SettingSleep.cpp displayapp/screens/settings/SettingSetDateTime.cpp displayapp/screens/settings/SettingSetDate.cpp displayapp/screens/settings/SettingSetTime.cpp diff --git a/src/components/motion/MotionController.cpp b/src/components/motion/MotionController.cpp index 72507ac5cd..55db6fa2f4 100644 --- a/src/components/motion/MotionController.cpp +++ b/src/components/motion/MotionController.cpp @@ -35,6 +35,9 @@ namespace { } } +MotionController::MotionController(Controllers::Settings& settingsController) : settingsController {settingsController} { +} + void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps) { if (this->nbSteps != nbSteps && service != nullptr) { service->OnNewStepCountValue(nbSteps); @@ -57,9 +60,16 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps) stats = GetAccelStats(); int32_t deltaSteps = nbSteps - this->nbSteps; + if (deltaSteps > 0) { - currentTripSteps += deltaSteps; + if (settingsController.isSleepOptionOn(Settings::SleepOption::IgnoreSteps) && + settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::Sleep) { + ignoreSteps += deltaSteps; + } else { + currentTripSteps += deltaSteps; + } } + this->nbSteps = nbSteps; } diff --git a/src/components/motion/MotionController.h b/src/components/motion/MotionController.h index be0241d32e..853f39945f 100644 --- a/src/components/motion/MotionController.h +++ b/src/components/motion/MotionController.h @@ -6,12 +6,15 @@ #include "drivers/Bma421.h" #include "components/ble/MotionService.h" +#include "components/settings/Settings.h" #include "utility/CircularBuffer.h" namespace Pinetime { namespace Controllers { class MotionController { public: + MotionController(Controllers::Settings& settingsController); + enum class DeviceTypes { Unknown, BMA421, @@ -33,13 +36,20 @@ namespace Pinetime { } uint32_t NbSteps() const { - return nbSteps; + if (nbSteps > ignoreSteps) { + return nbSteps - ignoreSteps; + } + return 0; } void ResetTrip() { currentTripSteps = 0; } + void ResetIgnoreSteps() { + ignoreSteps = 0; + } + uint32_t GetTripSteps() const { return currentTripSteps; } @@ -69,6 +79,7 @@ namespace Pinetime { private: uint32_t nbSteps = 0; uint32_t currentTripSteps = 0; + uint32_t ignoreSteps = 0; TickType_t lastTime = 0; TickType_t time = 0; @@ -100,6 +111,7 @@ namespace Pinetime { DeviceTypes deviceType = DeviceTypes::Unknown; Pinetime::Controllers::MotionService* service = nullptr; + Controllers::Settings& settingsController; }; } } diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 602de3a585..30df13f9c2 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -14,6 +14,7 @@ namespace Pinetime { enum class Notification : uint8_t { On, Off, Sleep }; enum class ChimesOption : uint8_t { None, Hours, HalfHours }; enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 }; + enum class SleepOption : uint8_t { AllowChimes = 0, AllowNotify = 1, EnableAOD = 2, DisableBle = 3, IgnoreSteps = 4 }; enum class Colors : uint8_t { White, Silver, @@ -268,6 +269,37 @@ namespace Pinetime { return getWakeUpModes()[static_cast(mode)]; } + bool sleepDisabledBle = false; + + void setSleepOption(SleepOption option, bool enabled) { + if (enabled != isSleepOptionOn(option)) { + settingsChanged = true; + } + settings.sleepOption.set(static_cast(option), enabled); + + // Handle special behavior + if (enabled) { + switch (option) { + case SleepOption::AllowNotify: + settings.sleepOption.set(static_cast(SleepOption::DisableBle), false); + break; + case SleepOption::DisableBle: + settings.sleepOption.set(static_cast(SleepOption::AllowNotify), false); + break; + default: + break; + } + } + }; + + std::bitset<5> getSleepOptions() const { + return settings.sleepOption; + } + + bool isSleepOptionOn(const SleepOption option) const { + return getSleepOptions()[static_cast(option)]; + } + void SetBrightness(Controllers::BrightnessController::Levels level) { if (level != settings.brightLevel) { settingsChanged = true; @@ -301,7 +333,7 @@ namespace Pinetime { private: Pinetime::Controllers::FS& fs; - static constexpr uint32_t settingsVersion = 0x0008; + static constexpr uint32_t settingsVersion = 0x0009; struct SettingsData { uint32_t version = settingsVersion; @@ -324,6 +356,8 @@ namespace Pinetime { std::bitset<5> wakeUpMode {0}; uint16_t shakeWakeThreshold = 150; + std::bitset<5> sleepOption {0}; + Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium; }; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 6671ac9e51..d322bae104 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -45,6 +45,7 @@ #include "displayapp/screens/settings/SettingWakeUp.h" #include "displayapp/screens/settings/SettingDisplay.h" #include "displayapp/screens/settings/SettingSteps.h" +#include "displayapp/screens/settings/SettingSleep.h" #include "displayapp/screens/settings/SettingSetDateTime.h" #include "displayapp/screens/settings/SettingChimes.h" #include "displayapp/screens/settings/SettingShakeThreshold.h" @@ -301,6 +302,7 @@ void DisplayApp::Refresh() { if (state != States::Running || !systemTask->IsSleeping()) { break; } + while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) { brightnessController.Lower(); vTaskDelay(100); @@ -311,6 +313,7 @@ void DisplayApp::Refresh() { } 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) { @@ -611,6 +614,9 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio case Apps::SettingSteps: currentScreen = std::make_unique(settingsController); break; + case Apps::SettingSleep: + currentScreen = std::make_unique(settingsController); + break; case Apps::SettingSetDateTime: currentScreen = std::make_unique(this, dateTimeController, settingsController); break; diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 2104a267c0..255926133b 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -38,6 +38,7 @@ namespace Pinetime { SettingDisplay, SettingWakeUp, SettingSteps, + SettingSleep, SettingSetDateTime, SettingChimes, SettingShakeThreshold, diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 41c383c0d4..1e5cf8b39c 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743, 0xf186" } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index bd958b285f..dac78ddfe0 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -38,7 +38,7 @@ namespace Pinetime { static constexpr const char* dice = "\xEF\x94\xA2"; static constexpr const char* eye = "\xEF\x81\xAE"; static constexpr const char* home = "\xEF\x80\x95"; - static constexpr const char* sleep = "\xEE\xBD\x84"; + static constexpr const char* crescentMoon = "\xEF\x86\x86"; // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; @@ -61,6 +61,7 @@ namespace Pinetime { static constexpr const char* notificationsOff = "\xEE\x9F\xB6"; static constexpr const char* notificationsOn = "\xEE\x9F\xB7"; + static constexpr const char* sleep = "\xEE\xBD\x84"; static constexpr const char* flashlight = "\xEF\x80\x8B"; static constexpr const char* paintbrushLg = "\xEE\x90\x8A"; diff --git a/src/displayapp/screens/settings/QuickSettings.cpp b/src/displayapp/screens/settings/QuickSettings.cpp index c5c3071aef..db5b44a53a 100644 --- a/src/displayapp/screens/settings/QuickSettings.cpp +++ b/src/displayapp/screens/settings/QuickSettings.cpp @@ -146,22 +146,45 @@ void QuickSettings::OnButtonEvent(lv_obj_t* object) { settingsController.SetBrightness(brightness.Level()); } else if (object == btn3) { - + // Turn notifications off if (settingsController.GetNotificationStatus() == Controllers::Settings::Notification::On) { settingsController.SetNotificationStatus(Controllers::Settings::Notification::Off); lv_label_set_text_static(btn3_lvl, Symbols::notificationsOff); lv_obj_set_state(btn3, static_cast(ButtonState::NotificationsOff)); + // Turn sleep on } else if (settingsController.GetNotificationStatus() == Controllers::Settings::Notification::Off) { settingsController.SetNotificationStatus(Controllers::Settings::Notification::Sleep); lv_label_set_text_static(btn3_lvl, Symbols::sleep); lv_obj_set_state(btn3, static_cast(ButtonState::Sleep)); - } else { + + if (settingsController.isSleepOptionOn(Controllers::Settings::SleepOption::DisableBle)) { + if (settingsController.GetBleRadioEnabled()) { + settingsController.SetBleRadioEnabled(false); + app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); + settingsController.sleepDisabledBle = true; + } + } + // Turn notifications on + } else if (settingsController.GetNotificationStatus() == Controllers::Settings::Notification::Sleep) { settingsController.SetNotificationStatus(Controllers::Settings::Notification::On); lv_label_set_text_static(btn3_lvl, Symbols::notificationsOn); lv_obj_set_state(btn3, static_cast(ButtonState::NotificationsOn)); motorController.RunForDuration(35); } + // Re-enable Bluetooth settings for all notification statuses except Sleep + // (Prevents the need to modify logic if additional statuses are added) + if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep) { + // Enable Ble if previously disabled + if (settingsController.sleepDisabledBle == true) { + if (!settingsController.GetBleRadioEnabled()) { + settingsController.SetBleRadioEnabled(true); + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); + } + settingsController.sleepDisabledBle = false; + } + } + } else if (object == btn4) { settingsController.SetSettingsMenu(0); app->StartApp(Apps::Settings, DisplayApp::FullRefreshDirections::Up); diff --git a/src/displayapp/screens/settings/SettingSleep.cpp b/src/displayapp/screens/settings/SettingSleep.cpp new file mode 100644 index 0000000000..a3f427b7c8 --- /dev/null +++ b/src/displayapp/screens/settings/SettingSleep.cpp @@ -0,0 +1,79 @@ +#include "displayapp/screens/settings/SettingSleep.h" +#include +#include "displayapp/DisplayApp.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" +#include "components/settings/Settings.h" +#include "displayapp/screens/Styles.h" + +using namespace Pinetime::Applications::Screens; + +constexpr std::array SettingSleep::options; + +namespace { + void event_handler(lv_obj_t* obj, lv_event_t event) { + auto* screen = static_cast(obj->user_data); + if (event == LV_EVENT_VALUE_CHANGED) { + screen->UpdateSelected(obj); + } + } +} + +SettingSleep::SettingSleep(Pinetime::Controllers::Settings& settingsController) : settingsController {settingsController} { + lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); + + lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10); + lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); + lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + + lv_obj_set_pos(container1, 10, 35); + lv_obj_set_width(container1, LV_HOR_RES - 20); + lv_obj_set_height(container1, LV_VER_RES - 20); + lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); + + lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(title, "Sleep Actions"); + lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); + lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 15, 15); + + lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); + lv_label_set_text_static(icon, Symbols::crescentMoon); + lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); + lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); + + for (unsigned int i = 0; i < options.size(); i++) { + cbOption[i] = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(cbOption[i], options[i].name); + if (settingsController.isSleepOptionOn(static_cast(i))) { + lv_checkbox_set_checked(cbOption[i], true); + } + cbOption[i]->user_data = this; + lv_obj_set_event_cb(cbOption[i], event_handler); + } +} + +SettingSleep::~SettingSleep() { + lv_obj_clean(lv_scr_act()); + settingsController.SaveSettings(); +} + +void SettingSleep::UpdateSelected(lv_obj_t* object) { + // Find the index of the checkbox that triggered the event + for (size_t i = 0; i < options.size(); i++) { + if (cbOption[i] == object) { + bool currentState = settingsController.isSleepOptionOn(options[i].sleepOption); + settingsController.setSleepOption(options[i].sleepOption, !currentState); + break; + } + } + + // Update checkbox according to current sleep options. + // This is needed because we can have extra logic when setting or unsetting sleep options, + // for example, when setting AllowNotify, DisableBle is unset and vice versa. + auto sleepOptions = settingsController.getSleepOptions(); + for (size_t i = 0; i < options.size(); ++i) { + lv_checkbox_set_checked(cbOption[i], sleepOptions[i]); + } +} diff --git a/src/displayapp/screens/settings/SettingSleep.h b/src/displayapp/screens/settings/SettingSleep.h new file mode 100644 index 0000000000..a689b831c0 --- /dev/null +++ b/src/displayapp/screens/settings/SettingSleep.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" + +namespace Pinetime { + + namespace Applications { + namespace Screens { + + class SettingSleep : public Screen { + public: + SettingSleep(Pinetime::Controllers::Settings& settingsController); + ~SettingSleep() override; + + void UpdateSelected(lv_obj_t* object); + + private: + struct Option { + Controllers::Settings::SleepOption sleepOption; + const char* name; + }; + + Controllers::Settings& settingsController; + static constexpr std::array options = {{ + {Controllers::Settings::SleepOption::AllowChimes, "Allow Chimes"}, + {Controllers::Settings::SleepOption::AllowNotify, "Allow Notify"}, + {Controllers::Settings::SleepOption::EnableAOD, "Enable AOD"}, + {Controllers::Settings::SleepOption::DisableBle, "Disable BLE"}, + {Controllers::Settings::SleepOption::IgnoreSteps, "Ignore Steps"}, + }}; + + lv_obj_t* cbOption[options.size()]; + }; + } + } +} diff --git a/src/displayapp/screens/settings/Settings.h b/src/displayapp/screens/settings/Settings.h index 3722c2be39..27483a3e4c 100644 --- a/src/displayapp/screens/settings/Settings.h +++ b/src/displayapp/screens/settings/Settings.h @@ -37,16 +37,17 @@ namespace Pinetime { {Symbols::clock, "Time format", Apps::SettingTimeFormat}, {Symbols::home, "Watch face", Apps::SettingWatchFace}, + {Symbols::crescentMoon, "Sleep", Apps::SettingSleep}, {Symbols::shoe, "Steps", Apps::SettingSteps}, {Symbols::clock, "Date & Time", Apps::SettingSetDateTime}, {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat}, - {Symbols::batteryHalf, "Battery", Apps::BatteryInfo}, + {Symbols::batteryHalf, "Battery", Apps::BatteryInfo}, {Symbols::clock, "Chimes", Apps::SettingChimes}, {Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold}, {Symbols::check, "Firmware", Apps::FirmwareValidation}, - {Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth}, + {Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth}, {Symbols::list, "About", Apps::SysInfo}, // {Symbols::none, "None", Apps::None}, diff --git a/src/main.cpp b/src/main.cpp index 24f13caddd..0c2a0ab720 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,7 +103,7 @@ Pinetime::Controllers::MotorController motorController {}; Pinetime::Controllers::DateTime dateTimeController {settingsController}; Pinetime::Drivers::Watchdog watchdog; Pinetime::Controllers::NotificationManager notificationManager; -Pinetime::Controllers::MotionController motionController; +Pinetime::Controllers::MotionController motionController {settingsController}; Pinetime::Controllers::AlarmController alarmController {dateTimeController, fs}; Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index eb013d6d1a..279660a27d 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -207,7 +207,9 @@ void SystemTask::Work() { } break; case Messages::OnNewNotification: - if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { + if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On || + (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::Sleep && + settingsController.isSleepOptionOn(Pinetime::Controllers::Settings::SleepOption::AllowNotify))) { if (IsSleeping()) { GoToRunning(); } @@ -321,19 +323,17 @@ void SystemTask::Work() { stepCounterMustBeReset = true; break; case Messages::OnNewHour: - using Pinetime::Controllers::AlarmController; - if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep && - 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.IsAlerting()) { - GoToRunning(); - displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); + + if (settingsController.GetChimeOption() != Controllers::Settings::ChimesOption::None && !alarmController.IsAlerting()) { + if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep || + settingsController.isSleepOptionOn(Pinetime::Controllers::Settings::SleepOption::AllowChimes)) { + if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep) { + GoToRunning(); + } + displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime); + } } break; case Messages::OnChargingEvent: @@ -418,10 +418,16 @@ void SystemTask::GoToSleep() { if (IsSleepDisabled()) { return; } - NRF_LOG_INFO("[systemtask] Going to sleep"); - if (settingsController.GetAlwaysOnDisplay()) { + + if (settingsController.isSleepOptionOn(Pinetime::Controllers::Settings::SleepOption::EnableAOD) && + settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::Sleep) { + NRF_LOG_INFO("[systemtask] Always On Display Enabled For Sleep"); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToAOD); + } else if (settingsController.GetAlwaysOnDisplay()) { + NRF_LOG_INFO("[systemtask] Going To Always On Display"); displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToAOD); } else { + NRF_LOG_INFO("[systemtask] Going To Sleep"); displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep); } heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep); @@ -440,6 +446,7 @@ void SystemTask::UpdateMotion() { if (stepCounterMustBeReset) { motionSensor.ResetStepCounter(); + motionController.ResetIgnoreSteps(); stepCounterMustBeReset = false; }