/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    rtc_if.c
  * @author  MCD Application Team
  * @brief   Configure RTC Alarm, Tick and Calendar manager
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include <automation.h>
#include <math.h>
#include "main.h" /*for Mx generated RTC_N_PREDIV_S and RTC_N_PREDIV_A*/
#include <stdbool.h>
#include "rtc_if.h"
#include "utils.h"

/* USER CODE BEGIN Includes */
#include <string.h>
#include "commonMsg.h"
#include "actuator_mgr.h"
/* USER CODE END Includes */

/* External variables ---------------------------------------------------------*/
extern RTC_HandleTypeDef hrtc;

/* USER CODE BEGIN EV */

/* USER CODE END EV */

/* Private typedef -----------------------------------------------------------*/
typedef struct
{
  uint32_t Rtc_Time; /* Reference time */

  RTC_TimeTypeDef RTC_Calndr_Time; /* Reference time in calendar format */

  RTC_DateTypeDef RTC_Calndr_Date; /* Reference date in calendar format */

} RtcTimerContext_t;

/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
#define MAX_TS_SIZE (int) 16

#define MIN_ALARM_DELAY               3 /* in ticks */
/* RTC Ticks/ms conversion */

#define RTC_BKP_SECONDS    RTC_BKP_DR0
#define RTC_BKP_SUBSECONDS RTC_BKP_DR1
#define RTC_BKP_MSBTICKS   RTC_BKP_DR2

/* Sub-second mask definition  */
#define RTC_ALARMSUBSECONDMASK    RTC_N_PREDIV_S<<RTC_ALRMASSR_MASKSS_Pos

/* RTC Time base in us */
#define USEC_NUMBER               1000000
#define MSEC_NUMBER               (USEC_NUMBER/1000)

#define COMMON_FACTOR        3
#define CONV_NUMER                (MSEC_NUMBER>>COMMON_FACTOR)
#define CONV_DENOM                (1<<(RTC_N_PREDIV_S-COMMON_FACTOR))

#define  DAYS_IN_LEAP_YEAR (uint32_t) 366

#define  DAYS_IN_YEAR      (uint32_t) 365

#define  SECONDS_IN_1DAY   (uint32_t) 86400

#define  SECONDS_IN_1HOUR   (uint32_t) 3600

#define  SECONDS_IN_1MINUTE   (uint32_t) 60

#define  MINUTES_IN_1HOUR    (uint32_t) 60

#define  HOURS_IN_1DAY      (uint32_t) 24

#define  DAYS_IN_MONTH_CORRECTION_NORM     ((uint32_t) 0x99AAA0 )
#define  DAYS_IN_MONTH_CORRECTION_LEAP     ((uint32_t) 0x445550 )

/* Calculates ceiling(X/N) */
#define DIVC(X,N)   ( ( (X) + (N) -1 ) / (N) )

//#define RTIF_DEBUG

/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/

#ifdef RTIF_DEBUG
#include "sys_app.h" /*for app_log*/
#define RTC_IF_DBG_PRINTF(...) do{ {UTIL_ADV_TRACE_COND_FSend(VLEVEL_ALWAYS, T_REG_OFF, TS_OFF, __VA_ARGS__);} }while(0);
#else
#define RTC_IF_DBG_PRINTF(...)
#endif /* RTIF_DEBUG */

/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
RTC_AlarmTypeDef sAlarm;
char cur_time[80], cur_date[80];

/*!
 * \brief Indicates if the RTC is already Initialized or not
 */
static bool RTC_Initalized = false;

/*!
 * Keep the value of the RTC timer when the RTC alarm is set
 * Set with the RTC_SetTimerContext function
 * Value is kept as a Reference to calculate alarm
 */
static RtcTimerContext_t RtcTimerContext;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/

/*!
 * @brief get current time from calendar in ticks
 * @param pointer to RTC_DateStruct
 * @param pointer to RTC_TimeStruct
 * @retval time in ticks
 */
static uint32_t RTC_GetCalendarValue(RTC_DateTypeDef *RTC_DateStruct, RTC_TimeTypeDef *RTC_TimeStruct);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Exported variables ---------------------------------------------------------*/

/*Timer driver*/
const UTIL_TIMER_Driver_s UTIL_TimerDriver =
{
  RTC_IF_Init,

  RTC_IF_SetTimerContext,
  RTC_IF_GetTimerContext,

  RTC_IF_GetTimerElapsedTime,
  RTC_IF_GetTimerValue,
  RTC_IF_GetMinimumTimeout,

  RTC_IF_Convert_ms2Tick,
  RTC_IF_Convert_Tick2ms,
};

