/* --COPYRIGHT--,BSD
 * Copyright (c) 2019, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * --/COPYRIGHT--*/
//*****************************************************************************
//         MSP430FR2355 - Dual Ray Smoke AFE Demo
//
// This application demonstrates dual-ray smoke detection function
// with FRAM based MSP430FR2355 device.
// Texas Instruments, Inc.
// Jan 2020
// Lixin C , Luis R
// ******************************************************************************
#include <stdbool.h>
#include <stdint.h>
#include <DualRaySmokeAFELib.h>
#include <DualRaySmokeAFE_HAL.h>
#include "DualRaySmokeAFE_App.h"

// Structure with smoke detector data.
tDualRaySmokeAFELib_DualRayData sDualRaySmokeAFELib_Data;
// ID for AlarmOnPeriodicTimerCallback
static uint16_t u16AlarmOnTimerCallbackID;
// ID for AlarmSilentPeriodicTimerCallback
static uint16_t u16AlarmSilentTimerCallbackID;
// Counter for Silent detection after alarm is ON.
static uint16_t  u16SilentDetectCount;
// Counter for Silent time after button is pressed
static uint16_t  u16AlarmSilentCount;

// Callback for push button to detect long and short presses
static bool TestButtonCallback(bool long_press);
// Callback for ULP Timer used to take new measurements
static bool ULPTimerCallback(void);
// Callback for Low-Power Periodic Timer used to keep track when alarm is ON.
static bool AlarmOnPeriodicTimerCallback();
// Callback for Low-Power Periodic Timer used to keep track when alarm is Silent.
static bool AlarmSilentPeriodicTimerCallback();
// Initialization function
static inline void AppInit(void);

