text: /*
   *
   *    Copyright (c) 2020 Project CHIP Authors
   *    Copyright (c) 2019 Google LLC.
   *    All rights reserved.
   *
   *    Licensed under the Apache License, Version 2.0 (the "License");
   *    you may not use this file except in compliance with the License.
   *    You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *    Unless required by applicable law or agreed to in writing, software
   *    distributed under the License is distributed on an "AS IS" BASIS,
   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *    See the License for the specific language governing permissions and
   *    limitations under the License.
   */
  
  #include "AppTask.h"
  #include "AppConfig.h"
  #include "AppEvent.h"
  #include "LEDWidget.h"
  #include "lcd.h"
  #include "qrcodegen.h"
  #include "sl_simple_led_instances.h"
  #include <app-common/zap-generated/attribute-id.h>
  #include <app-common/zap-generated/attribute-type.h>
  #include <app-common/zap-generated/cluster-id.h>
  #include <app/clusters/identify-server/identify-server.h>
  #include <app/server/OnboardingCodesUtil.h>
  #include <app/server/Server.h>
  #include <app/util/attribute-storage.h>
  
  #include "app/clusters/ota-requestor/BDXDownloader.h"
  #include "app/clusters/ota-requestor/OTARequestor.h"
  #include "platform/EFR32/OTAImageProcessorImpl.h"
  #include "platform/GenericOTARequestorDriver.h"
  
  #include <assert.h>
  
  #include <credentials/DeviceAttestationCredsProvider.h>
  #include <credentials/examples/DeviceAttestationCredsExample.h>
  
  #include <setup_payload/QRCodeSetupPayloadGenerator.h>
  #include <setup_payload/SetupPayload.h>
  
  #include <platform/EFR32/freertos_bluetooth.h>
  
  #include <lib/support/CodeUtils.h>
  
  #include <platform/CHIPDeviceLayer.h>
  #if CHIP_ENABLE_OPENTHREAD
  #include <platform/EFR32/ThreadStackManagerImpl.h>
  #include <platform/OpenThread/OpenThreadUtils.h>
  #include <platform/ThreadStackManager.h>
  #endif
  
  #define FACTORY_RESET_TRIGGER_TIMEOUT 3000
  #define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000
  #define APP_TASK_STACK_SIZE (4096)
  #define APP_TASK_PRIORITY 2
  #define APP_EVENT_QUEUE_SIZE 10
  #define EXAMPLE_VENDOR_ID 0xcafe
  
  #define SYSTEM_STATE_LED &sl_led_led0
  #define LIGHT_LED &sl_led_led1
  #define APP_FUNCTION_BUTTON &sl_button_btn0
  #define APP_LIGHT_SWITCH &sl_button_btn1
  
  namespace {
  TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer.
  
  TaskHandle_t sAppTaskHandle;
  QueueHandle_t sAppEventQueue;
  
  LEDWidget sStatusLED;
  LEDWidget sLightLED;
  
  bool sIsThreadProvisioned = false;
  bool sIsThreadEnabled     = false;
  bool sHaveBLEConnections  = false;
  
  // EmberAfIdentifyEffectIdentifier sIdentifyEffect = EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT;
  
  uint8_t sAppEventQueueBuffer[APP_EVENT_QUEUE_SIZE * sizeof(AppEvent)];
  StaticQueue_t sAppEventQueueStruct;
  
  StackType_t appStack[APP_TASK_STACK_SIZE / sizeof(StackType_t)];
  StaticTask_t appTaskStruct;
  
  } // namespace
  
  using namespace chip;
  using namespace chip::TLV;
  using namespace ::chip::Credentials;
  using namespace ::chip::DeviceLayer;
  
  // Global OTA objects
  OTARequestor gRequestorCore;
  DeviceLayer::GenericOTARequestorDriver gRequestorUser;
  BDXDownloader gDownloader;
  OTAImageProcessorImpl gImageProcessor;
  
  AppTask AppTask::sAppTask;
  
  CHIP_ERROR AppTask::StartAppTask()
  {
      sAppEventQueue = xQueueCreateStatic(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent), sAppEventQueueBuffer, &sAppEventQueueStruct);
      if (sAppEventQueue == NULL)
      {
          EFR32_LOG("Failed to allocate app event queue");
          appError(APP_ERROR_EVENT_QUEUE_FAILED);
      }
  
      // Start App task.
      sAppTaskHandle = xTaskCreateStatic(AppTaskMain, APP_TASK_NAME, ArraySize(appStack), NULL, 1, appStack, &appTaskStruct);
      return (sAppTaskHandle == nullptr) ? APP_ERROR_CREATE_TASK_FAILED : CHIP_NO_ERROR;
  }
  
  CHIP_ERROR AppTask::Init()
  {
      CHIP_ERROR err = CHIP_NO_ERROR;
  
      // Init ZCL Data Model
      chip::Server::GetInstance().Init();
  
      // Initialize device attestation config
      SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider());
  
      // Create FreeRTOS sw timer for Function Selection.
      sFunctionTimer = xTimerCreate("FnTmr",          // Just a text name, not used by the RTOS kernel
                                    1,                // == default timer period (mS)
                                    false,            // no timer reload (==one-shot)
                                    (void *) this,    // init timer id = app task obj context
                                    TimerEventHandler // timer callback handler
      );
      if (sFunctionTimer == NULL)
      {
          EFR32_LOG("funct timer create failed");
          appError(APP_ERROR_CREATE_TIMER_FAILED);
      }
  
      EFR32_LOG("Current Software Version: %s", CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING);
      err = LightMgr().Init();
      if (err != CHIP_NO_ERROR)
      {
          EFR32_LOG("LightMgr().Init() failed");
          appError(err);
      }
  
      LightMgr().SetCallbacks(ActionInitiated, ActionCompleted);
  
      // Initialize LEDs
      LEDWidget::InitGpio();
      sStatusLED.Init(SYSTEM_STATE_LED);
      sLightLED.Init(LIGHT_LED);
      sLightLED.Set(LightMgr().IsLightOn());
      UpdateClusterState();
  
      ConfigurationMgr().LogDeviceConfig();
  
  // Print setup info on LCD if available
  #ifdef DISPLAY_ENABLED
      std::string QRCode;
  
      if (GetQRCode(QRCode, chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)) == CHIP_NO_ERROR)
      {
          LCDWriteQRCode((uint8_t *) QRCode.c_str());
      }
      else
      {
          EFR32_LOG("Getting QR code failed!");
      }
  #else
      PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kBLE));
  #endif
  
      // Initialize OTA components
      InitOTARequestor();
  
      return err;
  }
  
  void AppTask::AppTaskMain(void * pvParameter)
  {
      AppEvent event;
  
      CHIP_ERROR err = sAppTask.Init();
      if (err != CHIP_NO_ERROR)
      {
          EFR32_LOG("AppTask.Init() failed");
          appError(err);
      }
  
      EFR32_LOG("App Task started");
  
      while (true)
      {
          BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10));
          while (eventReceived == pdTRUE)
          {
              sAppTask.DispatchEvent(&event);
              eventReceived = xQueueReceive(sAppEventQueue, &event, 0);
          }
  
          // Collect connectivity and configuration state from the CHIP stack. Because
          // the CHIP event loop is being run in a separate task, the stack must be
          // locked while these values are queried.  However we use a non-blocking
          // lock request (TryLockCHIPStack()) to avoid blocking other UI activities
          // when the CHIP task is busy (e.g. with a long crypto operation).
          if (PlatformMgr().TryLockChipStack())
          {
              sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned();
              sIsThreadEnabled     = ConnectivityMgr().IsThreadEnabled();
              sHaveBLEConnections  = (ConnectivityMgr().NumBLEConnections() != 0);
              PlatformMgr().UnlockChipStack();
          }
  
          // Update the status LED if factory reset has not been initiated.
          //
          // If system has "full connectivity", keep the LED On constantly.
          //
          // If thread and service provisioned, but not attached to the thread network
          // yet OR no connectivity to the service OR subscriptions are not fully
          // established THEN blink the LED Off for a short period of time.
          //
          // If the system has ble connection(s) uptill the stage above, THEN blink
          // the LEDs at an even rate of 100ms.
          //
          // Otherwise, blink the LED ON for a very short time.
          if (sAppTask.mFunction != kFunction_FactoryReset)
          {
              if (sIsThreadProvisioned && sIsThreadEnabled)
              {
                  sStatusLED.Blink(950, 50);
              }
              else if (sHaveBLEConnections)
              {
                  sStatusLED.Blink(100, 100);
              }
              else
              {
                  sStatusLED.Blink(50, 950);
              }
          }
  
          sStatusLED.Animate();
      }
  }
  
  void AppTask::LightActionEventHandler(AppEvent * aEvent)
  {
      bool initiated = false;
      LightingManager::Action_t action;
      int32_t actor;
      CHIP_ERROR err = CHIP_NO_ERROR;
  
      if (aEvent->Type == AppEvent::kEventType_Light)
      {
          action = static_cast<LightingManager::Action_t>(aEvent->LightEvent.Action);
          actor  = aEvent->LightEvent.Actor;
      }
      else if (aEvent->Type == AppEvent::kEventType_Button)
      {
          if (LightMgr().IsLightOn())
          {
              action = LightingManager::OFF_ACTION;
          }
          else
          {
              action = LightingManager::ON_ACTION;
          }
          actor = AppEvent::kEventType_Button;
      }
      else
      {
          err = APP_ERROR_UNHANDLED_EVENT;
      }
  
      if (err == CHIP_NO_ERROR)
      {
          initiated = LightMgr().InitiateAction(actor, action);
  
          if (!initiated)
          {
              EFR32_LOG("Action is already in progress or active.");
          }
      }
  }
  
  void AppTask::ButtonEventHandler(const sl_button_t * buttonHandle, uint8_t btnAction)
  {
      if (buttonHandle == NULL)
      {
          return;
      }
  
      AppEvent button_event           = {};
      button_event.Type               = AppEvent::kEventType_Button;
      button_event.ButtonEvent.Action = btnAction;
  
      if (buttonHandle == APP_LIGHT_SWITCH && btnAction == SL_SIMPLE_BUTTON_PRESSED)
      {
          button_event.Handler = LightActionEventHandler;
          sAppTask.PostEvent(&button_event);
      }
      else if (buttonHandle == APP_FUNCTION_BUTTON)
      {
          button_event.Handler = FunctionHandler;
          sAppTask.PostEvent(&button_event);
      }
  }
  
  void AppTask::TimerEventHandler(TimerHandle_t xTimer)
  {
      AppEvent event;
      event.Type               = AppEvent::kEventType_Timer;
      event.TimerEvent.Context = (void *) xTimer;
      event.Handler            = FunctionTimerEventHandler;
      sAppTask.PostEvent(&event);
  }
  
  void AppTask::FunctionTimerEventHandler(AppEvent * aEvent)
  {
      if (aEvent->Type != AppEvent::kEventType_Timer)
      {
          return;
      }
  
      // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT,
      // initiate factory reset
      if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_StartBleAdv)
      {
          EFR32_LOG("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
  
          // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to
          // cancel, if required.
          sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
  
          sAppTask.mFunction = kFunction_FactoryReset;
  
          // Turn off all LEDs before starting blink to make sure blink is
          // co-ordinated.
          sStatusLED.Set(false);
  
          sStatusLED.Blink(500);
      }
      else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
      {
          // Actually trigger Factory Reset
          sAppTask.mFunction = kFunction_NoneSelected;
          ConfigurationMgr().InitiateFactoryReset();
      }
  }
  
  void AppTask::FunctionHandler(AppEvent * aEvent)
  {
      // To trigger software update: press the APP_FUNCTION_BUTTON button briefly (<
      // FACTORY_RESET_TRIGGER_TIMEOUT) To initiate factory reset: press the
      // APP_FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT +
      // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT All LEDs start blinking after
      // FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated.
      // To cancel factory reset: release the APP_FUNCTION_BUTTON once all LEDs
      // start blinking within the FACTORY_RESET_CANCEL_WINDOW_TIMEOUT
      if (aEvent->ButtonEvent.Action == SL_SIMPLE_BUTTON_PRESSED)
      {
          if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected)
          {
              sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
              sAppTask.mFunction = kFunction_StartBleAdv;
          }
      }
      else
      {
          // If the button was released before factory reset got initiated, start BLE advertissement in fast mode
          if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_StartBleAdv)
          {
              sAppTask.CancelTimer();
              sAppTask.mFunction = kFunction_NoneSelected;
  
              if (!ConnectivityMgr().IsThreadProvisioned())
              {
                  // Enable BLE advertisements
                  ConnectivityMgr().SetBLEAdvertisingEnabled(true);
                  ConnectivityMgr().SetBLEAdvertisingMode(ConnectivityMgr().kFastAdvertising);
              }
              else
              {
                  EFR32_LOG("Network is already provisioned, Ble advertissement not enabled");
              }
          }
          else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
          {
              sAppTask.CancelTimer();
  
              // Change the function to none selected since factory reset has been
              // canceled.
              sAppTask.mFunction = kFunction_NoneSelected;
  
              EFR32_LOG("Factory Reset has been Canceled");
          }
      }
  }
  
  void AppTask::CancelTimer()
  {
      if (xTimerStop(sFunctionTimer, 0) == pdFAIL)
      {
          EFR32_LOG("app timer stop() failed");
          appError(APP_ERROR_STOP_TIMER_FAILED);
      }
  
      mFunctionTimerActive = false;
  }
  
  void AppTask::StartTimer(uint32_t aTimeoutInMs)
  {
      if (xTimerIsTimerActive(sFunctionTimer))
      {
          EFR32_LOG("app timer already started!");
          CancelTimer();
      }
  
      // timer is not active, change its period to required value (== restart).
      // FreeRTOS- Block for a maximum of 100 ticks if the change period command
      // cannot immediately be sent to the timer command queue.
      if (xTimerChangePeriod(sFunctionTimer, aTimeoutInMs / portTICK_PERIOD_MS, 100) != pdPASS)
      {
          EFR32_LOG("app timer start() failed");
          appError(APP_ERROR_START_TIMER_FAILED);
      }
  
      mFunctionTimerActive = true;
  }
  
  void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor)
  {
      // Action initiated, update the light led
      if (aAction == LightingManager::ON_ACTION)
      {
          EFR32_LOG("Turning light ON")
      }
      else if (aAction == LightingManager::OFF_ACTION)
      {
          EFR32_LOG("Turning light OFF")
      }
  
      if (aActor == AppEvent::kEventType_Button)
      {
          sAppTask.mSyncClusterToButtonAction = true;
      }
  }
  
  void AppTask::ActionCompleted(LightingManager::Action_t aAction)
  {
      // action has been completed bon the light
      if (aAction == LightingManager::ON_ACTION)
      {
          EFR32_LOG("Light ON")
      }
      else if (aAction == LightingManager::OFF_ACTION)
      {
          EFR32_LOG("Light OFF")
      }
  
      if (sAppTask.mSyncClusterToButtonAction)
      {
          UpdateClusterState();
          sAppTask.mSyncClusterToButtonAction = false;
      }
  }
  
  void AppTask::PostLightActionRequest(int32_t aActor, LightingManager::Action_t aAction)
  {
      AppEvent event;
      event.Type              = AppEvent::kEventType_Light;
      event.LightEvent.Actor  = aActor;
      event.LightEvent.Action = aAction;
      event.Handler           = LightActionEventHandler;
      PostEvent(&event);
  }
  
  void AppTask::PostEvent(const AppEvent * aEvent)
  {
      if (sAppEventQueue != NULL)
      {
          BaseType_t status;
          if (xPortIsInsideInterrupt())
          {
              BaseType_t higherPrioTaskWoken = pdFALSE;
              status                         = xQueueSendFromISR(sAppEventQueue, aEvent, &higherPrioTaskWoken);
  
  #ifdef portYIELD_FROM_ISR
              portYIELD_FROM_ISR(higherPrioTaskWoken);
  #elif portEND_SWITCHING_ISR // portYIELD_FROM_ISR or portEND_SWITCHING_ISR
              portEND_SWITCHING_ISR(higherPrioTaskWoken);
  #else                       // portYIELD_FROM_ISR or portEND_SWITCHING_ISR
  #error "Must have portYIELD_FROM_ISR or portEND_SWITCHING_ISR"
  #endif // portYIELD_FROM_ISR or portEND_SWITCHING_ISR
          }
          else
          {
              status = xQueueSend(sAppEventQueue, aEvent, 1);
          }
  
          if (!status)
              EFR32_LOG("Failed to post event to app task event queue");
      }
      else
      {
          EFR32_LOG("Event Queue is NULL should never happen");
      }
  }
  
  void AppTask::DispatchEvent(AppEvent * aEvent)
  {
      if (aEvent->Handler)
      {
          aEvent->Handler(aEvent);
      }
      else
      {
          EFR32_LOG("Event received with no handler. Dropping event.");
      }
  }
  
  void AppTask::UpdateClusterState(void) {}
  
  void AppTask::InitOTARequestor()
  {
  
      // Initialize and interconnect the Requestor and Image Processor objects -- START
      SetRequestorInstance(&gRequestorCore);
  
      gRequestorCore.Init(&(chip::Server::GetInstance()), &gRequestorUser, &gDownloader);
  
      gRequestorUser.Init(&gRequestorCore, &gImageProcessor);
  
      OTAImageProcessorParams ipParams;
      ipParams.imageFile = CharSpan("test.txt");
      gImageProcessor.SetOTAImageProcessorParams(ipParams);
      gImageProcessor.SetOTADownloader(&gDownloader);
  
      // Connect the Downloader and Image Processor objects
      gDownloader.SetImageProcessorDelegate(&gImageProcessor);
      // Initialize and interconnect the Requestor and Image Processor objects -- END
  }
  