/**
 * @file	   sdl.c
 *
 * @copyright  Nehemis SARL reserves all rights even in the event of industrial
 *             property rights. We reserve all rights of disposal such as
 *             copying and passing on to third parties.
 *
 * @brief	   This file contains the initialization and execution logic for
 *             the Valve Controller and CANopen stack. It defines the main
 *             scheduler loop for running these components within the firmware.
 *
 * @todo	   SDL_MIN_OPERATIONAL_NODES to be changed to 20, are the node is fixed.
 * 			   (electronic issue in the board)
 *
 */

/******************************************************************************
 * Include Header Files
 ******************************************************************************/
/* CANopen includes */
#include <co_canopen.h>
#include <gen_define.h>
#include <co_canopen.h>
#include "co_sdo.h"
#include "co_odaccess.h"

/* User includes */
#include "sdl.h"
#include "processBoard.h"
#include "enduranceTestBench.h"
#include "hal_system.h"

#include "stdlib.h"
/******************************************************************************
 * Macro constant declarations
 ******************************************************************************/
#define SDL_CANOPEN_BITRATE					250u
#define SDL_PU_MASTER_NODE_ID				1u
#define SDL_LSS_TIMEOUT						20u		/* Defined in stack examples */
#define SDL_LSS_CONFIG_TIMEOUT				1000u
#define SDL_LSS_CONFIG_INTERVAL				4u
#define SDL_APPL_TIMER_TIME					250000uL
#define SDL_LSS_NODE_COUNT					20u
#define SDL_LSS_NODE_HB_MS					1000uL
#define SDL_LSS_NODE_STATE_RESET_INTERVAL 	3u

#define SDL_TEST_BENCH_SDO_TIMEOUT			1000u
#define SDL_TEST_BENCH_STARTUP_TIMEOUT		10000uLL

#define SDL_POSITION_SETPOINT_INDEX			0x6002
#define SDL_POSITION_SETPOINT_SUB_INDEX		0x0
#define SDL_POSITION_FEEDBACK_INDEX			0x6004
#define SDL_POSITION_FEEDBACK_SUB_INDEX		0x0

#define SDL_SDO_CLIENT_COB					0x1280
#define SDL_SDO_CLIENT_TO_SERVER_COB		0x600
#define SDL_SDO_SERVER_TO_CLIENT_COB		0x580

#define SDL_MIN_OPERATIONAL_NODES			18u		/* To be 20, one node not in working state currently */
/******************************************************************************
 * Type Declarations
 ******************************************************************************/
typedef struct
{
    uint32 vendorId_u32;
    uint32 productId_u32;
    uint32 versionNbr_u32;
    uint32 serialNbr_u32;
    uint8 nodeId_u8;
} SdlLssNodeInfo_t;

/******************************************************************************
 * Global Declarations
 ******************************************************************************/
static bool 		testBenchStarted_b;
SdlLssNodeInfo_t 	var_gst;
static CO_TIMER_T 	monitorTimer_gst;						/**< application timer */
static bool 		monitorSleep_gb = CO_FALSE;				/**< sleep flag */
static bool   		masterStarted_gb;						/**< master started flag */
static const SdlLssNodeInfo_t nodeLookupTable_gast[SDL_LSS_NODE_COUNT] =
{
	    {0x319, 0x4d2,   0x1,   0x01, 0x5} ,
	    {0x319, 0x4d2,   0x1,   0x02, 0x6} ,
	    {0x319, 0x4d2,   0x1,   0x03, 0x7} ,
	    {0x319, 0x4d2,   0x1,   0x04, 0x8} ,
	    {0x319, 0x4d2,   0x1,   0x05, 0x9} ,
	    {0x319, 0x4d2,   0x1,   0x06, 0xA} ,
	    {0x319, 0x4d2,   0x1,   0x07, 0xB} ,
	    {0x319, 0x4d2,   0x1,   0x08, 0xC} ,
	    {0x319, 0x4d2,   0x1,   0x09, 0xD} ,
	    {0x319, 0x4d2,   0x1,   0x0A, 0xE} ,
	    {0x319, 0x4d2,   0x1,   0x0B, 0xF} ,
	    {0x319, 0x4d2,   0x1,   0x0C, 0x10},
	    {0x319, 0x4d2,   0x1,   0x0D, 0x11},
	    {0x319, 0x4d2,   0x1,   0x0E, 0x12},
	    {0x319, 0x4d2,   0x1,   0x0F, 0x13},
	    {0x319, 0x4d2,   0x1,   0x10, 0x14},
	    {0x319, 0x4d2,   0x1,   0x11, 0x15},
	    {0x319, 0x4d2,   0x1,   0x12, 0x16},
	    {0x319, 0x4d2,   0x1,   0x13, 0x17},
	    {0x319, 0x4d2,   0x1,   0x14, 0x18}
};
static CO_NMT_STATE_T nodeNMTState_gaen[SDL_LSS_NODE_COUNT];

