/**
 * @file        bmapi_lin_master.c
 * @brief       BMAPI example: Transmit test LIN message and then wait until a new message is received.
 * @author      busmust
 * @version     1.0.0.1
 * @copyright   Copyright 2020 by Busmust Tech Co.,Ltd <br>
 *              All rights reserved. Property of Busmust Tech Co.,Ltd.<br>
 *              Restricted rights to use, duplicate or disclose of this code are granted through contract.
 */
#ifdef __GNUC__
#include <unistd.h>
#elif defined(_MSC_VER)
#include <Windows.h>
#define usleep(us) Sleep((us)/1000)
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "bmapi.h"

#define TEST_USE_READEX 1
#define TEST_MSG_TX_ID  0x30
#define TEST_MSG_RX_ID  0x31
#define TEST_MSG_DLC    0x8
#define TEST_MSG_LENGTH 0x8
#define TEST_MSG_COUNT  10000
#define TEST_MSG_ROUND  10000
#define TEST_MSG_CYCLE  10
#define TEST_MSG_TX_TIMEOUT 30000
#define TEST_MSG_RX_TIMEOUT 30000

static int lin_transmit(BM_ChannelHandle channel, BM_NotificationHandle notification, uint32_t nroundmessages, uint32_t nrounds, uint32_t cycle, bool master);
static int lin_receive(BM_ChannelHandle channel, BM_NotificationHandle notification, uint32_t nroundmessages, uint32_t nrounds, uint32_t cycle, bool master);


/**
 * @brief BMAPI test program: Transmit or receive messages according to command line arguments:
 * @param[in]  argc    Number of command line arguments
 * @param[in]  argv[0] Path of this program
 * @param[in]  argv[1] Channel: Selected port index
 * @param[in]  argv[2] Mode: only "tx" or "rx" is supported
 * @param[in]  argv[3] nmessages: Number of messages to receive/transmit within a round
 * @param[in]  argv[4] nrounds: Number of rounds to transmit
 * @param[in]  argv[4] ncycles: Round cycle, a delay value in milliseconds between consecutive rounds
 * @return     Program exit code
 */
int main(int argc, char* argv[])
{
	BM_ChannelHandle channel = NULL;
	BM_NotificationHandle notification = NULL;
	BM_ChannelInfoTypeDef channelinfos[32];
	int nchannels = sizeof(channelinfos) / sizeof(channelinfos[0]);
	int channelid = argc > 1 ? atoi(argv[1]) : 0;
	const char* mode = argc > 2 ? argv[2] : "Tx";
	int exitcode = 0;
	BM_BitrateTypeDef bitrate;
	BM_StatusTypeDef error = BM_ERROR_OK;
	int i;

	printf(
		"\n"
		"========== BMAPI TEST PROGRAM ==========\n"
		"========= BUSMUST TECH Co.,Ltd. ========\n"
	);

	/* Step1: Initialize BMAPI library before any other operation */
	printf("Initializing BMAPI library ...\n");
	error = BM_Init();
	if (error != BM_ERROR_OK)
	{
		exitcode = 1;
		goto __exit;
	}

	/* Step2: Enumerate connected device channels */
	printf("Enumerating LIN channels ...\n");
	error = BM_EnumerateByCap(channelinfos, &nchannels, BM_LIN_CAP);
	if (error != BM_ERROR_OK)
	{
		exitcode = 2;
		goto __exit;
	}

	for (i = 0; i < nchannels; i++)
	{
		printf("[%d] %s\n", i, channelinfos[i].name);
	}
	if (channelid < nchannels)
	{
		/* Step3: Open the selected channel and configure baudrate */
		uint32_t modeId = strstr(mode, "-master") != NULL ? BM_LIN_MASTER_MODE : BM_LIN_SLAVE_MODE;
		printf("Opening channel %s in %s mode ...\n", channelinfos[channelid].name, mode);
		memset(&bitrate, 0, sizeof(bitrate));
		/* default LIN baudrate is 19200bps, feel free to change. */
		bitrate.nbitrate = 19200; 
		error = BM_OpenEx(&channel, &channelinfos[channelid], modeId, BM_TRESISTOR_120, &bitrate, NULL, 0);
		if (error != BM_ERROR_OK)
		{
			exitcode = 3;
			goto __exit;
		}

		BM_LinProtocolConfigTypeDef protocol;
		memset(&protocol, 0, sizeof(protocol));
		protocol.version = BM_LIN_VER_2_1; /* LIN V2.1 */
		protocol.checksum = BM_LIN_ENHANCED_CHECKSUM;
		BM_SetLinProtocol(channel, &protocol);
		BM_SetTerminalRegister(channel, BM_TRESISTOR_PULLUP_1K);

		printf("Getting channel notification handle ...\n");
		error = BM_GetNotification(channel, &notification);
		if (error != BM_ERROR_OK)
		{
			exitcode = 4;
			goto __exit;
		}

		BM_ClearBuffer(channel);
		Sleep(100);

		/* Step4: TX (according to command line arguments) */
		uint32_t nmessages = argc > 3 ? atoi(argv[3]) : TEST_MSG_COUNT;
		uint32_t nrounds = argc > 4 ? atoi(argv[4]) : TEST_MSG_ROUND;
		uint32_t ncyclems = argc > 5 ? atoi(argv[5]) : TEST_MSG_CYCLE;
		if (strcmp(mode, "tx-master") == 0)
		{
            lin_transmit(channel, notification, nmessages, nrounds, ncyclems, true);
		}
		else if (strcmp(mode, "rx-master") == 0)
		{
            lin_receive(channel, notification, nmessages, nrounds, ncyclems, true);
		}
        else if (strcmp(mode, "tx-slave") == 0)
        {
            lin_transmit(channel, notification, nmessages, nrounds, ncyclems, false);
        }
        else if (strcmp(mode, "rx-slave") == 0)
        {
            lin_receive(channel, notification, nmessages, nrounds, ncyclems, false);
        }
		else
		{
			printf("ERROR: %s mode is not supported. LIN demo only support 'tx-master','rx-master','tx-slave','rx-slave' mode.\n", mode);
		}

		/* Step5: Close the opened channel and release connected hardware */
		printf("Closing channel ...\n");
		error = BM_Close(channel);
		if (error != BM_ERROR_OK)
		{
			exitcode = 5;
			goto __exit;
		}
	}
	else
	{
		printf("Input Error: There are %d available Busmust device, port index %d is invalid.\n", nchannels, channelid);
		exitcode = 6;
	}

__exit:
	if (error != BM_ERROR_OK)
	{
		char buffer[256] = { 0 };
		BM_GetErrorText(error, buffer, sizeof(buffer), 0);
		printf("Error 0X%08X: %s.\n", error, buffer);
	}
	/* Step6: Cleanup library resource */
	BM_UnInit();
	printf("Exit %s app.\n", mode);
	return exitcode;
}

