/**************************************************************************//**
 * @file
 * @brief Demo for Silabs Si117x Static HRM/SpO2 library
 * @version 1.3
 ******************************************************************************
 * @section License
 * <b>(C) Copyright 2017 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * This file is licensed under the Silabs License Agreement that is included in
 * with the software release. Before using this software for any purpose, you
 * must agree to the terms of that agreement.
 *
 ******************************************************************************/
#include "em_device.h"
#include "em_chip.h"
#include "em_gpio.h"
#include "em_cmu.h"
#include "em_emu.h"
#include "i2cspm.h"
#if (USE_DISPLAY == 1)
  #include <stdio.h>
  #include "graphics.h"
  #include "rtcdriver.h"
#endif
#include "../si117xlib/si117xhrm_static.h"
#include <sihrmUser.h>
#include "em_letimer.h"
#include "accel_sys_out.h"
#include "sihrm_demo.h"
#include "../si117xdrv/si117x_functions.h"
#include "../si117xdrv/si117xdrv.h"
#if (UART_DEBUG == 1)
  #include "uart_debug.h"
#endif
#include "bspconfig.h"

/**************************************************************************//**
 * Local defines
 *****************************************************************************/
#define SIHRM_DEMO_VERSION "Demo v1.4"

/**************************************************************************//**
 * Global variables
 *****************************************************************************/

/** Determines whether this example uses the accelerometer.  If this is set to
 *  0 the application will keep the accelerometer in deep sleep mode.
 */
uint8_t use_accelerometer=0;

/** Determines the interrupt behavior and param table set. 3 = -B3, 2 = -B2 */
uint8_t si117x_rev_id = 3;

/** Flags that are set by the irq service routines when a button is pressed */
volatile bool pb0Pressed=0, pb1Pressed=0;

/** Structure describing the i2c interface for the HRM sensor */
devicePortConfig_t hrmI2C;

/** Structure describing the i2c interface for the accelerometer */
AccelPortConfig_t accelI2C;

/** Data Storage memory bock required by the si117xhrm algorithm */
Si117xhrmDataStorage_t hrmDataStorage;
Si117xSpO2DataStorage_t spo2Data;
Si117xDataStorage_t dataStorage;

/** Sensor handle for the si117xhrm algorithm */
Si117xhrmHandle_t *hrmHandle;

/** HRM status flags returned by the si117xhrm algorithm */
static int32_t hrmStatus=0;

/** Heart Rate result from the si117xhrm algorithm */
static int16_t heartRate;

/** SpO2 result from the si117xhrm algorithm */
static int16_t spo2;

/** Handle to the si117x low-level code */
static HANDLE si117xHandle;

/** Configuration for the si117xhrm algorithm */
Si117xhrmUserConfiguration_t hrmConfiguration = {
	      0x007,                     // task_enable
	      0,                         // powerMode: 0(Power-saving mode: uses ppg1 on near LED) or 1(Performance mode: uses ppg2 on far LED)
#if (UART_DEBUG == 1)
	      1,                         // debugEnable
#else
		  0,                         // debugEnable
#endif
	      1,                         // accelerometerSynchronizationMode
	      1221,                      // The timestamp period in 100 nanoseconds - 8192Hz
	      40500,                     // accelerometerNormalizationFactorx10
	      Si117xSamplesPerChannel,   // numSamplesPerIrq
	      0,                         // *debugData
	      0                          // *deviceConfiguration; This value should be set to NULL
};

/** Flag used to indicate what to diplay */
#if (USE_DISPLAY == 1)
  static volatile displayType_t displayType = LCD_HOME;
#endif

/** Flag used to indicate HRM/SpO2 state */
static volatile HRMSpO2State_t hrmSpO2State = HRM_STATE_IDLE;

/** Global configuration for the si117xhrm algorithm */
static SI117XDRV_GlobalCfg_t globalCfg = SI117XDRV_DEFAULT_GLOBAL_CFG;

#if (UART_DEBUG == 1)
  Si117xhrmData_t hrmData;
  char debugMessage[100];
#endif

/**************************************************************************//**
 * Local prototypes
 *****************************************************************************/