/******************************************************************************
 * Static function Declarations
 ******************************************************************************/
static void SdlInitCanopen(void);
static void SdlRunCanopen(void);
static RET_T SdlOverwriteLoadIndication(uint8 index_u8);
static void SdlLssIndCallback(CO_LSS_MASTER_SERVICE_T service_en, uint16 errorCode_u16, uint8 errorSpec_u8, uint32 *pIdentity_pu32);
static void SdlLssHbMonitorCallback(uint8 nodeID_u8, CO_ERRCTRL_T state_en, CO_NMT_STATE_T nmtState_en);
static void SdlLssResetNodesCallback(void *pData_pv);									/**< callback of reset handler */
static uint8 SdlLssGetNodeIndex(uint8 nodeId_u8);
static void SdlLssToggleMonitorFlag(void *pData);
static void SdlLssNodeHandlerRun(void);
static uint32 SdlLssConfigureNode(uint8 arrIndex_u8);
static RET_T SdlLssSetSdoCobID(uint8 sdoNr_u8, uint8 nodeId_u8);
/* Supress warnings for implicit declaration.
 * Need this function for overwriting the manual COB ids
 * for the TPDO. */
extern void userOverwriteCobIdSettings(void);
static bool SdlAreAllNodesOperational(void);

/******************************************************************************
 * Public Function Definitions
 ******************************************************************************/
void SdlInit(void)
{
	testBenchStarted_b = false;
	SdlInitCanopen();
}


void SdlRun(void)
{
	SdlRunCanopen();
	SdlLssNodeHandlerRun();
	if (SdlAreAllNodesOperational())
	{
        EnduranceTestBenchRun(&testBenchStarted_b);
        if (testBenchStarted_b == true)
        {
            ProcessBoardRun();
        }
	}
}


/******************************************************************************
 * Private Function Declarations
 ******************************************************************************/
/**
 * @brief	Init the CANopen stack.
 *
 */
static void SdlInitCanopen(void)
{
	RET_T retval;
	HalSystemInit();
	codrvHardwareInit();
	retval = codrvCanInit(SDL_CANOPEN_BITRATE);
	retval = codrvTimerSetup(CO_TIMER_INTERVAL);

	/* CANopen Initialization */
	retval = coCanOpenStackInit(SdlOverwriteLoadIndication);
	retval = coEventRegister_LSS_MASTER(SdlLssIndCallback);
	retval = coEventRegister_ERRCTRL(SdlLssHbMonitorCallback);

	/* Register SDO event handlers */
	coEventRegister_SDO_CLIENT_READ(EnduranceTestBenchReadInd);
    coEventRegister_SDO_CLIENT_WRITE(EnduranceTestBenchWriteInd);

	/* enable CAN communication */
	retval = codrvCanEnable();
	retval = coTimerStart(&monitorTimer_gst, SDL_APPL_TIMER_TIME, SdlLssToggleMonitorFlag, NULL, CO_TIMER_ATTR_ROUNDUP_CYCLIC);
	//retval = coTimerStart(&nodeResetTimer_gst, (SDL_LSS_NODE_STATE_RESET_INTERVAL * 1000000uLL), SdlLssResetNodesCallback, NULL, CO_TIMER_ATTR_ROUNDUP_CYCLIC);

	coLssIdentifyNonConfiguredSlaves(SDL_LSS_CONFIG_TIMEOUT, SDL_LSS_CONFIG_INTERVAL);
	retval = coNmtStateReq(SDL_PU_MASTER_NODE_ID, CO_NMT_STATE_OPERATIONAL, CO_TRUE);
}