/*System Time driver*/
const UTIL_SYSTIM_Driver_s UTIL_SYSTIMDriver =
{
  RTC_IF_BkUp_Write_Seconds,
  RTC_IF_BkUp_Read_Seconds,
  RTC_IF_BkUp_Write_SubSeconds,
  RTC_IF_BkUp_Read_SubSeconds,
  RTC_IF_GetTime,
};

/* USER CODE BEGIN Exported_Variables */
/* USER CODE END Exported_Variables */

/* Exported functions ---------------------------------------------------------*/
/* USER CODE BEGIN Exported_Functions */

/* USER CODE END Exported_Functions */

void RTC_IF_Init(void)
{
  if (RTC_Initalized == false)
  {
    RTC_IF_DBG_PRINTF("RTC_init\n\r");

    MX_RTC_Init();

    /** Configure the Alarm A */
    HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
    /** Configure the Alarm B */
    HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_B);
    /*Enable Direct Read of the calendar registers (not through Shadow) */
    HAL_RTCEx_EnableBypassShadow(&hrtc);

    RTC_IF_SetTimerContext();

    RTC_Initalized = true;
  }
}

uint32_t RTC_IF_SetTimerContext(void)
{
  /*store time context*/
  RtcTimerContext.Rtc_Time = RTC_GetCalendarValue(&RtcTimerContext.RTC_Calndr_Date, &RtcTimerContext.RTC_Calndr_Time);
  RTC_IF_DBG_PRINTF("RTC_IF_SetTimerContext=%d\n\r", RtcTimerContext.Rtc_Time);
  /*return time context*/
  return (uint32_t) RtcTimerContext.Rtc_Time;
}

uint32_t RTC_IF_GetTimerContext(void)
{
  RTC_IF_DBG_PRINTF("RTC_IF_GetTimerContext=%d\n\r", RtcTimerContext.Rtc_Time);
  /*return time context*/
  return (uint32_t) RtcTimerContext.Rtc_Time;
}

uint32_t RTC_IF_GetTimerElapsedTime(void)
{
  RTC_TimeTypeDef RTC_TimeStruct;
  RTC_DateTypeDef RTC_DateStruct;

  uint32_t CalendarValue = RTC_GetCalendarValue(&RTC_DateStruct, &RTC_TimeStruct);

  return ((uint32_t)(CalendarValue - RtcTimerContext.Rtc_Time));
}

uint32_t RTC_IF_GetTimerValue(void)
{
  uint32_t CalendarValue = 0;
  RTC_TimeTypeDef RTC_TimeStruct;
  RTC_DateTypeDef RTC_DateStruct;

  if (RTC_Initalized == true)
  {
    CalendarValue = (uint32_t) RTC_GetCalendarValue(&RTC_DateStruct, &RTC_TimeStruct);
  }
  return CalendarValue;
}

uint32_t RTC_IF_GetMinimumTimeout(void)
{
  return (MIN_ALARM_DELAY);
}

uint32_t RTC_IF_Convert_ms2Tick(uint32_t timeMilliSec)
{
  return (uint32_t)((((uint64_t)timeMilliSec) * CONV_DENOM) / CONV_NUMER);
}

uint32_t RTC_IF_Convert_Tick2ms(uint32_t tick)
{
  return (((uint64_t)(tick) * CONV_NUMER) / CONV_DENOM);
}

uint32_t RTC_IF_GetTime(uint16_t *mSeconds)
{
  RTC_TimeTypeDef RTC_TimeStruct ;
  RTC_DateTypeDef RTC_DateStruct;
  uint32_t ticks;

  uint64_t calendarValue = RTC_GetCalendarValue(&RTC_DateStruct, &RTC_TimeStruct);

  uint32_t seconds = (uint32_t)(calendarValue >> RTC_N_PREDIV_S);

  ticks = (uint32_t) calendarValue & RTC_PREDIV_S;

  *mSeconds = RTC_IF_Convert_Tick2ms(ticks);

  return seconds;
}

void RTC_IF_BkUp_Write_Seconds(uint32_t Seconds)
{
  HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_SECONDS, Seconds);
}

void RTC_IF_BkUp_Write_SubSeconds(uint32_t SubSeconds)
{
  HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_SUBSECONDS, SubSeconds);
}

uint32_t RTC_IF_BkUp_Read_Seconds(void)
{
  return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_SECONDS);
}

uint32_t RTC_IF_BkUp_Read_SubSeconds(void)
{
  return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_SUBSECONDS);
}

/* USER CODE BEGIN EF */

/* USER CODE END EF */

/* Private functions ---------------------------------------------------------*/

__STATIC_INLINE uint32_t _GetSubSecond(RTC_TypeDef *RTCx)
{
  return (uint32_t)(READ_BIT(RTCx->SSR, RTC_SSR_SS));
}

