//----------------------------------------------------
/**
	\file		ctk/inc/modbusL2.h
	\ingroup	serial
	\ingroup	all

	\brief		Implementace protokolu SerialModbus (v1.02)
				<<< ALL >>>
	
	\author		ConTeK s.r.o., Bc. Jakub Kastner
	\version	01.02
	\date		28.12.2015
*/
//----------------------------------------------------

#include <modbusL2sl.h>
#include <hwboard.h>

#ifndef MSVC
 #include <usart.h>
#else
 #include <vuart.h>
#endif

//----------------------------------------------------
//	\name	Konstanty 
//@{
#define MAX_DATA_LENHT	(252)	//!< ukoncovaci znak bloku dat
#define BROADCAST_MSG
//@}

MODBUS_BUFF REC, REC1, REC2, REC3, SND;

//----------------------------------------------------
/**
	\brief	Funkce pro vypocet CRC

	\param	_msg		Zprava ze ktere se ma CRC pocitat
	\return	vypocitane CRC
*/
//----------------------------------------------------
uint16_t CalcCRC ( MODBUS_MSG * const _msg )
{
	uint16_t CRC = 0xFFFF;
	uint16_t POLY = 0xA001;
	uint8_t *actChar = (uint8_t*)_msg;
	uint8_t	charLeft = _msg->DataSize + 2;
	uint8_t tmp;

	while(charLeft > 0)
	{
		tmp = *actChar;

		CRC = CRC ^ tmp;
		
		for(uint8_t n = 0; n <= 7; n++)
		{
			if((CRC & 0x01) == 0x01)
				CRC = (CRC>>1)^POLY;
			else
				CRC = (CRC>>1);
		}
		
		actChar++;
		charLeft--;
	}

	return CRC;
}


//----------------------------------------------------
/**
	\brief	Funkce pro odeslani zpravy 

	\param	_ctx	Kontext komunikace
			_msg	Ukazatel na zpravu
*/
//----------------------------------------------------
static uint8_t cbMBSendMSG ( MODBUS_CTX * const _ctx,  MODBUS_MSG * _msg)
{
	uint8_t ret = 0;
	uint16_t CRC;

	if( SND.Desc.Status == STS_READY )
	{
		_msg->Addr = _ctx->Address;
		CRC = CalcCRC( _msg );
		memcpy(&SND.Msg,_msg,_msg->DataSize + 2);
		SND.Msg[(_msg->DataSize + 2)] = CRC;
		SND.Msg[(_msg->DataSize + 3)] = CRC>>8;
		SND.Desc.UsedSize = _msg->DataSize + 4;

		ret = 1;
	}

	return ret;
}

//----------------------------------------------------
/**
	\brief	calback pro zpracovani zpravy  

*/
//----------------------------------------------------

static bool_t cbMBProcesMSG( MODBUS_CTX * const _ctx, MODBUS_MSG * const _msg )
{
	MODBUS_MSG reply;

	reply.Function = _msg->Function + 0x80;
	reply.Data[0] = 0x01;
	reply.DataSize = 1;

	_ctx->SendMsg( _ctx, &reply);

	return TRUE;
}

//----------------------------------------------------
/**
	\brief	Callback volany po prijmu zpravy.

	\param	_cmm	Ukazatel na nizsi komunikacni knihovnu

*/
//----------------------------------------------------

void MODBUSRecvMsg( COMMDESC * const _cmm )
{
	MODBUS_BUFF * rec;
	MODBUS_CTX * ctx = (MODBUS_CTX *) _cmm->CmmL1;
	RSTSTYPE status;

	flock_t lock = FastLock();

		rec = (MODBUS_BUFF *) _cmm->Recv( _cmm, &status );

	FastUnlock( lock );

	if(status == RSTS_OK)
	{
		uint8_t datacnt = rec->Desc.UsedSize;
		if( datacnt >= 4)
		{
			//nakopirovat do noveho bufferu a vystavit semafor, aby mbtick sel do dalsiho stavu
			memcpy( &ctx->REC_MSG, rec->Msg, ( datacnt - 2 ));
			memcpy( &ctx->REC_MSG.CRC, rec->Msg + ( datacnt - 2 ), 2);

			ctx->REC_MSG.DataSize = ( datacnt - 4 );
			ctx->Cmd = ST_do_checking;

			SetLedEx(1, TRUE);
		}
	}
	else
	{
		//Do nothing parity error
	}

	// smazame a navratime prijimaci buffer do fronty
	memset(rec, 0, sizeof(MODBUS_BUFF));
	_cmm->RecvBuffPrepare( _cmm, &rec->Desc, MODBUS_MAX_MSG_LENGHT );

	ctx->CPT1 ++;
	ctx->endOfRecieveTime = tmTicks();
}

void Msgsended (const COMMDESC * const _cmm, const BUFDESC * const _desc)
{
	MODBUS_CTX * ctx = (MODBUS_CTX *) _cmm->CmmL1;
	ctx->Cmd = ST_do_idle;
}

//----------------------------------------------------
/**
	\brief	Funkce ticku komunikacniho automatu 

	\param	_ctx		Kontextu automatu komunikce
*/
//----------------------------------------------------