static void gpioSetup(void);
static void InitializeHrmTimestampCounter (void);
static void Accelerometer_Init(AccelPortConfig_t* i2c);
static void configSi117xIntInput(void);
static void Si117xGlobalConfig (Si117xhrmUserConfiguration_t *config);

#if (USE_DISPLAY == 1)
  static void (*rtcCallback)(void*) = 0;
  static void * rtcCallbackArg = 0;
  static void memLcdCallback(RTCDRV_TimerID_t id, void *user);
  static void HeartRateMonitor_UpdateDisplay(HRMSpO2State_t state, displayType_t display);
#endif

/**************************************************************************//**
 * @brief  Main function
 *****************************************************************************/
int main(void)
{
  int error;
  int8_t hrmVersion[32];
  int16_t numSamplesProcessed;
  static bool updateDisplay = false;

#if (USE_DISPLAY == 1)
  char sihrm_version[42];
#endif
  
  I2CSPM_Init_TypeDef i2cInit = I2CSPM_INIT;
  EMU_DCDCInit_TypeDef dcdcInit = EMU_DCDCINIT_DEFAULT;

  /* Chip errata */
  CHIP_Init();

  /* Initalize hardware */
  EMU_DCDCInit(&dcdcInit);

  CMU_OscillatorEnable(cmuOsc_HFRCO, true, false);
  CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFRCO);

  /* Initalize other peripherals and drivers */
  gpioSetup();

#if (USE_DISPLAY == 1)
  RTCDRV_Init();
  GRAPHICS_Init();
#endif

#if (UART_DEBUG == 1)
  UART_Debug_Init();
#endif

  I2CSPM_Init(&i2cInit);

  hrmI2C.i2cAddress = SI1175_I2C_ADDR;
  hrmI2C.i2c = i2cInit.port;
  hrmI2C.irqPort=SI117X_INT_PORT;
  hrmI2C.irqPin=SI117X_INT_PIN;


  accelI2C.i2cAddress = ACCEL_I2C_ADDR;
  accelI2C.i2c = i2cInit.port;
  accelI2C.irqPort=ACCEL_INT_PORT;
  accelI2C.irqPin=ACCEL_INT_PIN;

  /*  Initialize Accelerometer for GGG and GRI  */
  Accelerometer_Init(&accelI2C);

  /*  if accelerometer is not used we put it in deep suspend  */
  if(use_accelerometer == 0)
    AccelWriteToRegister(accelHandle, 0x11, 0x20, false);		// Deep Suspend power mode

  /*  Setup Timestamp counter  */
  InitializeHrmTimestampCounter ();

  /*  Get the version of the HRM algorithm  */
  si117xhrm_QuerySoftwareRevision(hrmVersion);

  //allocate memory block for hrm
  dataStorage.spo2 = &spo2Data;
  dataStorage.hrm = &hrmDataStorage;

#if 0   //This loop can be enabled to test the idle/sleep power consumption
  while (true)
  {
    EMU_EnterEM2(false);
  }
#endif

  error = si117xhrm_Initialize(&hrmI2C, 0, &dataStorage, &hrmHandle);
#if (USE_DISPLAY == 1)
  sprintf(sihrm_version, "si117xhrm %s", hrmVersion);
  if(error != 0)
  {
	GRAPHICS_DrawError();
	while (true)
	  EMU_EnterEM2(false);
  }
  else
    GRAPHICS_DrawInit (SIHRM_DEMO_VERSION, sihrm_version);