/**
 * @brief	Runs the CANopen stack.
 *
 */
static void SdlRunCanopen(void)
{
    coCommTask();
    SdlLssNodeHandlerRun();
}


/******************************************************************************
 * Private Function Definitions
 ******************************************************************************/
/**
 * @brief	Handler for the defined nodes in the network.
 * 			This function starts the configuration of a node if needed
 *
 * @return	void
 *
 */
static void SdlLssNodeHandlerRun(void)
{
    if (monitorSleep_gb != CO_TRUE)
    {
		for (uint8 i_u8 = 0u; i_u8 < SDL_LSS_NODE_COUNT; i_u8++)
		{
			if (nodeNMTState_gaen[i_u8] == CO_NMT_STATE_PREOP)
			{
				SdlLssConfigureNode(i_u8);
			}
			if (nodeNMTState_gaen[i_u8] == CO_NMT_STATE_OPERATIONAL)
			{
				/* Do nothing */
			}
		}
    }
    monitorSleep_gb = CO_TRUE;
}


/**
 * @brief	Load function for overwriing the TPDO COB id values.
 *
 * @param	index_u8	Subindex parameter to point parameter area(unused)
 *
 * @return	Error Code
 */
static RET_T SdlOverwriteLoadIndication(uint8 index_u8)
{
    userOverwriteCobIdSettings();

    return RET_OK;
}


/**
 * @brief	This function is called if a new NMT state of a node,
 * 			is recordnized by the CANopen stack.
 *
 * @param	nodeID_u8	Node id of the node that has registered a change.
 * 			state_en	Error control state
 * 			nmtState_en	NMT state of the particular node.
 *
 * @return	Array index of the node in the network.
 *
 */
static uint8 SdlLssGetNodeIndex(uint8 nodeId_u8)
{
	uint8 arrIndex_u8;

	/* find node ID array arrIndex_u8 */
	for (arrIndex_u8 = 0u; arrIndex_u8 < SDL_LSS_NODE_COUNT; arrIndex_u8++)
	{
		if (nodeId_u8 == nodeLookupTable_gast[arrIndex_u8].nodeId_u8)
		{
			return arrIndex_u8;
		}
	}

	return NMS_UINT8_MAX;
}


/**
 * @brief	This function is called if a new NMT state of a node,
 * 			is recordnized by the CANopen stack.
 *
 * @param	nodeID_u8	Node id of the node that has registered a change.
 * 			state_en	Error control state
 * 			nmtState_en	NMT state of the particular node.
 *
 * @return	Array index of the node in the network.
 *
 */
static void SdlLssToggleMonitorFlag(void *pData)
{
	(void)pData;

	/* deactivate application sleep flag */
	monitorSleep_gb = CO_FALSE;
}


/**
* @brief	Configure a specific node.
* 			This function adds the defined node to heartbeat consumers,
* 			and sets its heartbeat interval.
*
* @param	arrIndex_u8	Index of the node in the array which is to be configured.
*
* @return	Status of the operation.
*
*/
static uint32 SdlLssConfigureNode(uint8 arrIndex_u8)
{
    uint32 error_u32 	= SDL_DEFAULT_ERROR;
    RET_T retVal_en		= RET_INTERNAL_ERROR;

    /* Get the node ID from the lookup table using the index passed */
    uint8 nodeId_u8 	= nodeLookupTable_gast[arrIndex_u8].nodeId_u8;

    /* Add node to heartbeat consumers with +25% tolerance
     * Rationale - Allows some flexibility in detecting timeouts due to minor clock drifts,
     * bus delays, or transmission issues.*/
    retVal_en = coHbConsumerSet(nodeId_u8,
    		((SDL_LSS_NODE_HB_MS / 100u * 25u) + SDL_LSS_NODE_HB_MS));
    retVal_en = coHbConsumerStart(nodeId_u8);

    /* setup SDO channel */
    retVal_en = SdlLssSetSdoCobID((arrIndex_u8 + 1u), nodeLookupTable_gast[arrIndex_u8].nodeId_u8);

    /* start node */
    retVal_en = coNmtStateReq(nodeId_u8, CO_NMT_STATE_OPERATIONAL, CO_FALSE);
    if (retVal_en != RET_OK)
    {
    	error_u32 = SDL_LSS_NODE_CONFIG_ERROR;
    }
    /* set local state to operational, if not operational yet */
    if (masterStarted_gb == CO_FALSE)
    {
        /* set local state */
        coNmtLocalStateReq(CO_NMT_STATE_OPERATIONAL);

        /* save started flag */
        masterStarted_gb = CO_TRUE;
    }

    return error_u32;
}