/*!
  * @brief get current time from calendar in ticks
  * @param pointer to RTC_DateStruct
  * @param pointer to RTC_TimeStruct
  * @retval time in ticks
  */
static uint32_t RTC_GetCalendarValue(RTC_DateTypeDef *RTC_DateStruct, RTC_TimeTypeDef *RTC_TimeStruct)
{
  uint32_t calendarValue = 0;
  uint32_t first_read;
  uint32_t correction;

  /* Get Time and Date*/
  HAL_RTC_GetTime(&hrtc, RTC_TimeStruct, RTC_FORMAT_BIN);

  /* make sure it is correct due to asynchronus nature of RTC*/
  do
  {
    first_read = _GetSubSecond(RTC);
    HAL_RTC_GetDate(&hrtc, RTC_DateStruct, RTC_FORMAT_BIN);
    HAL_RTC_GetTime(&hrtc, RTC_TimeStruct, RTC_FORMAT_BIN);

  } while (first_read != _GetSubSecond(RTC));

  /* calculte amount of elapsed days since 01/01/2000 */
  calendarValue = DIVC((DAYS_IN_YEAR * 3 + DAYS_IN_LEAP_YEAR) * RTC_DateStruct->Year, 4);

  correction = ((RTC_DateStruct->Year % 4) == 0) ? DAYS_IN_MONTH_CORRECTION_LEAP : DAYS_IN_MONTH_CORRECTION_NORM ;

  calendarValue += (DIVC((RTC_DateStruct->Month - 1) * (30 + 31),
                         2) - (((correction >> ((RTC_DateStruct->Month - 1) * 2)) & 0x3)));

  calendarValue += (RTC_DateStruct->Date - 1);

  /* convert from days to seconds */
  calendarValue *= SECONDS_IN_1DAY;

  calendarValue += ((uint32_t)RTC_TimeStruct->Seconds +
                    ((uint32_t)RTC_TimeStruct->Minutes * SECONDS_IN_1MINUTE) +
                    ((uint32_t)RTC_TimeStruct->Hours * SECONDS_IN_1HOUR)) ;

  calendarValue = (calendarValue << RTC_N_PREDIV_S) + (RTC_PREDIV_S - RTC_TimeStruct->SubSeconds);

  return (calendarValue);
}

void TimestampNow(uint8_t *buff, uint16_t *size)
{
  /* USER CODE BEGIN TimestampNow_1 */

  /* USER CODE END TimestampNow_1 */
  uint32_t ms = HAL_GetTick();
  snprintf((char *)buff, MAX_TS_SIZE, "%02ld:%02ld:%02ld.%03ld: ", ms/(60*60*1000)%24, ms/(60*1000)%60, (ms/1000)%60, ms%1000);
  *size = strlen((char *)buff);
  /* USER CODE BEGIN TimestampNow_2 */

  /* USER CODE END TimestampNow_2 */
}

void RTC_calibration( int16_t cal )
{
	int16_t cal_p, cal_m;

	HAL_RTCEx_SetCalibrationOutPut(&hrtc, RTC_CALIBOUTPUT_512HZ);
    if (cal > 0)
    {
        cal_p = RTC_SMOOTHCALIB_PLUSPULSES_SET;
        cal_m = 512 - cal;
    }
    else
    {
        cal_p = RTC_SMOOTHCALIB_PLUSPULSES_RESET;
        cal_m = -cal;
    }
    HAL_RTCEx_SetSmoothCalib(&hrtc, RTC_SMOOTHCALIB_PERIOD_32SEC, cal_p, cal_m);

    HAL_RTCEx_DeactivateCalibrationOutPut(&hrtc);
}

int16_t RTC_get_calibration()
{
int16_t cal;

    if (hrtc.Instance->CALR & 0x8000)
    {
        cal = 512 - (hrtc.Instance->CALR & 0x1ff);
    }
    else
    {
        cal = -(hrtc.Instance->CALR & 0x1ff);
    }
    return cal;
}

// Michael Keith and Tom Craver published an expression to minimise the number of keystrokes
// needed to enter a self-contained function for converting a Gregorian date into a numerical day of the week
uint8_t get_weekday( RTC_DateTypeDef sDate )
{
	int d = sDate.Date;
	int m = sDate.Month;
	int y = sDate.Year + 2000;
	uint8_t weekday  = (d += m < 3 ? y-- : y - 2, 23*m/9 + d + 4 + y/4- y/100 + y/400)%7;

	return weekday;
}