#endif

  Si117xGlobalConfig(&hrmConfiguration);
  si117xhrm_Configure(hrmHandle, &hrmConfiguration); //This initializes the HRM buffers and variables.
  configSi117xIntInput();
  si117xhrm_GetLowLevelHandle(hrmHandle, &si117xHandle);


  while (true)
  {
#if (UART_DEBUG == 0)
	/* Go to sleep (EM2) */
	if (!updateDisplay && (sihrmUser_SampleQueueNumentries(si117xHandle) == 0))   //do not sleep if there are samples pending in the sample queue
	  EMU_EnterEM2(true); //restore clocks after waking
#endif

#if (UART_DEBUG == 1)
    error = si117xhrm_Process(hrmHandle, &heartRate, &spo2, 1, &numSamplesProcessed, &hrmStatus, &hrmData);
#else
    error = si117xhrm_Process(hrmHandle, &heartRate, &spo2, 1, &numSamplesProcessed, &hrmStatus, 0);
#endif

    switch(hrmSpO2State)
    {
      case HRM_STATE_IDLE:
    	updateDisplay = true;
    	break;
      case HRM_STATE_ACQUIRING:
      case HRM_STATE_ACTIVE:
		if((error == SI117xHRM_SUCCESS) && (hrmStatus & SI117xHRM_STATUS_FRAME_PROCESSED))
		{
		  hrmStatus &= ~SI117xHRM_STATUS_FRAME_PROCESSED;
#if (UART_DEBUG == 1)
          debugMessage[0] = 0x44; //Debug message starts with ASCII D
          sprintf(debugMessage+1, "Fs = %hd, Pi = %hu, Status = %ld, Pulse = %hdbpm, SpO2 = %hd%%", hrmData.Fs, hrmData.spo2IrPerfusionIndex, hrmStatus, heartRate, spo2);
          si117xhrm_OutputDebugMessage(hrmHandle, (int8_t*)debugMessage);
#endif
		  updateDisplay = true;
		  hrmSpO2State = HRM_STATE_ACTIVE;
		}
		else if ((hrmStatus & SI117xHRM_STATUS_FINGER_OFF) || (hrmStatus & SI117xHRM_STATUS_SPO2_FINGER_OFF) ||
				(hrmStatus & SI117xHRM_STATUS_ZERO_CROSSING_INVALID))
		{
		  heartRate = 0;
		  spo2 = 0;
		  updateDisplay = true;
		  hrmSpO2State = HRM_STATE_ACQUIRING;
		}
		break;
      default:
    	break;
    }
#if (USE_DISPLAY == 1)
    if(updateDisplay)
    {
      HeartRateMonitor_UpdateDisplay(hrmSpO2State, displayType);
      updateDisplay = false;
    }
#endif
  } //while(1)
}


/**************************************************************************//**
 * @brief This function enables the timestamp counter for HRM.
 *****************************************************************************/
static void InitializeHrmTimestampCounter (void)
{
  LETIMER_Init_TypeDef letimerInit = LETIMER_INIT_DEFAULT;

  CMU_ClockSelectSet(cmuClock_LFA,cmuSelect_LFXO);
  CMU_ClockEnable(cmuClock_LETIMER0, true);
  CMU_ClockDivSet(cmuClock_LETIMER0, cmuClkDiv_4);		// 32,768 Hz / 4 = 8192 Hz = 122.0703 us period
  CMU_ClockEnable(cmuClock_CORELE, true);

  LETIMER_Init(LETIMER0, &letimerInit);
}


/**************************************************************************//**
 * @brief  Set up GPIO input for Si117x interrupt line.
 *****************************************************************************/
static void configSi117xIntInput(void)
{
  NVIC_ClearPendingIRQ(GPIO_ODD_IRQn);
  NVIC_EnableIRQ(GPIO_ODD_IRQn);

  NVIC_ClearPendingIRQ(GPIO_EVEN_IRQn);
  NVIC_EnableIRQ(GPIO_EVEN_IRQn);

  /* Configure as input and enable si117x interrupt */
  /* Interrupt is active low */
  GPIO_PinModeSet(SI117X_INT_PORT, SI117X_INT_PIN, gpioModeInputPull, 1);
  GPIO_IntConfig(SI117X_INT_PORT, SI117X_INT_PIN, false, true, true);
}