static void cbMBTick( MODBUS_CTX * const _ctx )
{
	//tick predchudcem
	_ctx->Cmm->Tick( _ctx->Cmm );

	//! Stavovy automat starajici se o komunikaci podle normy ISO 14230
	STM_BEGIN( _ctx->Stm );
			//---------------------------------------------------
			//! Ukonceny automat
			STM_FINISHED_STATE
			{
			}

			//---------------------------------------------------
			//! Restartovani automatu
			STM_RESTART_STATE
			{
				stGoto(ST_MODBUS_IDLE);
			}

			//---------------------------------------------------
			//! No Pending request
			STM_STATE( ST_MODBUS_IDLE )
			{
				if(_ctx->Cmd == ST_do_checking) 
				{//prijmut paket no frame error
					stGoto(ST_MODBUS_CHECKING_REQUEST);
				}
			}
			//---------------------------------------------------
			//! checking request
			STM_STATE( ST_MODBUS_CHECKING_REQUEST )
			{
				uint16_t calcCRC = CalcCRC( &_ctx->REC_MSG );

				if((calcCRC == _ctx->REC_MSG.CRC)&&
					((_ctx->REC_MSG.Addr == _ctx->Address)||
					 (_ctx->REC_MSG.Addr == MODBUS_BROADCAST_ADDRESS)))
				{
					// zprava je pro nas a je v poradku
					stGoto(ST_MODBUS_PROCESSING_REQUIED_ACTION);
				}
				else
				{
					// eror in frame or not my addres
					SetLedEx( 1, FALSE );
					_ctx->Cmd = ST_do_idle;
					stGoto(ST_MODBUS_IDLE);
				}
			}
			//---------------------------------------------------
			//! PROCESSING_REQUIED_ACTION
			STM_STATE( ST_MODBUS_PROCESSING_REQUIED_ACTION )
			{
				if( _ctx->ProcesMsg( _ctx, &_ctx->REC_MSG ) )
				{//REPLY NEED
					stGoto( ST_MODBUS_REPLY );
				}
				else
				{// broadcastmode
					SetLedEx( 1, FALSE );
					_ctx->Cmd = ST_do_idle;
					stGoto( ST_MODBUS_IDLE );
				}
			}
			//---------------------------------------------------
			//! PROCESSING_REQUIED_ACTION
			STM_STATE( ST_MODBUS_REPLY );
			{
				if( stFirstPass )
					_ctx->Send( _ctx->Cmm, &SND.Desc, SND.Desc.UsedSize);
				SetLedEx( 1, FALSE );
				
				if( _ctx->Cmd == ST_do_idle )
					stGoto( ST_MODBUS_IDLE );				
			}

		STM_DEFAULT_END;
}


//----------------------------------------------------
/**
	\brief	Funkce pro inicializaci komunikacniho automatu 

	\param	_ctx		Struktura kontextu automatu komunikace
	\param	_cmm		Adresa knihovny fyzicke vrstvy
*/                                                                 
//----------------------------------------------------

bool_t MBInitialize( MODBUS_CTX * const _ctx, COMMDESC * const _cmm )
{
	//Vynulovani Kontextu
	memset(_ctx, 0, sizeof(MODBUS_CTX) );

	flock_t lock = FastLock();

	//donastaveni nizsi vrstvy
	_ctx->Cmm = _cmm;

	// Zapnuti komunikacniho automatu
	_cmm->flRunning = TRUE;

	// callback volany po prijenti zpravy (bytu)
	_ctx->Cmm->cbReceived = MODBUSRecvMsg; 

	// inicializace automatu
	stmInitialize(&_ctx->Stm);
	stmReqRestart(&_ctx->Stm);

	// Vlozime prijimaci buffery do fronty
	_cmm->RecvBuffPrepare( _cmm, &REC.Desc, MODBUS_MAX_MSG_LENGHT );
	_cmm->RecvBuffPrepare( _cmm, &REC1.Desc, MODBUS_MAX_MSG_LENGHT );
//	_cmm->RecvBuffPrepare( _cmm, &REC2.Desc, MODBUS_MAX_MSG_LENGHT );
//	_cmm->RecvBuffPrepare( _cmm, &REC3.Desc, MODBUS_MAX_MSG_LENGHT );

	_cmm->CmmL1 = _ctx;
	
	_ctx->Send = _cmm->Send;

	_ctx->SendMsg = &cbMBSendMSG;
	_ctx->ProcesMsg = &cbMBProcesMSG;
	_ctx->Tick = &cbMBTick;

	_cmm->cbSended = &Msgsended;

	_ctx->Address = 2;

	FastUnlock(lock);

	return TRUE;
}


//----------------------------------------------------
/**
	\brief	Funkce pro ukonceni komunikacniho automatu 

	\param	_ctx		Struktura kontextu automatu komunikace
*/
//----------------------------------------------------

void MBFinalize( MODBUS_CTX * const _ctx )
{
	COMMDESC * cmm = _ctx->Cmm;

	if (cmm != NULL)
	{
		cmm->flRunning = FALSE; // zastaveni komunikace
	}
}