//! \brief Implements a smoke detector AFE demo.
//!         Initializes the system and libraries and stays in periodic loop
//!         performing measurements and detecting smoke.
//!         If smoke is detected, an alarm will be set.
//!
//! \return none
void main(void)
{
    tDualRaySmokeAFE_AlarmDetectionState eNewAlarmState;
    bool toggleIndicatorLED;

    WDTCTL = WDTPW | WDTHOLD;               // Stop watchdog timer

    AppInit();

    __enable_interrupt();

    while(1)
    {
        toggleIndicatorLED = true;

        // Check if any of the control flags are set
        if (sDualRaySmokeAFE_AppConfig.APPCTL.all &
               (APPCTL_STARTCALIBRATION     | APPCTL_STARTTEMPERATUREMEAS   |
                APPCTL_STARTALARMTEST       | APPCTL_TURNALARMOFF           |
                APPCTL_TURNALARMSILENTON    | APPCTL_TURNALARMSILENTOFF     |
                APPCTL_CONFIGUPDATE ) )
        {
            if (sDualRaySmokeAFE_AppConfig.APPCTL.ConfigUpdate == true)
            {
                // clear flag
                sDualRaySmokeAFE_AppConfig.APPCTL.ConfigUpdate = false;
                // Reconfigure AFE and library
                DualRaySmokeAFELib_UpdateConfig(&sDualRaySmokeAFELib_Config,
                                                &sDualRaySmokeAFE_HALConfig);
#ifdef __ENABLE_GUI__
                // Send Configuration to GUI
                DualRaySmokeAFE_App_GUIComm_SendConfig();
#endif
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.StartCalibration == true)
            {
                // calibrate VLO every calibration interval time
                // clear flag
                sDualRaySmokeAFE_AppConfig.APPCTL.StartCalibration = false;
                // Trigger Calibration
                DualRaySmokeAFE_HAL_Timing_ULPTimer_calibrate();
                // blink two fast cycles and one slow cycles to indicate VLO re-calibration
                DualRaySmokeAFE_HAL_IO_ToggleLEDIndicatorTimer(4, 50);
                DualRaySmokeAFE_HAL_IO_ToggleLEDIndicatorTimer(2, 500);
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.StartTemperatureMeas == true)
            {
                // Perform temperature measurement every u16TempSensorInterval
                DualRaySmokeAFELib_TemperatureMeasurement(&sDualRaySmokeAFELib_Data);
                // Clear flag
                sDualRaySmokeAFE_AppConfig.APPCTL.StartTemperatureMeas = false;
#ifdef __ENABLE_GUI__
                // Send Temperature Data to GUI
                DualRaySmokeAFE_App_GUIComm_SendTemperature();
#endif
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.StartAlarmTest == true)
            {
                sDualRaySmokeAFE_AppConfig.APPCTL.StartAlarmTest = false;
                if (sDualRaySmokeAFE_AppConfig.APPSTATUS.AlarmDetectionEnabled == true)
                {
                    sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOn = true;
                }
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmSilentOn == true)
            {
                sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmSilentOn = false;
                if (sDualRaySmokeAFE_AppConfig.APPSTATUS.AlarmOn == true)
                {
                    // Turn alarm off and then in silent mode
                    DualRaySmokeAFE_App_TurnAlarmOff();
                    DualRaySmokeAFELib_setAlarmSilent();
                    u16AlarmSilentCount = 0;
                    // Enable timer callback to keep track of time with alarm silent
                    DualRaySmokeAFE_HAL_Timing_LPPerTimer_enableCallback(
                                                 u16AlarmSilentTimerCallbackID);
                }
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmSilentOff == true)
            {
                sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmSilentOff = false;
                sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOff = true;
                DualRaySmokeAFE_HAL_Timing_LPPerTimer_disableCallback(
                                                u16AlarmSilentTimerCallbackID);
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOff == true)
            {
                sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOff = false;
                DualRaySmokeAFE_App_TurnAlarmOff();
            }

            if (sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOn == true)
            {
                sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOn = false;
                DualRaySmokeAFE_App_TurnAlarmOn();
            }

        }

        // Check if a new measurement should be taken
        if (sDualRaySmokeAFE_AppConfig.APPCTL.StartMeasurement == true)
        {
            // clear flag
            sDualRaySmokeAFE_AppConfig.APPCTL.StartMeasurement = false;
            // Perform Measurement
            DualRaySmokeAFELib_Measurement(&sDualRaySmokeAFELib_Data);

#ifdef __ENABLE_GUI__
            // Send Measurement Data to GUI
            DualRaySmokeAFE_App_GUIComm_SendAppData();
#endif

            // disable interrupt to avoid issues with other events which
            // can modify APPSTATUS
            __disable_interrupt();

            if (sDualRaySmokeAFE_AppConfig.APPSTATUS.AlarmDetectionEnabled == true)
            {
                // Run algorithm to detect alarm
                eNewAlarmState = DualRaySmokeAFELib_DetectionAlgorithm(
                                                   &sDualRaySmokeAFELib_Data);
                // State machine for alarm detection
                switch (eNewAlarmState)
                {
                    case DUALRAYSMOKEAFE_ALARM_WARNING1:
                        // makes the sample period 1/4*WAKEUP_INTERVAL
                        DualRaySmokeAFE_App_Config_SetTempMeasIntervalmsec(
                              sDualRaySmokeAFE_AppConfig.u16MeasIntervalms>>2);
                    break;
                    case DUALRAYSMOKEAFE_ALARM_WARNING2:
                        // makes the sample period 1/8*WAKEUP_INTERVAL
                        DualRaySmokeAFE_App_Config_SetTempMeasIntervalmsec(
                              sDualRaySmokeAFE_AppConfig.u16MeasIntervalms>>3);
                    break;
                    case DUALRAYSMOKEAFE_ALARM_TURN_ON:
                        // makes the sample period 1/16*WAKEUP_INTERVAL
                        DualRaySmokeAFE_App_Config_SetTempMeasIntervalmsec(
                              sDualRaySmokeAFE_AppConfig.u16MeasIntervalms>>4);
                        sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOn = true;
                        toggleIndicatorLED = false;
                    break;
                    case DUALRAYSMOKEAFE_ALARM_TURN_OFF:
                        sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOff = true;
                    break;
                    case DUALRAYSMOKEAFE_ALARM_ON_THRESHOLDHIGH:
                        // If alarm is ON and above threshold, restart silent
                        //  detect counter
                        u16SilentDetectCount = 0;
                    case DUALRAYSMOKEAFE_ALARM_ON:
                    case DUALRAYSMOKEAFE_ALARM_ON_THRESHOLDLOW:
                    case DUALRAYSMOKEAFE_ALARM_SILENT:
                        // Don't toggle LED if alarm is ON or Silent
                        toggleIndicatorLED = false;
                        if (DualRaySmokeAFE_HAL_Timing_LPPerTimer_getStatus() == false)
                        {
                            // If no event is enabled, force Alarm Off Mode
                            sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOff = true;
                        }
                    break;
                }   // switch (eAlarmState)
            } // (AlarmDetectionEnabled == true)

            if (toggleIndicatorLED == true)
            {
                // 10ms LED toggle to indicate measurement
                DualRaySmokeAFE_HAL_IO_ToggleLEDIndicatorTimer(2, 10);
            }
        } //if (sDualRaySmokeAFE_AppConfig.APPCTL.StartMeasurement == true)

        __disable_interrupt();
        // If no flags set, go to sleep
        if (sDualRaySmokeAFE_AppConfig.APPCTL.all == 0x00)
        {
            if (DualRaySmokeAFE_HAL_Timing_LPPerTimer_getStatus() == true)
            {
                // Go to LPM3 if the Low Power Timer is enabled
                // I.e. Alarm is ON or the test button was pressed
                __bis_SR_register(LPM3_bits|GIE);
            }
            else
            {
                // Go to LPM4 if Low Power Periodic Timer is disabled
                // I.e. Alarm is OFF and no button pressed
                __bis_SR_register(LPM4_bits|GIE);
            }
        }

    } // while(1)
}