/**************************************************************************//**
* @brief Setup GPIO, enable sensor isolation switch
*****************************************************************************/
static void gpioSetup(void)
{
  /* Enable GPIO clock */
  CMU_ClockEnable(cmuClock_GPIO, true);

  /*si117x interrupt. we want this pin high while si117x starts up*/
  GPIO_PinModeSet(SI117X_INT_PORT, SI117X_INT_PIN, gpioModePushPull, 1);

  /* Configure PB0 as input and enable interrupt  */
  GPIO_PinModeSet(BSP_GPIO_PB0_PORT, BSP_GPIO_PB0_PIN, gpioModeInputPull, 1);
  GPIO_IntConfig(BSP_GPIO_PB0_PORT, BSP_GPIO_PB0_PIN, false, true, true);

  /* Configure PB1 as input and enable interrupt */
  GPIO_PinModeSet(BSP_GPIO_PB1_PORT, BSP_GPIO_PB1_PIN, gpioModeInputPull, 1);
  GPIO_IntConfig(BSP_GPIO_PB1_PORT, BSP_GPIO_PB1_PIN, false, true, true);

  NVIC_ClearPendingIRQ(GPIO_EVEN_IRQn);
  NVIC_EnableIRQ(GPIO_EVEN_IRQn);

  NVIC_ClearPendingIRQ(GPIO_ODD_IRQn);
  NVIC_EnableIRQ(GPIO_ODD_IRQn);
}


/* EFR32 MG12 BRD4162A*/
#ifdef EFR32MG12P332F1024GL125
/**************************************************************************//**
 * @brief GPIO Interrupt handler (PB1)
 *****************************************************************************/
void GPIO_EVEN_IRQHandler(void)
{
  uint32_t flags;
  flags = GPIO_IntGet();
  GPIO_IntClear(flags);

  if (flags & (1 << BSP_GPIO_PB0_PIN))
  {
	/* PB0 */
	pb0Pressed = true;
#if (USE_DISPLAY == 1)
	if(displayType == LCD_HRM)
	{
	  displayType = LCD_SPO2;
	}
	else if(displayType == LCD_SPO2)
	{
	  displayType = LCD_HRM;
	}
#endif
  }

}

/**************************************************************************//**
 * @brief GPIO Interrupt handler (PB0,Si117x INT)
 *****************************************************************************/
void GPIO_ODD_IRQHandler(void)
{
  uint32_t flags;
  flags = GPIO_IntGet();
  GPIO_IntClear(flags);

  if (flags & (1 << SI117X_INT_PIN))
  { sihrmUser_ProcessIrq(si117xHandle, ~LETIMER_CounterGet(LETIMER0), &hrmConfiguration);		//LETIMER is a countdown timer.  The SiHRM requires a countup timer.  Therefore we complement the count
  }

  if (flags & (1 << BSP_GPIO_PB1_PIN))
  {
	/* PB1 */
	pb1Pressed = true;
	if(hrmSpO2State == HRM_STATE_IDLE)
	{
	  si117xhrm_Run(hrmHandle, true);
	  hrmSpO2State = HRM_STATE_ACQUIRING;
#if (USE_DISPLAY == 1)
	  displayType = LCD_HRM;
#endif
	}
	else
	{
	  si117xhrm_Pause(hrmHandle);
	  hrmSpO2State = HRM_STATE_IDLE;
#if (USE_DISPLAY == 1)
	  displayType = LCD_HOME;
#endif
	}
  }

}
#endif


#if (USE_DISPLAY == 1)
/**************************************************************************//**
 * @brief   Register a callback function at the given frequency.
 *
 * @param[in] pFunction  Pointer to function that should be called at the
 *                       given frequency.
 * @param[in] argument   Argument to be given to the function.
 * @param[in] frequency  Frequency at which to call function at.
 *
 * @return  0 for successful or
 *         -1 if the requested frequency does not match the RTCC frequency.
 *****************************************************************************/
int rtccIntCallbackRegister(void (*pFunction)(void*),
                           void* argument,
                           unsigned int frequency)
{
  RTCDRV_TimerID_t timerId;
  rtcCallback    = pFunction;
  rtcCallbackArg = argument;

  RTCDRV_AllocateTimer(&timerId);

  RTCDRV_StartTimer(timerId, rtcdrvTimerTypePeriodic, 1000 / frequency,
                    memLcdCallback, 0);

  return 0;
}

/**************************************************************************//**
 * @brief   The actual callback for Memory LCD toggling
 * @param[in] id
 *   The id of the RTC timer (not used)
 *****************************************************************************/