uint8_t now( struct tm* tm )
{
RTC_DateTypeDef gDate;
RTC_TimeTypeDef gTime;

	/* Get the RTC current Time */
	HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
	/* Get the RTC current Date */
	HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);

	uint8_t weekday = get_weekday( gDate );  // Find the day of the week : 0:Sunday -> 6 Saturday
	tm->tm_wday = weekday;
	tm->tm_year = gDate.Year + 100;
	tm->tm_mday = gDate.Date;
	tm->tm_mon = gDate.Month - 1;
	tm->tm_hour = gTime.Hours;
	tm->tm_min = gTime.Minutes;
	tm->tm_sec = gTime.Seconds;
	tm->tm_isdst = -1;
	tm->tm_yday = 275 * gDate.Month / 9 - 30 + gDate.Date - 2 % gDate.Month;

	return weekday;
}

// yy: just the decade 2021 means 21
void set_time( uint8_t dd, uint8_t mm, uint8_t yy, uint8_t hh, uint8_t min, uint8_t sec)
{
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;

	sTime.Hours = hh; // set hours
	sTime.Minutes = min; // set minutes
	sTime.Seconds = sec; // set seconds
	sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
	sTime.StoreOperation = RTC_STOREOPERATION_RESET;
	if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
	{
		Error_Handler();
	}

	sDate.Month = mm; // month
	sDate.Date = dd; // date
	sDate.Year = yy; // year
	sDate.WeekDay = get_weekday(sDate); // week day
	if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
	{
		Error_Handler();
	}
	HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x32F2); // backup register
}

uint32_t get_EpochTime()
{
	struct tm datetime;

	now(&datetime);
	return mktime(&datetime);
}

uint8_t get_alarmA_time( uint8_t *hr, uint8_t *min, uint8_t *sec )
{
	RTC_DateTypeDef gDate;

	/* Get the RTC current Date */
	HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);

	HAL_RTC_GetAlarm(&hrtc, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN);
	*hr = sAlarm.AlarmTime.Hours;
	*min = sAlarm.AlarmTime.Minutes;
	*sec = sAlarm.AlarmTime.Seconds;

	return sAlarm.AlarmDateWeekDay;
}

void stop_alarmA()
{
	HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
}

void stop_alarmB()
{
	HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_B);
}

int get_time(void)
{
	RTC_DateTypeDef gDate;
	RTC_TimeTypeDef gTime;
	uint32_t first_read;

	/* Get the RTC current Time */
	HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
	/* make sure it is correct due to asynchronous nature of RTC*/
	do
	{
		first_read = _GetSubSecond(RTC);
		HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);
		HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
	} while (first_read != _GetSubSecond(RTC));

	/* Display time Format: hh:mm:ss */
	snprintf((char*)cur_time,sizeof(cur_time),"%02d:%02d:%02d",gTime.Hours, gTime.Minutes, gTime.Seconds);
	/* Display date Format: dd-mm-yy */
	snprintf((char*)cur_date,sizeof(cur_date),"%02d/%02d/%4d",gDate.Date, gDate.Month, 2000 + gDate.Year);

	return gTime.Hours * 60 + gTime.Minutes;
}

void set_alarmA( struct tm* alarm_tm )
{
	sAlarm.AlarmTime.Hours = alarm_tm->tm_hour; // hours
	sAlarm.AlarmTime.Minutes = alarm_tm->tm_min; // min
	sAlarm.AlarmTime.Seconds = alarm_tm->tm_sec; //seconds
	sAlarm.AlarmTime.SubSeconds = 0x0;
	sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
	sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
	sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
	sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
	sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
	sAlarm.AlarmDateWeekDay = alarm_tm->tm_mday; // DATE
	sAlarm.Alarm = RTC_ALARM_A;
	if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
	{
		Error_Handler();
	}
}

void set_alarmB( struct tm* alarm_tm )
{
	sAlarm.AlarmTime.Hours = alarm_tm->tm_hour; // hours
	sAlarm.AlarmTime.Minutes = alarm_tm->tm_min; // min
	sAlarm.AlarmTime.Seconds = 0x0;
	sAlarm.AlarmTime.SubSeconds = 0x0;
	sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
	sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
	sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
	sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
	sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
	sAlarm.AlarmDateWeekDay = alarm_tm->tm_mday; // DATE
	sAlarm.Alarm = RTC_ALARM_B;
	if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
	{
		Error_Handler();
	}
}

void updateAutomation()
{
	osMessageQId destQueue = getQueueId(AUTOMATION);

	MSG_STRUCT *p_sendMsg = allocMemoryPoolMessage(); // receiver must free
	p_sendMsg->opcode = OP_RTC;
	p_sendMsg->outputs = ALL_OUTPUTS_OFF;
	p_sendMsg->changeState = Z_UPDATE;
	osMessagePut(destQueue, (uint32_t)p_sendMsg, osWaitForever);
}

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
	updateAutomation();
}

void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
{
	updateAUXtimer( AUX_230VAC_PORT );
}

/* USER CODE BEGIN PrFD */

/* USER CODE END PrFD */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