/**
* @brief 		This function configures a SDO client for the
* 				given node ID and SDO number.
*
* @param		sdoNr_u8	Sdo number
* 				nodeId_u8	Node id of the node.
*
* @return		Error state.
*
*/
static RET_T SdlLssSetSdoCobID(uint8 sdoNr_u8, uint8 nodeId_u8)
{
	uint16 cobIndex_u16;
	uint32 newCobId_u32;
	RET_T retVal_en = RET_INTERNAL_ERROR;

	/* get od index of sdoNr */
	cobIndex_u16 = SDL_SDO_CLIENT_COB + sdoNr_u8 - 1u;	/* SDO starts from 1280 index */

	/* set cobID for client to server direction (request) */
	newCobId_u32 = SDL_SDO_CLIENT_TO_SERVER_COB + nodeId_u8;
	retVal_en = coOdSetCobid(cobIndex_u16, 1u, newCobId_u32);

	if (retVal_en == RET_OK)
	{
		/* set cobID for server to client direction (response) */
		newCobId_u32 = SDL_SDO_SERVER_TO_CLIENT_COB + nodeId_u8;
		retVal_en = coOdSetCobid(cobIndex_u16, 2u, newCobId_u32);
	}

	/* print failed setup details */
	if (retVal_en != RET_OK)
	{
		/* ERROR */
	}

	return retVal_en;
}


/**
 * @brief	Checks if all nodes are in OPERATIONAL state.
 *
 * @return	CO_TRUE if all nodes are OPERATIONAL, CO_FALSE otherwise.
 */
static bool SdlAreAllNodesOperational(void)
{
	uint8 operationalNodeNb_u8 = 0u;

    for (uint8 i_u8 = 0u; i_u8 < SDL_LSS_NODE_COUNT ; i_u8++)
    {
        if (nodeNMTState_gaen[i_u8] == CO_NMT_STATE_OPERATIONAL)
        {
        	operationalNodeNb_u8++;
        }
    }

    return (operationalNodeNb_u8 >= SDL_MIN_OPERATIONAL_NODES) ? CO_TRUE : CO_FALSE;
}
/******************************************************************************
 * Callback Function Definitions
 ******************************************************************************/
/**
 * @brief	LSS indication function for handling the LSS api calls for dynamic
 * 			setup of node ids.
 *
 * @param	service_en		 LSS master services for indication functions
 * 			errorCode_u16	 Error code in the module
 * 			errorSpec_u8	 Specific error case that has occured
 * 			pIdentity_pu32	 LSS slave identity.
 *
 */