static int lin_transmit(BM_ChannelHandle channel, BM_NotificationHandle notification, uint32_t nroundmessages, uint32_t nrounds, uint32_t cycle, bool master)
{
	uint32_t n = 0;
	uint32_t nbadmsgs = 0;
	uint32_t nmessages = ((uint32_t)nroundmessages) * ((uint32_t)nrounds);
	BM_LinMessageTypeDef msg = { 0 };
	BM_StatusTypeDef error = BM_ERROR_OK;

	/* Compose a LIN message object for TX */
	printf("Transmitting test message as %s ...\n", master ? "master" : "slave");
	time_t t1 = time(NULL);


	/**
	 * @brief Simple synchronous transmission:
	 *        Each message is transmitted to the USB device and the Write function get blocked until the message's ACK is received
	 *        You might want to set a timeout value (in milliseconds) to handle bus errors (i.e. BUSOFF)
	 */
	for (n = 0; n < nmessages; n++)
	{
		uint32_t timestamp = 0;
        msg.id = TEST_MSG_TX_ID;
        msg.ctrl.lin.DLC = TEST_MSG_LENGTH;
        msg.ctrl.lin.TRANSMIT = 1;
        msg.ctrl.lin.ENHANCED_CHECKSUM = 0; /* Let the hardware generate enhanced checksum automatically */
        *(uint32_t*)&msg.payload[0] = n;
        error = BM_WriteLinMessage(channel, &msg, 0, TEST_MSG_TX_TIMEOUT, &timestamp);
        if (error == BM_ERROR_BUSTIMEOUT)
        {
            nbadmsgs++;
            printf("Transmit timeout, offset=%d.\n", n);
            break;
        }
        else if (BM_ERROR_OK != error)
        {
            nbadmsgs++;
            printf("Failed to transmit message, error=0X%08X.\n", error);
            break;
        }
        else
        {
            printf(
                "LIN TX [%d/%d] @%u: id=%02X, payload=[%02X, %02x, %02X, %02x, %02X, %02x, %02X, %02x]\n", n, nmessages, timestamp,
				msg.id,
				msg.payload[0], msg.payload[1], msg.payload[2], msg.payload[3],
				msg.payload[4], msg.payload[5], msg.payload[6], msg.payload[7]
            );
        }
	}

	time_t t2 = time(NULL);
	uint32_t td = (uint32_t)(t2 - t1);
	float fps = ((float)n) / ((float)td);

	printf(
		"\n"
		"================ TX STAT ================\n"
		"Transmitted messages: %u\n"
		"Bad messages: %u\n"
		"Total time: %u seconds\n"
		"Frame rate: %.3f fps\n"
		"=========================================\n",
		n, nbadmsgs, td, fps
	);

	return n;
}