//! \brief Application initialization.
//!         Initializes the system and libraries.
//!
//! \return none
static inline void AppInit(void)
{
    // Initialize flags
    sDualRaySmokeAFE_AppConfig.APPCTL.all =
                                    sDualRaySmokeAFE_AppConfig.APPSTATUS.all;

    // Initialize System
    DualRaySmokeAFE_HAL_System_Init();
    // Set a callback function for Test button
    DualRaySmokeAFE_HAL_IO_SetTestButtonCallback(TestButtonCallback);
    // Initialize Timing
    DualRaySmokeAFE_HAL_Timing_Init();
    // Initialize Smoke Library
    DualRaySmokeAFELib_Init(&sDualRaySmokeAFELib_Config,
                            &sDualRaySmokeAFE_HALConfig);

    // Initialize the Low Power Periodic Timer
    DualRaySmokeAFE_HAL_Timing_LPPerTimer_setIntervalms(
                            sDualRaySmokeAFE_AppConfig.u16LPPerTimerIntervalms);
    // Register a callback function to keep timing when alarm is ON
    u16AlarmOnTimerCallbackID =
                    DualRaySmokeAFE_HAL_Timing_LPPerTimer_registerCallback(
                                               AlarmOnPeriodicTimerCallback);
    // Register a callback function to keep timing when alarm is silent
    u16AlarmSilentTimerCallbackID =
                    DualRaySmokeAFE_HAL_Timing_LPPerTimer_registerCallback(
                                            AlarmSilentPeriodicTimerCallback);

    // Set a callback function for the RTC
    DualRaySmokeAFE_HAL_Timing_ULPTimer_registerCallback(ULPTimerCallback);
    // Calibrate and start RTC
    DualRaySmokeAFE_HAL_Timing_ULPTimer_setIntervalms(
                            sDualRaySmokeAFE_AppConfig.u16MeasIntervalms);
    DualRaySmokeAFE_HAL_Timing_ULPTimer_calibrate();


#ifdef __ENABLE_GUI__
    DualRaySmokeAFE_App_GUIComm_Init();
#endif
    // Toggle LED ON/OFF 3 times to indicate start
    DualRaySmokeAFE_HAL_IO_ToggleLEDIndicatorTimer(6, 100);

    u16SilentDetectCount = 0;
    u16AlarmSilentCount = 0;
}

//! \brief Turns the alarm ON.
//!
//! \return none
void DualRaySmokeAFE_App_TurnAlarmOn(void)
{
    sDualRaySmokeAFE_AppConfig.APPSTATUS.AlarmOn = true;
    DualRaySmokeAFELib_setAlarmOn(
                            sDualRaySmokeAFE_AppConfig.APPSTATUS.HornEnabled);
    DualRaySmokeAFE_HAL_IO_SetLEDIndicator();
    DualRaySmokeAFE_HAL_IO_SetAlarmOutputHigh();
    // Enable timer callback to keep track of time with alarm ON
    DualRaySmokeAFE_HAL_Timing_LPPerTimer_enableCallback(
                                                    u16AlarmOnTimerCallbackID);
    u16SilentDetectCount = 0;
#ifdef __ENABLE_GUI__
       // Send Alarm status to GUI
    DualRaySmokeAFE_App_GUIComm_SendAlarmStatus();
#endif
}

