#include "displayapp/DisplayApp.h" #include #include "displayapp/screens/HeartRate.h" #include "displayapp/screens/Motion.h" #include "displayapp/screens/Timer.h" #include "displayapp/screens/Alarm.h" #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/datetime/DateTimeController.h" #include "components/ble/NotificationManager.h" #include "components/motion/MotionController.h" #include "components/motor/MotorController.h" #include "displayapp/screens/ApplicationList.h" #include "displayapp/screens/Clock.h" #include "displayapp/screens/FirmwareUpdate.h" #include "displayapp/screens/FirmwareValidation.h" #include "displayapp/screens/InfiniPaint.h" #include "displayapp/screens/Paddle.h" #include "displayapp/screens/StopWatch.h" #include "displayapp/screens/Metronome.h" #include "displayapp/screens/Music.h" #include "displayapp/screens/Navigation.h" #include "displayapp/screens/Notifications.h" #include "displayapp/screens/SystemInfo.h" #include "displayapp/screens/Tile.h" #include "displayapp/screens/Twos.h" #include "displayapp/screens/FlashLight.h" #include "displayapp/screens/BatteryInfo.h" #include "displayapp/screens/Steps.h" #include "displayapp/screens/PassKey.h" #include "displayapp/screens/Error.h" #include "drivers/Cst816s.h" #include "drivers/St7789.h" #include "drivers/Watchdog.h" #include "systemtask/SystemTask.h" #include "systemtask/Messages.h" #include "displayapp/screens/settings/QuickSettings.h" #include "displayapp/screens/settings/Settings.h" #include "displayapp/screens/settings/SettingWatchFace.h" #include "displayapp/screens/settings/SettingTimeFormat.h" #include "displayapp/screens/settings/SettingWakeUp.h" #include "displayapp/screens/settings/SettingDisplay.h" #include "displayapp/screens/settings/SettingSteps.h" #include "displayapp/screens/settings/SettingSetDateTime.h" #include "displayapp/screens/settings/SettingChimes.h" #include "displayapp/screens/settings/SettingShakeThreshold.h" #include "displayapp/screens/settings/SettingBluetooth.h" #include "libs/lv_conf.h" using namespace Pinetime::Applications; using namespace Pinetime::Applications::Display; namespace { inline bool in_isr() { return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0; } } DisplayApp::DisplayApp(Drivers::St7789& lcd, const Drivers::Cst816S& touchPanel, const Controllers::Battery& batteryController, const Controllers::Ble& bleController, Controllers::DateTime& dateTimeController, const Drivers::Watchdog& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, Pinetime::Controllers::HeartRateController& heartRateController, Controllers::Settings& settingsController, Pinetime::Controllers::MotorController& motorController, Pinetime::Controllers::MotionController& motionController, Pinetime::Controllers::TimerController& timerController, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, Pinetime::Controllers::FS& filesystem) : lcd {lcd}, touchPanel {touchPanel}, batteryController {batteryController}, bleController {bleController}, dateTimeController {dateTimeController}, watchdog {watchdog}, notificationManager {notificationManager}, heartRateController {heartRateController}, settingsController {settingsController}, motorController {motorController}, motionController {motionController}, timerController {timerController}, alarmController {alarmController}, brightnessController {brightnessController}, touchHandler {touchHandler}, filesystem {filesystem}, lvgl {lcd} { } void DisplayApp::Start(System::BootErrors error) { msgQueue = xQueueCreate(queueSize, itemSize); bootError = error; lvgl.Init(); if (error == System::BootErrors::TouchController) { LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None); } else { LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); } if (pdPASS != xTaskCreate(DisplayApp::Process, "displayapp", 800, this, 0, &taskHandle)) { APP_ERROR_HANDLER(NRF_ERROR_NO_MEM); } } void DisplayApp::Process(void* instance) { auto* app = static_cast(instance); NRF_LOG_INFO("displayapp task started!"); app->InitHw(); // Send a dummy notification to unlock the lvgl display driver for the first iteration xTaskNotifyGive(xTaskGetCurrentTaskHandle()); while (true) { app->Refresh(); } } void DisplayApp::InitHw() { brightnessController.Init(); ApplyBrightness(); motorController.Init(); } void DisplayApp::Refresh() { auto LoadPreviousScreen = [this]() { FullRefreshDirections returnDirection; switch (appStackDirections.Pop()) { case FullRefreshDirections::Up: returnDirection = FullRefreshDirections::Down; break; case FullRefreshDirections::Down: returnDirection = FullRefreshDirections::Up; break; case FullRefreshDirections::LeftAnim: returnDirection = FullRefreshDirections::RightAnim; break; case FullRefreshDirections::RightAnim: returnDirection = FullRefreshDirections::LeftAnim; break; default: returnDirection = FullRefreshDirections::None; break; } LoadScreen(returnAppStack.Pop(), returnDirection); }; TickType_t queueTimeout; switch (state) { case States::Idle: queueTimeout = portMAX_DELAY; break; case States::Running: if (!currentScreen->IsRunning()) { LoadPreviousScreen(); } queueTimeout = lv_task_handler(); break; default: queueTimeout = portMAX_DELAY; break; } Messages msg; if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) { switch (msg) { case Messages::DimScreen: brightnessController.Set(Controllers::BrightnessController::Levels::Low); break; case Messages::RestoreBrightness: ApplyBrightness(); break; case Messages::GoToSleep: while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { brightnessController.Lower(); vTaskDelay(100); } PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); state = States::Idle; break; case Messages::GoToRunning: ApplyBrightness(); state = States::Running; break; case Messages::UpdateTimeOut: PushMessageToSystemTask(System::Messages::UpdateTimeOut); break; case Messages::UpdateBleConnection: // clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected : // Screens::Clock::BleConnectionStates::NotConnected); break; case Messages::NewNotification: LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down); break; case Messages::TimerDone: if (currentApp == Apps::Timer) { auto* timer = static_cast(currentScreen.get()); timer->Reset(); } else { LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); } motorController.RunForDuration(35); break; case Messages::AlarmTriggered: if (currentApp == Apps::Alarm) { auto* alarm = static_cast(currentScreen.get()); alarm->SetAlerting(); } else { LoadNewScreen(Apps::Alarm, DisplayApp::FullRefreshDirections::None); } break; case Messages::ShowPairingKey: LoadNewScreen(Apps::PassKey, DisplayApp::FullRefreshDirections::Up); motorController.RunForDuration(35); break; case Messages::TouchEvent: { if (state != States::Running) { break; } lvgl.SetNewTouchPoint(touchHandler.GetX(), touchHandler.GetY(), touchHandler.IsTouching()); auto gesture = touchHandler.GestureGet(); if (gesture == TouchEvents::None) { break; } auto LoadDirToReturnSwipe = [](DisplayApp::FullRefreshDirections refreshDirection) { switch (refreshDirection) { default: case DisplayApp::FullRefreshDirections::Up: return TouchEvents::SwipeDown; case DisplayApp::FullRefreshDirections::Down: return TouchEvents::SwipeUp; case DisplayApp::FullRefreshDirections::LeftAnim: return TouchEvents::SwipeRight; case DisplayApp::FullRefreshDirections::RightAnim: return TouchEvents::SwipeLeft; } }; if (!currentScreen->OnTouchEvent(gesture)) { if (currentApp == Apps::Clock) { switch (gesture) { case TouchEvents::SwipeUp: LoadNewScreen(Apps::Launcher, DisplayApp::FullRefreshDirections::Up); break; case TouchEvents::SwipeDown: LoadNewScreen(Apps::Notifications, DisplayApp::FullRefreshDirections::Down); break; case TouchEvents::SwipeRight: LoadNewScreen(Apps::QuickSettings, DisplayApp::FullRefreshDirections::RightAnim); break; case TouchEvents::DoubleTap: PushMessageToSystemTask(System::Messages::GoToSleep); break; default: break; } } else if (gesture == LoadDirToReturnSwipe(appStackDirections.Top())) { LoadPreviousScreen(); } } else { lvgl.CancelTap(); } } break; case Messages::ButtonPushed: if (!currentScreen->OnButtonPushed()) { if (currentApp == Apps::Clock) { PushMessageToSystemTask(System::Messages::GoToSleep); } else { LoadPreviousScreen(); } } break; case Messages::ButtonLongPressed: if (currentApp != Apps::Clock) { if (currentApp == Apps::Notifications) { LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::Up); } else if (currentApp == Apps::QuickSettings) { LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::LeftAnim); } else { LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::Down); } appStackDirections.Reset(); returnAppStack.Reset(); } break; case Messages::ButtonLongerPressed: // Create reboot app and open it instead LoadNewScreen(Apps::SysInfo, DisplayApp::FullRefreshDirections::Up); break; case Messages::ButtonDoubleClicked: if (currentApp != Apps::Notifications && currentApp != Apps::NotificationsPreview) { LoadNewScreen(Apps::Notifications, DisplayApp::FullRefreshDirections::Down); } break; case Messages::BleFirmwareUpdateStarted: LoadNewScreen(Apps::FirmwareUpdate, DisplayApp::FullRefreshDirections::Down); break; 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); motorController.RunForDuration(35); break; case Messages::OnChargingEvent: motorController.RunForDuration(15); break; } } if (touchHandler.IsTouching()) { currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY()); } if (nextApp != Apps::None) { LoadNewScreen(nextApp, nextDirection); nextApp = Apps::None; } } void DisplayApp::StartApp(Apps app, DisplayApp::FullRefreshDirections direction) { nextApp = app; nextDirection = direction; } void DisplayApp::LoadNewScreen(Apps app, DisplayApp::FullRefreshDirections direction) { // Don't add the same screen to the stack back to back. // This is mainly to fix an issue with receiving two notifications at the same time // and shouldn't happen otherwise. if (app != currentApp) { returnAppStack.Push(currentApp); appStackDirections.Push(direction); } LoadScreen(app, direction); } void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections direction) { lvgl.CancelTap(); ApplyBrightness(); motorController.StopRinging(); currentScreen.reset(nullptr); SetFullRefresh(direction); switch (app) { case Apps::Launcher: currentScreen = std::make_unique(this, settingsController, batteryController, bleController, dateTimeController); break; case Apps::None: case Apps::Clock: currentScreen = std::make_unique(dateTimeController, batteryController, bleController, notificationManager, settingsController, heartRateController, motionController, filesystem); break; case Apps::Error: currentScreen = std::make_unique(bootError); break; case Apps::FirmwareValidation: currentScreen = std::make_unique(validator); break; case Apps::FirmwareUpdate: currentScreen = std::make_unique(bleController); break; case Apps::PassKey: currentScreen = std::make_unique(bleController.GetPairingKey()); break; case Apps::Notifications: currentScreen = std::make_unique(this, notificationManager, systemTask->nimble().alertService(), motorController, *systemTask, Screens::Notifications::Modes::Normal); break; case Apps::NotificationsPreview: currentScreen = std::make_unique(this, notificationManager, systemTask->nimble().alertService(), motorController, *systemTask, Screens::Notifications::Modes::Preview); break; case Apps::Timer: currentScreen = std::make_unique(timerController); break; case Apps::Alarm: currentScreen = std::make_unique(alarmController, settingsController.GetClockType(), *systemTask, motorController); break; // Settings case Apps::QuickSettings: currentScreen = std::make_unique(this, batteryController, dateTimeController, brightnessController, motorController, settingsController, bleController); break; case Apps::Settings: currentScreen = std::make_unique(this, settingsController); break; case Apps::SettingWatchFace: currentScreen = std::make_unique(this, settingsController, filesystem); break; case Apps::SettingTimeFormat: currentScreen = std::make_unique(settingsController); break; case Apps::SettingWakeUp: currentScreen = std::make_unique(settingsController); break; case Apps::SettingDisplay: currentScreen = std::make_unique(this, settingsController); break; case Apps::SettingSteps: currentScreen = std::make_unique(settingsController); break; case Apps::SettingSetDateTime: currentScreen = std::make_unique(this, dateTimeController, settingsController); break; case Apps::SettingChimes: currentScreen = std::make_unique(settingsController); break; case Apps::SettingShakeThreshold: currentScreen = std::make_unique(settingsController, motionController, *systemTask); break; case Apps::SettingBluetooth: currentScreen = std::make_unique(this, settingsController); break; case Apps::BatteryInfo: currentScreen = std::make_unique(batteryController); break; case Apps::SysInfo: currentScreen = std::make_unique(this, dateTimeController, batteryController, brightnessController, bleController, watchdog, motionController, touchPanel); break; case Apps::FlashLight: currentScreen = std::make_unique(*systemTask, brightnessController); break; case Apps::StopWatch: currentScreen = std::make_unique(*systemTask); break; case Apps::Twos: currentScreen = std::make_unique(); break; case Apps::Paint: currentScreen = std::make_unique(lvgl, motorController); break; case Apps::Paddle: currentScreen = std::make_unique(lvgl); break; case Apps::Music: currentScreen = std::make_unique(systemTask->nimble().music()); break; case Apps::Navigation: currentScreen = std::make_unique(systemTask->nimble().navigation()); break; case Apps::HeartRate: currentScreen = std::make_unique(heartRateController, *systemTask); break; case Apps::Metronome: currentScreen = std::make_unique(motorController, *systemTask); break; case Apps::Motion: currentScreen = std::make_unique(motionController); break; case Apps::Steps: currentScreen = std::make_unique(motionController, settingsController); break; } currentApp = app; } void DisplayApp::PushMessage(Messages msg) { if (in_isr()) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } else { xQueueSend(msgQueue, &msg, portMAX_DELAY); } } void DisplayApp::SetFullRefresh(DisplayApp::FullRefreshDirections direction) { switch (direction) { case DisplayApp::FullRefreshDirections::Down: lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Down); break; case DisplayApp::FullRefreshDirections::Up: lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Up); break; case DisplayApp::FullRefreshDirections::Left: lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Left); break; case DisplayApp::FullRefreshDirections::Right: lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Right); break; case DisplayApp::FullRefreshDirections::LeftAnim: lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::LeftAnim); break; case DisplayApp::FullRefreshDirections::RightAnim: lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::RightAnim); break; default: break; } } void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { if (systemTask != nullptr) { systemTask->PushMessage(message); } } void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { this->systemTask = systemTask; } void DisplayApp::ApplyBrightness() { auto brightness = settingsController.GetBrightness(); if (brightness != Controllers::BrightnessController::Levels::Low && brightness != Controllers::BrightnessController::Levels::Medium && brightness != Controllers::BrightnessController::Levels::High) { brightness = Controllers::BrightnessController::Levels::High; } brightnessController.Set(brightness); }