static void SdlLssIndCallback(CO_LSS_MASTER_SERVICE_T service_en, uint16 errorCode_u16, uint8 errorSpec_u8, uint32 *pIdentity_pu32)
{
	static uint8 matchedIndex_u8 = NMS_UINT8_MAX;

    if (errorCode_u16 != 0u)
    {
        if (errorCode_u16 == NMS_UINT16_MAX)
        {
        	/* ERROR */
        }
        else
        {
        	 /* ERROR */
        }

        if (service_en == CO_LSS_MASTER_SERVICE_STORE)
        {
        	 /* DEBUG INFO */
            coLssSwitchGlobal(CO_LSS_STATE_WAITING);
        }
        return;
    }

    switch (service_en)
    {
        case CO_LSS_MASTER_SERVICE_NON_CONFIG_SLAVE:
        	/* DEBUG INFO */
            coLssFastScan(SDL_LSS_TIMEOUT);
            break;

        case CO_LSS_MASTER_SERVICE_FASTSCAN:
			/* Match detected node with lookup table */
			for (uint8 i_u8 = 0u; i_u8 < SDL_LSS_NODE_COUNT; i_u8++)
			{
				if (pIdentity_pu32[0] == nodeLookupTable_gast[i_u8].vendorId_u32   &&
					pIdentity_pu32[1] == nodeLookupTable_gast[i_u8].productId_u32  &&
					pIdentity_pu32[2] == nodeLookupTable_gast[i_u8].versionNbr_u32 &&
					pIdentity_pu32[3] == nodeLookupTable_gast[i_u8].serialNbr_u32)
				{
					coLssSwitchSelective(pIdentity_pu32[0], pIdentity_pu32[1],
										 pIdentity_pu32[2], pIdentity_pu32[3], 20);
					matchedIndex_u8 = i_u8;
					break;
				}
			}
            break;

        case CO_LSS_MASTER_SERVICE_SWITCH_SELECTIVE:
            if (matchedIndex_u8 < SDL_LSS_NODE_COUNT)
            {
                coLssSetNodeId(nodeLookupTable_gast[matchedIndex_u8].nodeId_u8, SDL_LSS_TIMEOUT);
                matchedIndex_u8 = NMS_UINT8_MAX;
            }
            else
            {
            	/* ERROR */
            }
            break;

        case CO_LSS_MASTER_SERVICE_SET_NODEID:
        	/* DEBUG INFO */
            coLssInquireNodeId(SDL_LSS_TIMEOUT);
            break;

        case CO_LSS_MASTER_SERVICE_INQUIRE_NODEID:
        	/* DEBUG INFO */
            coLssStoreConfig(200);
            break;

        case CO_LSS_MASTER_SERVICE_STORE:
        	/* DEBUG INFO */
            coLssSwitchGlobal(CO_LSS_STATE_WAITING);
            break;

        default:
        	/* ERROR */
            break;
    }
}


/**
 * @brief	This function is called if a new NMT state of a node,
 * 			is recordnized by the CANopen stack.
 *
 * @param	nodeID_u8	Node id of the node that has registered a change.
 * 			state_en	Error control state
 * 			nmtState_en	NMT state of the particular node.
 *
 * @return	void
 *
 */
static void SdlLssHbMonitorCallback(uint8 nodeID_u8, CO_ERRCTRL_T state_en, CO_NMT_STATE_T nmtState_en)
{
	uint8 arrIndex_u8;

	/* look if node is monitored */
	arrIndex_u8 = SdlLssGetNodeIndex(nodeID_u8);

	/* handle monitored node */
	if (arrIndex_u8 != NMS_UINT16_MAX)
	{
		/* save states */
		nodeNMTState_gaen[arrIndex_u8] = nmtState_en;

		/* indicate if monitored node lost heartbeat */
		if (nmtState_en == CO_NMT_STATE_UNKNOWN)
		{
			/* To be transmitted via CAN */
			/* ERROR */
		}

		/* indicate if monitored node sent a bootup message */
		if (state_en == CO_ERRCTRL_BOOTUP)
		{
			/* INFO */
		}

	/* handle unmonitored node */
	}
	else
	{
		/* ERROR */
	}
}


/**
* @brief	This function tries to reset nodes with unknown NMT state.
*
* @param	pData_pv	Data.
*/
static void SdlLssResetNodesCallback(void *pData_pv)
{
	uint8 arrIndex_u8;

	(void)pData_pv;

	/* reset defined nodes without known state */
	for (arrIndex_u8 = 0u; arrIndex_u8 < SDL_LSS_NODE_COUNT; arrIndex_u8++)
	{
		if ((nodeNMTState_gaen[arrIndex_u8] != CO_NMT_STATE_PREOP) &&
			(nodeNMTState_gaen[arrIndex_u8] != CO_NMT_STATE_OPERATIONAL))
		{
			/* reset node */
			coNmtStateReq(nodeLookupTable_gast[arrIndex_u8].nodeId_u8, CO_NMT_STATE_RESET_NODE, CO_FALSE);
		}
	}
}