//! \brief Turns the alarm OFF.
//!
//! \return none
void DualRaySmokeAFE_App_TurnAlarmOff(void)
{
    sDualRaySmokeAFE_AppConfig.APPSTATUS.AlarmOn = false;
    DualRaySmokeAFE_App_Config_RestoreDefaultIntervalmsec();
    DualRaySmokeAFELib_setAlarmOff();
    DualRaySmokeAFE_HAL_IO_ClearLEDIndicator();
    DualRaySmokeAFE_HAL_IO_SetAlarmOutputLow();
    // Disable timer callback
    DualRaySmokeAFE_HAL_Timing_LPPerTimer_disableCallback(
                                                    u16AlarmOnTimerCallbackID);
#ifdef __ENABLE_GUI__
   // Send Alarm status to GUI
   DualRaySmokeAFE_App_GUIComm_SendAlarmStatus();
#endif
}


//! \brief Callback function for Ultra Low Power Timer (RTC).
//!         Called by HAL ISR to trigger a new measurement.
//!
//! \return true to wake-up the MCU.
static bool ULPTimerCallback(void)
{
    static uint16_t count_calibration = 0;
    static uint16_t count_temperature = 0;

    // Sample LEDs on every interrupt
    sDualRaySmokeAFE_AppConfig.APPCTL.StartMeasurement = true;

    // If calibration is enabled (!=0), check for interval and set flag
    if (sDualRaySmokeAFE_AppConfig.APPSTATUS.RTCCalibEnabled == true)
    {
        count_calibration++;                   // count for RTC interval
        if (count_calibration >= sDualRaySmokeAFE_AppConfig.u16RTCCalibInterval)
        {
            sDualRaySmokeAFE_AppConfig.APPCTL.StartCalibration = true;
            count_calibration = 0;
        }
    }

    if (sDualRaySmokeAFE_AppConfig.APPSTATUS.TemperatureMeasEnabled == true)
    {
        count_temperature++;                   // count for temperature sensor
        if (count_temperature >=
                            sDualRaySmokeAFE_AppConfig.u16TempSensorInterval)
        {
            sDualRaySmokeAFE_AppConfig.APPCTL.StartTemperatureMeas = true;
            count_temperature = 0;
        }
    }

    // Return true to wake-up MCU
    return true;

}

//! \brief Callback function for Test button.
//!         Called by HAL ISR when test button is pressed.
//!
//! \param[in] long_press is true if a long press is detected.
//!             false if short press is detected.
//!
//! \return true to wake-up the MCU.
static bool TestButtonCallback(bool long_press)
{
    if (long_press == false)
    {
        // Short press, turn OFF alarm if enabled
        sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmSilentOn = true;
    }
    else
    {
        // long press, enable alarm test
        sDualRaySmokeAFE_AppConfig.APPCTL.StartAlarmTest = true;
    }
    return false;
}


//! \brief Callback function when alarm is ON.
//!         Uses the Low-Power Periodic timer.
//!         Used to count time for alarm to turn OFF.
//!
//! \return true to wake-up the MCU.
static bool AlarmOnPeriodicTimerCallback(void)
{
    if (u16SilentDetectCount++ >=
                sDualRaySmokeAFE_AppConfig.u16LPPerTimerCyclesBeforeAlarmOff)
    {
        sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmOff = true;
        return true;
    }

    // Don't wake-up MCU
    return false;
}

//! \brief Callback function when alarm is Silent.
//!         Uses the Low-Power Periodic timer.
//!         Used to count time when alarm is silent.
//!
//! \return true to wake-up the MCU.
static bool AlarmSilentPeriodicTimerCallback(void)
{
    if (u16AlarmSilentCount++ >=
                    sDualRaySmokeAFE_AppConfig.u16PPerTimerCyclesAlarmSilent)
    {
        sDualRaySmokeAFE_AppConfig.APPCTL.TurnAlarmSilentOff = true;
        return true;
    }

    // Don't wake-up MCU
    return false;
}