static int lin_receive(BM_ChannelHandle channel, BM_NotificationHandle notification, uint32_t nroundmessages, uint32_t nrounds, uint32_t cycle, bool master)
{
	uint32_t n = 0;
	uint32_t nbadmsgs = 0;
	uint32_t nmessages = ((uint32_t)nroundmessages) * ((uint32_t)nrounds);
	BM_StatusTypeDef error = BM_ERROR_OK;

	/* Compose a LIN message object for TX */
	printf("Receiving test message as %s ...\n", master ? "master" : "slave");
	time_t t1 = time(NULL);


	/**
	 * @brief Simple synchronous transmission:
	 *        Each message is transmitted to the USB device and the Write function get blocked until the message's ACK is received
	 *        You might want to set a timeout value (in milliseconds) to handle bus errors (i.e. BUSOFF)
	 */
	uint32_t previousTimestamp = 0;
	for (n = 0; n < nmessages; n++)
	{
		uint32_t timestamp = 0;
		
#if TEST_USE_READEX
		// Master and slave can both call BM_ReadLinMessageEx to read LIN messages, BMAPI will take care of everything.
		{
			BM_LinMessageTypeDef msg = { 0 };
			msg.id = TEST_MSG_RX_ID;
			int payloadLength = TEST_MSG_LENGTH;
			msg.ctrl.lin.DLC = payloadLength;
			msg.ctrl.lin.ENHANCED_CHECKSUM = 1; /* Use enhanced checksum */
			msg.ctrl.lin.TRANSMIT = 0; /* Direction is RX (this is a reading request) */
			if ((error = BM_ReadLinMessageEx(channel, &msg, NULL, TEST_MSG_RX_TIMEOUT, &timestamp)) == BM_ERROR_OK)
			{
                printf(
                    "LIN RX [%d/%d]: id=%02X, checksum=%02x[%s], payload=[%02X, %02x, %02X, %02x, %02X, %02x, %02X, %02x], errors=%08x.\n", n, nmessages, msg.id,
                    msg.ctrl.lin.CHECKSUM,
					msg.ctrl.lin.ENHANCED_CHECKSUM ? "Enhanced" : "Classic",
					msg.payload[0], msg.payload[1], msg.payload[2], msg.payload[3],
					msg.payload[4], msg.payload[5], msg.payload[6], msg.payload[7],
					msg.ctrl.lin.ERRORS
                );
			}
			else
			{
				nbadmsgs++;
				printf("Failed to receive message, error=0X%08X.\n", error);
				break;
			}
		}
#else
        BM_LinMessageTypeDef request;
        BM_LinMessageTypeDef response;
		if (master)
		{
            /* Transmit the reading request to slave device first, otherwise LIN slave will not send any LIN message to bus */
            memset(&request, 0, sizeof(request));
            request.id = TEST_MSG_RX_ID;
            int payloadLength = TEST_MSG_LENGTH;
            request.ctrl.lin.DLC = payloadLength;
            request.ctrl.lin.ENHANCED_CHECKSUM = 1; /* Use enhanced checksum */
            request.ctrl.lin.TRANSMIT = 0; /* Direction is RX (this is a reading request) */
            error = BM_WriteLinMessage(channel, &request, 0, TEST_MSG_TX_TIMEOUT, &timestamp);
            if (error == BM_ERROR_BUSTIMEOUT)
            {
                nbadmsgs++;
                printf("Transmit timeout, offset=%d.\n", n);
                break;
            }
            else if (BM_ERROR_OK != error)
            {
                nbadmsgs++;
                printf("Failed to transmit message, error=0X%08X.\n", error);
                break;
            }
		}

		/* Wait for response notification */
		if (BM_WaitForNotifications(&notification, 1, TEST_MSG_RX_TIMEOUT) < 0)
		{
            printf("Receive timeout, offset=%d.\n", n);
			break;
		}

        /* Receive the reading response from slave device */
        while (error = BM_ReadLinMessage(channel, &response, NULL, &timestamp) == BM_ERROR_OK)
        {
            if (response.ctrl.lin.ERRORS)
            {
                printf("Lin protocol error detected, code=0X%08X.\n", response.ctrl.lin.ERRORS);
            }
            else
            {
                printf(
					"LIN RX [%d/%d]: id=%02X, payload=[%02X, %02x, %02X, %02x, %02X, %02x, %02X, %02x]\n", n, nmessages, response.id,
					response.payload[0], response.payload[1], response.payload[2], response.payload[3],
					response.payload[4], response.payload[5], response.payload[6], response.payload[7]
					);
            }
            break;
        }
#endif
	}

	time_t t2 = time(NULL);
	uint32_t td = (uint32_t)(t2 - t1);
	float fps = ((float)n) / ((float)td);

	printf(
		"\n"
		"================ RX STAT ================\n"
		"Received messages: %u\n"
		"Bad/lost messages: %u\n"
		"Total time: %u seconds\n"
		"Frame rate: %.3f fps\n"
		"=========================================\n",
		n, nbadmsgs, td, fps
	);

	return n;
}
/**
 * End of file
 */