static void memLcdCallback(RTCDRV_TimerID_t id, void *user)
{
  (void) id;
  (void) user;
  rtcCallback(rtcCallbackArg);
}

/**************************************************************************//**
 * @brief Update the HRM/SpO2 Demo LCD Display
 *****************************************************************************/
static void HeartRateMonitor_UpdateDisplay(HRMSpO2State_t state, displayType_t display)
{
  int8_t hrmVersion[32];
  char sihrm_version[42];

  switch(state)
  {
    case HRM_STATE_IDLE:
      if(display == LCD_HOME)
      {
    	si117xhrm_QuerySoftwareRevision(hrmVersion);
    	sprintf(sihrm_version, "si117xhrm %s", hrmVersion);
        GRAPHICS_DrawInit (SIHRM_DEMO_VERSION, sihrm_version);
      }
      break;
    case HRM_STATE_ACQUIRING:
      if(display == LCD_HRM)
        GRAPHICS_DrawHrm(heartRate, hrmStatus, false);
      else if(displayType == LCD_SPO2)
    	GRAPHICS_DrawSpO2(spo2, hrmStatus, false);
      break;
    case HRM_STATE_ACTIVE:
      if(display == LCD_HRM)
        GRAPHICS_DrawHrm(heartRate, hrmStatus, true);
      else if(displayType == LCD_SPO2)
      	GRAPHICS_DrawSpO2(spo2, hrmStatus, true);
      break;
    default:
      break;
  }
}
#endif

/**************************************************************************//**
 * @brief Initialize the global configuration
 *****************************************************************************/
static void Si117xGlobalConfig (Si117xhrmUserConfiguration_t *config)
{
  SI117XDRV_DeviceSelect_t si117xDevice;
  si117xDevice = sihrmUser_getDeviceID ();
  globalCfg.ecgSampleRateus = 5000;
  globalCfg.ppgSampleRateus = 40000;
  globalCfg.taskEnable = config->taskEnable & 0xf;
  globalCfg.fifo_int_level = 3*2*15;
  globalCfg.synch_mode = config->accelerometerSynchronizationMode;
  globalCfg.irq_enable = SI117XDRV_IRQ_EN_FIFO;
  SI117XDRV_Reset(si117xDevice);
  SI117XDRV_InitGlobal(si117xDevice, &globalCfg);
}

/**************************************************************************//**
 * @brief Initialize the Accelerometer
 *****************************************************************************/
static void Accelerometer_Init(AccelPortConfig_t* i2c)
{
  if(AccelInit(i2c, 0, &accelHandle) == 0)
  {

    /* Set INT1 to open-drain. This setting is required to power up the si117x properly
     * on the Si117X_WRIST_PPG evb
	 * Set INT2 pin to push-pull and active low
     */
    AccelWriteToRegister(accelHandle, 0x20, 0x02, false);

    /* Setup BMA255 accelerometer but put it in Standby mode */
    AccelWriteToRegister(accelHandle, 0x0f, 0x08, false);		// +/- 8g
    AccelWriteToRegister(accelHandle, 0x10, 0x09, false);		// BW=15.62Hz, Fs = 2 x BW = 31.25

    /* Standby mode */
    AccelWriteToRegister(accelHandle, 0x12, 0x40, false);		// low power mode
    AccelWriteToRegister(accelHandle, 0x11, 0x80, false);		// Suspend power mode

    AccelWriteToRegister(accelHandle, 0x30, ACCEL_FIFO_LEVEL, false);		// 24, FIFO watermark =< 32
    AccelWriteToRegister(accelHandle, 0x21, 0x87, false);		// INT is latched

    /* Clear FIFO */
    AccelWriteToRegister(accelHandle, 0x3e, 0x40, false);	    // Stop when FIFO full

    AccelWriteToRegister(accelHandle, 0x17, 0x00, true);		// INT disabled.
    AccelWriteToRegister(accelHandle, 0x1a, 0x00, true);		// INT2 pin disabled
    AccelReadFromRegister(accelHandle, 0x0a, true);             // clear INGT flag
  }
  else
    accelHandle = 0;
}

