diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8e8e96863b..b3b94ab715 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -425,6 +425,7 @@ list(APPEND SOURCE_FILES displayapp/screens/settings/SettingChimes.cpp displayapp/screens/settings/SettingShakeThreshold.cpp displayapp/screens/settings/SettingBluetooth.cpp + displayapp/screens/settings/SettingActivity.cpp ## Watch faces displayapp/screens/WatchFaceAnalog.cpp @@ -453,6 +454,7 @@ list(APPEND SOURCE_FILES components/datetime/DateTimeController.cpp components/brightness/BrightnessController.cpp components/motion/MotionController.cpp + components/activity/ActivityController.cpp components/ble/NimbleController.cpp components/ble/DeviceInformationService.cpp components/ble/CurrentTimeClient.cpp @@ -522,6 +524,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/datetime/DateTimeController.cpp components/brightness/BrightnessController.cpp components/motion/MotionController.cpp + components/activity/ActivityController.cpp components/ble/NimbleController.cpp components/ble/DeviceInformationService.cpp components/ble/CurrentTimeClient.cpp @@ -643,6 +646,7 @@ set(INCLUDE_FILES components/datetime/DateTimeController.h components/brightness/BrightnessController.h components/motion/MotionController.h + components/activity/ActivityController.h components/firmwarevalidator/FirmwareValidator.h components/ble/BleController.h components/ble/NotificationManager.h diff --git a/src/components/activity/ActivityController.cpp b/src/components/activity/ActivityController.cpp new file mode 100644 index 0000000000..575751dc95 --- /dev/null +++ b/src/components/activity/ActivityController.cpp @@ -0,0 +1,27 @@ +#include "components/activity/ActivityController.h" + +using namespace Pinetime::Controllers; + +void ActivityController::UpdateSteps(uint32_t nbSteps, uint8_t minutes) { + uint8_t hourSegment = minutes * (stepHistory.Size() - 2) / 60; + if (hourSegment != prevHourSegment && prevHourSegment != UINT8_MAX) { + stepHistory++; + canNotify = true; + } + prevHourSegment = hourSegment; + stepHistory[0] = nbSteps; + + if (stepHistory[0] < stepHistory[stepHistory.Size() - 1]) { + for (uint8_t i = 1; i < stepHistory.Size(); i++) { + stepHistory[i] = 0; + } + } +} + +bool ActivityController::ShouldNotify(uint16_t thresh) { + if (canNotify && stepHistory[stepHistory.Size() - 1] - stepHistory[1] < thresh) { + canNotify = false; + return true; + } + return false; +} diff --git a/src/components/activity/ActivityController.h b/src/components/activity/ActivityController.h new file mode 100644 index 0000000000..d9d9e19e96 --- /dev/null +++ b/src/components/activity/ActivityController.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "components/datetime/DateTimeController.h" +#include "components/settings/Settings.h" +#include "components/motion/MotionController.h" +#include "utility/CircularBuffer.h" + +namespace Pinetime { + namespace Controllers { + class ActivityController { + public: + void UpdateSteps(uint32_t nbSteps, uint8_t minutes); + bool ShouldNotify(uint16_t thresh); + + private: + Utility::CircularBuffer stepHistory = {}; + uint8_t prevHourSegment = UINT8_MAX; + bool canNotify = false; + }; + } +} diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index efa44fdee4..7d6be9c6e2 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -12,6 +12,7 @@ namespace Pinetime { enum class ClockType : uint8_t { H24, H12 }; enum class Notification : uint8_t { On, Off, Sleep }; enum class ChimesOption : uint8_t { None, Hours, HalfHours }; + enum class Activity : uint8_t { On, Off }; enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, @@ -86,6 +87,28 @@ namespace Pinetime { return settings.chimesOption; }; + void SetActivity(Activity activity) { + if (activity != settings.activity) { + settingsChanged = true; + } + settings.activity = activity; + }; + + Activity GetActivity() const { + return settings.activity; + }; + + void SetActivityThresh(uint32_t activityThresh) { + if (activityThresh != settings.activityThresh) { + settingsChanged = true; + } + settings.activityThresh = activityThresh; + }; + + uint16_t GetActivityThresh() const { + return settings.activityThresh; + }; + void SetPTSColorTime(Colors colorTime) { if (colorTime != settings.PTS.ColorTime) settingsChanged = true; @@ -292,6 +315,9 @@ namespace Pinetime { Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital; ChimesOption chimesOption = ChimesOption::None; + Activity activity = Activity::On; + uint16_t activityThresh = 75; + PineTimeStyle PTS; WatchFaceInfineat watchFaceInfineat; diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h index f253bc0387..29725d3d38 100644 --- a/src/displayapp/Apps.h +++ b/src/displayapp/Apps.h @@ -37,6 +37,7 @@ namespace Pinetime { SettingChimes, SettingShakeThreshold, SettingBluetooth, + SettingActivity, Error }; } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index a930fe961c..b7394d70fc 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -48,6 +48,7 @@ #include "displayapp/screens/settings/SettingChimes.h" #include "displayapp/screens/settings/SettingShakeThreshold.h" #include "displayapp/screens/settings/SettingBluetooth.h" +#include "displayapp/screens/settings/SettingActivity.h" #include "libs/lv_conf.h" @@ -500,6 +501,9 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio case Apps::SettingBluetooth: currentScreen = std::make_unique(this, settingsController); break; + case Apps::SettingActivity: + currentScreen = std::make_unique(settingsController); + break; case Apps::BatteryInfo: currentScreen = std::make_unique(batteryController); break; diff --git a/src/displayapp/screens/settings/SettingActivity.cpp b/src/displayapp/screens/settings/SettingActivity.cpp new file mode 100644 index 0000000000..3bbaff3613 --- /dev/null +++ b/src/displayapp/screens/settings/SettingActivity.cpp @@ -0,0 +1,103 @@ +#include "displayapp/screens/settings/SettingActivity.h" + +#include +#include + +#include "displayapp/DisplayApp.h" +#include "displayapp/screens/Symbols.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void EventHandler(lv_obj_t* obj, lv_event_t event) { + SettingActivity* screen = static_cast(obj->user_data); + screen->UpdateSelected(obj, event); + } +} + +SettingActivity::SettingActivity(Controllers::Settings& settingsController) : settingsController {settingsController} { + lv_obj_t* container = lv_cont_create(lv_scr_act(), nullptr); + + lv_obj_set_style_local_bg_opa(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_pad_all(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10); + lv_obj_set_style_local_pad_inner(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); + lv_obj_set_style_local_border_width(container, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + lv_obj_set_pos(container, 30, 60); + lv_obj_set_width(container, LV_HOR_RES - 50); + lv_obj_set_height(container, LV_VER_RES - 60); + lv_cont_set_layout(container, LV_LAYOUT_COLUMN_LEFT); + + lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(title, "Activity notifications"); + 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::shoe); + lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); + lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); + + enabled = lv_checkbox_create(container, nullptr); + lv_checkbox_set_text(enabled, "Enabled"); + if (settingsController.GetActivity() == Controllers::Settings::Activity::On) { + lv_checkbox_set_checked(enabled, true); + } + enabled->user_data = this; + lv_obj_set_event_cb(enabled, EventHandler); + + stepValue = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_label_set_text_fmt(stepValue, "%" PRIu32, settingsController.GetActivityThresh()); + lv_label_set_align(stepValue, LV_LABEL_ALIGN_CENTER); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 20); + + btnPlus = lv_btn_create(lv_scr_act(), nullptr); + btnPlus->user_data = this; + lv_obj_set_size(btnPlus, 80, 50); + lv_obj_align(btnPlus, lv_scr_act(), LV_ALIGN_CENTER, 55, 80); + lv_obj_t* lblPlus = lv_label_create(btnPlus, nullptr); + lv_label_set_text_static(lblPlus, "+"); + lv_obj_set_event_cb(btnPlus, EventHandler); + + btnMinus = lv_btn_create(lv_scr_act(), nullptr); + btnMinus->user_data = this; + lv_obj_set_size(btnMinus, 80, 50); + lv_obj_set_event_cb(btnMinus, EventHandler); + lv_obj_align(btnMinus, lv_scr_act(), LV_ALIGN_CENTER, -55, 80); + lv_obj_t* lblMinus = lv_label_create(btnMinus, nullptr); + lv_label_set_text_static(lblMinus, "-"); +} + +SettingActivity::~SettingActivity() { + lv_obj_clean(lv_scr_act()); + settingsController.SaveSettings(); +} + +void SettingActivity::UpdateSelected(lv_obj_t* object, lv_event_t event) { + uint16_t value = settingsController.GetActivityThresh(); + if (object == btnPlus && event == LV_EVENT_PRESSED) { + value += 1; + if (value <= 15000) { + settingsController.SetActivityThresh(value); + lv_label_set_text_fmt(stepValue, "%" PRIu16, settingsController.GetActivityThresh()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 20); + } + } + + if (object == btnMinus && event == LV_EVENT_PRESSED) { + value -= 1; + if (value >= 1) { + settingsController.SetActivityThresh(value); + lv_label_set_text_fmt(stepValue, "%" PRIu16, settingsController.GetActivityThresh()); + lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_CENTER, 0, 20); + } + } + + if (object == enabled && event == LV_EVENT_VALUE_CHANGED) { + bool currentState = settingsController.GetActivity() == Controllers::Settings::Activity::On; + settingsController.SetActivity(currentState ? Controllers::Settings::Activity::Off : Controllers::Settings::Activity::On); + lv_checkbox_set_checked(enabled, !currentState); + } +} diff --git a/src/displayapp/screens/settings/SettingActivity.h b/src/displayapp/screens/settings/SettingActivity.h new file mode 100644 index 0000000000..5f7c6cb25a --- /dev/null +++ b/src/displayapp/screens/settings/SettingActivity.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class SettingActivity : public Screen { + public: + explicit SettingActivity(Controllers::Settings& settingsController); + ~SettingActivity() override; + + void UpdateSelected(lv_obj_t* object, lv_event_t event); + + private: + Controllers::Settings& settingsController; + + lv_obj_t* enabled; + lv_obj_t* stepValue; + lv_obj_t* btnPlus; + lv_obj_t* btnMinus; + }; + } + } +} diff --git a/src/displayapp/screens/settings/Settings.h b/src/displayapp/screens/settings/Settings.h index 3f8097538e..e9e3c74879 100644 --- a/src/displayapp/screens/settings/Settings.h +++ b/src/displayapp/screens/settings/Settings.h @@ -29,7 +29,7 @@ namespace Pinetime { static constexpr int entriesPerScreen = 4; // Increment this when more space is needed - static constexpr int nScreens = 3; + static constexpr int nScreens = 4; static constexpr std::array entries {{ {Symbols::sun, "Display", Apps::SettingDisplay}, @@ -45,13 +45,12 @@ namespace Pinetime { {Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold}, {Symbols::check, "Firmware", Apps::FirmwareValidation}, {Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth}, - {Symbols::list, "About", Apps::SysInfo}, - - // {Symbols::none, "None", Apps::None}, - // {Symbols::none, "None", Apps::None}, - // {Symbols::none, "None", Apps::None}, - // {Symbols::none, "None", Apps::None}, + {Symbols::shoe, "Activity", Apps::SettingActivity}, + {Symbols::list, "About", Apps::SysInfo}, + {Symbols::none, "None", Apps::None}, + {Symbols::none, "None", Apps::None}, + {Symbols::none, "None", Apps::None}, }}; ScreenList screens; }; diff --git a/src/main.cpp b/src/main.cpp index ee6a6d3de5..6d86235d7e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,7 @@ #include #include "BootloaderVersion.h" +#include "components/activity/ActivityController.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" @@ -108,6 +109,7 @@ Pinetime::Controllers::AlarmController alarmController {dateTimeController}; Pinetime::Controllers::TouchHandler touchHandler; Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; +Pinetime::Controllers::ActivityController activityController; Pinetime::Applications::DisplayApp displayApp(lcd, touchPanel, @@ -144,7 +146,8 @@ Pinetime::System::SystemTask systemTask(spi, heartRateApp, fs, touchHandler, - buttonHandler); + buttonHandler, + activityController); int mallocFailedCount = 0; int stackOverflowCount = 0; extern "C" { diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 4719bb50ae..871e741f4e 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -18,6 +18,7 @@ #include "BootErrors.h" #include +#include using namespace Pinetime::System; @@ -51,7 +52,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, Pinetime::Applications::HeartRateTask& heartRateApp, Pinetime::Controllers::FS& fs, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::ButtonHandler& buttonHandler) + Pinetime::Controllers::ButtonHandler& buttonHandler, + Controllers::ActivityController& activityController) : spi {spi}, spiNorFlash {spiNorFlash}, twiMaster {twiMaster}, @@ -72,6 +74,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, fs {fs}, touchHandler {touchHandler}, buttonHandler {buttonHandler}, + activityController {activityController}, nimbleController(*this, bleController, dateTimeController, @@ -180,6 +183,22 @@ void SystemTask::Work() { while (true) { UpdateMotion(); + activityController.UpdateSteps(motionController.NbSteps(), dateTimeController.Minutes()); + if (activityController.ShouldNotify(settingsController.GetActivityThresh()) && + settingsController.GetActivity() == Controllers::Settings::Activity::On && + settingsController.GetNotificationStatus() == Controllers::Settings::Notification::On) { + NRF_LOG_INFO("activity"); + Controllers::NotificationManager::Notification notif; + constexpr char message[] = "Low activity\0Fewer steps than threshold in last hour. Try walking around."; + constexpr size_t messageSize = std::min(sizeof message - 1, Controllers::NotificationManager::MaximumMessageSize()); + std::memcpy(notif.message.data(), message, messageSize); + notif.message[messageSize] = '\0'; + notif.size = messageSize; + notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert; + notificationManager.Push(std::move(notif)); + PushMessage(Messages::OnNewNotification); + } + Messages msg; if (xQueueReceive(systemTasksMsgQueue, &msg, 100) == pdTRUE) { switch (msg) { diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 79f1cf444f..2161c6ad34 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -20,6 +20,7 @@ #include "touchhandler/TouchHandler.h" #include "buttonhandler/ButtonHandler.h" #include "buttonhandler/ButtonActions.h" +#include "components/activity/ActivityController.h" #ifdef PINETIME_IS_RECOVERY #include "displayapp/DisplayAppRecovery.h" @@ -72,7 +73,8 @@ namespace Pinetime { Pinetime::Applications::HeartRateTask& heartRateApp, Pinetime::Controllers::FS& fs, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::ButtonHandler& buttonHandler); + Pinetime::Controllers::ButtonHandler& buttonHandler, + Controllers::ActivityController& activityController); void Start(); void PushMessage(Messages msg); @@ -120,6 +122,7 @@ namespace Pinetime { Pinetime::Controllers::FS& fs; Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::ButtonHandler& buttonHandler; + Pinetime::Controllers::ActivityController& activityController; Pinetime::Controllers::NimbleController nimbleController; static void Process(void* instance);