//----------------------------------------------------
/**
	\file		ctk/inc/canopen.h
	\ingroup	can
	\ingroup	all

	\brief		Implementace casti CANOpen normy
				<<< ALL >>>
	
	\author		ConTeK s.r.o., Pavel Bazika
	\version	01.01
	\date		05.09.2007
*/
//----------------------------------------------------

#ifndef _CANOPEN_H_
#define _CANOPEN_H_

#include <cancomm.h>
#include <libsets.h>
#include <stmachine.h>

// todo libsets
#define CANOPEN_MAX_SDO_LENGTH	127

//----------------------------------------------------
//!	Typ CANOpen zpravy

typedef enum _COMSG_TYPE
{
	COMSG_NO = 0,
	COMSG_PDO,
	COMSG_SDO
} COMSG_TYPE;


//----------------------------------------------------
//!	Stavy CANOpen zarizeni

typedef enum _CANOPEN_STATE
{
	COST_INIT,
	COST_PREOP,
	COST_OPER,
	COST_STOP,
	COST_ERROR
} CANOPEN_STATE;

//----------------------------------------------------
//!	Typy casti zpravy PDO

typedef uint8_t		pdo_data_t[8];

//----------------------------------------------------
//!	Struktura PDO zpravy

typedef struct _CANOPENMSG_PDO
{
	pdo_data_t		Data;						//!< Zprava
} CANOPENMSG_PDO;


//----------------------------------------------------
//!	Typy casti zpravy SDO
//@{
typedef uint8_t		sdo_cmd_t;
typedef uint16_t	sdo_index_t;
typedef uint8_t		sdo_subindex_t;
typedef uint8_t		sdo_data_t[4];
//@}

//----------------------------------------------------
//!	Priznaky typu protokolu SDO
//@{
#define SDO_CMD_FLAG_EXPEDITED		0x02
#define SDO_CMD_FLAG_SIZE			0x01
#define SDO_CMD_FLAG_TOGGLE			0x10
#define SDO_CMD_FLAG_LAST_SEGMENT	0x01
//@}

//----------------------------------------------------
//!	Konstanty client command specifier - cast command SDO
//@{
#define SDO_CMD_CCS_WRITE_REQ			0x20
#define SDO_CMD_CCS_WRITE_SEGMENT_REQ	0x00
#define SDO_CMD_CCS_READ_REQ			0x40
#define SDO_CMD_CCS_READ_SEGMENT_REQ	0x60

#define SDO_CMD_CCS_MASK				0xE0
//@}

//----------------------------------------------------
//!	Konstanty server command specifier - cast command SDO
//@{
#define SDO_CMD_SCS_WRITE_RSP			0x60
#define SDO_CMD_SCS_WRITE_SEGMENT_RSP	0x20
#define SDO_CMD_SCS_READ_RSP			0x40
#define SDO_CMD_SCS_READ_SEGMENT_RSP	0x00
#define SDO_CMD_SCS_ERROR_RSP			0x80

#define SDO_CMD_SCS_MASK				0xE0
//@}

//----------------------------------------------------
//!	Konstanty delky SDO dat - cast command SDO (bez bitu, ze je velikost indikovana)
//@{
#define SDO_CMD_DATALENGTH_32		0x00
#define SDO_CMD_DATALENGTH_24		0x04
#define SDO_CMD_DATALENGTH_16		0x08
#define SDO_CMD_DATALENGTH_8		0x0C

#define SDO_CMD_DATALENGTH_MASK		0x0C
#define SDO_CMD_DATALENGTH_SHIFT	2
//@}

//----------------------------------------------------
//!	Konstanty delky SDO dat v segmentovem prenosu - cast command SDO
//@{
#define SDO_CMD_SEG_DATALENGTH_MASK		0x0E
#define SDO_CMD_SEG_DATALENGTH_SHIFT	1
//@}

//----------------------------------------------------
//!	Struktura SDO zpravy

typedef struct _CANOPENMSG_SDO
{
	sdo_cmd_t		Command;					//!< Prikaz
	sdo_index_t		Index;						//!< Index
	sdo_subindex_t	SubIndex;					//!< Subindex
	sdo_data_t		Data;						//!< Zprava
} CANOPENMSG_SDO;


//----------------------------------------------------
//!	Typy casti zpravy NMT

typedef uint8_t		nmt_cmd_t;
typedef uint8_t		nmt_nodeid_t;


//----------------------------------------------------
//!	Struktura NMT zpravy

typedef struct _CANOPENMSG_NMT
{
	nmt_cmd_t		Command;					//!< Prikaz
	nmt_nodeid_t	NodeID;						//!< Identifikator uzlu
} CANOPENMSG_NMT;


//----------------------------------------------------
//!	Prikazy CANOpen NMT (network management)
//@{
static const nmt_cmd_t CONMT_START_REMOTE_NODE = 0x01;
static const nmt_cmd_t CONMT_STOP_REMOTE_NODE = 0x02;
static const nmt_cmd_t CONMT_ENTER_PREOP_STATE = 0x80;
static const nmt_cmd_t CONMT_RESET_NODE = 0x81;
static const nmt_cmd_t CONMT_RESET_COMM = 0x82;
//@}

//----------------------------------------------------
//!	Identifikator vsech zarizeni

static const nmt_nodeid_t CONMT_NODEID_ALL = 0x00;


//----------------------------------------------------
//!	Forward deklarace kontextu

struct _CANOPEN_CTX;


//----------------------------------------------------
//@{
//!	\name	Typy metod CANOpen objektu

typedef COMSG_TYPE (*comRecv_t) (struct _CANOPEN_CTX * _ctx, const CAN_BUFFER * _msg);

typedef void (*comTick_t) (struct _CANOPEN_CTX * _ctx);

typedef void (*comChangeSlaveState_t) (const struct _CANOPEN_CTX * _ctx, nmt_cmd_t _command);
//@}

//----------------------------------------------------
//!	Typ periodicke zpravy

typedef enum _CANOPEN_PERIOD_MSG_TYPE
{
	COPM_TYPE_CALLBACK,
	COPM_TYPE_POINTER,
	COPM_TYPE_CANMSG
} CANOPEN_PERIOD_MSG_TYPE;


//----------------------------------------------------
/**
	\brief	Callback plnici buffer periodicky odesilane zpravy

	\param	ctx		Kontext
	\param	Buffer	Buffer k naplneni

	\return	Pokud FALSE, nebude se nic odesilat
*/
//-----------------------------------------------------

typedef bool_t (*comPeriodCallback_t) (const struct _CANOPEN_CTX * _ctx, const CANOPENMSG_PDO * _message);


//----------------------------------------------------
/**
	\brief	Struktura popisujici polozku v seznamu periodicky odesilanych zprav

	\note	Struktura obsahuje vlastni buffer pro zpravu, ten bud primo odesila, 
			nebo se do ni kopiruje z adresy dane ukazatelem Data, nebo se vola callback a tim se to plni
*/
//----------------------------------------------------
 
typedef struct _CANOPEN_PERIOD_MSG
{
	struct _CANOPEN_PERIOD_MSG	*Next;		//!< Nasledujici periodicka zprava v seznamu
	uint8_t 		Type;					//!< Typ periodicke zpravy
	ticks_t			Interval;				//!< Interval, jak casto se zprava odesila
	ticks_t			Timer;					//!< Casovac odmerujici interval
	CAN_BUFFER		Buf;					//!< Buffer pro zpravu
	uint8_t			DataSize;
	union
	{
		const void	*Data;					//!< Pointer na data v pripade typu COPM_TYPE_POINTER
		comPeriodCallback_t	Callback;		//!< Pointer na callback v pripade typu COPM_TYPE_CALLBACK
	} Data;
	
} CANOPEN_PERIOD_MSG;

// prikazy SDO
typedef enum _CANOPEN_SDO_CMD
{
	COSDOCMD_NONE = 0,
	COSDOCMD_READ,
	COSDOCMD_WRITE
} CANOPEN_SDO_CMD;

// status vysledku operace SDO komunikace
typedef enum _CANOPEN_SDO_ERROR
{
	SDO_ERR_NONE = 0,		//!< Vse OK
	SDO_ERR_OVERFLOW,		//!< Prijimana zprava prekrocila maximalni velikost
	SDO_ERR_UNEXP_EOM,		//!< Neocekavany konec segmentovane zpravy - prijato mene dat, nez bylo deklarovano
	SDO_ERR_SIZE_EXCEEDED,	//!< Predem deklarovana delka dat byla prekrocena
	SDO_ERR_SIZE_DIFFER,	//!< Velikost serverem navracenych dat je jina nez ocekavana pri pozadavku
	SDO_ERR_TOGGLE_ABORT,	//!< Vysilani zpravy bylo serverem zruseno, nespravny toggle bit
	SDO_ERR_OUT_OF_RETRY,	//!< Pocet pokusu k zotaveni vyprsel - tzn. no response, nebo neustaly abort
	SDO_ERR_ABORTED			//!< Server zaslal abort zpravu, podrobnosti v error class a code parametrech callbacku
} CANOPEN_SDO_ERROR;

// callbacky komunikace SDO

typedef void (*comiSDOReadDone_t) (
		const struct _CANOPEN_CTX * _ctx, 
		CANIDTYPE _id, 
		sdo_index_t _index, 
		sdo_subindex_t _subIndex, 
		const uint8_t * _data, 
		uint8_t _size, 
		void * _userData);

typedef void (*comiSDOWriteDone_t) (
		const struct _CANOPEN_CTX * _ctx, 
		CANIDTYPE _id, 
		sdo_index_t _index, 
		sdo_subindex_t _subIndex, 
		void * _userData);

typedef void (*comiSDOError_t) (
		const struct _CANOPEN_CTX * _ctx,
		CANIDTYPE _id,
		sdo_index_t _index,
		sdo_subindex_t _subIndex,
		CANOPEN_SDO_ERROR _ctkErrCode,
		uint8_t _errorClass,
		uint8_t _errorCode,
		uint16_t _additionalCode,
		const uint8_t * _data,
		uint8_t _size,
		void * _userData);

// kontext komunikace SDO

typedef struct _CANOPEN_SDO_CTX
{
	STMACHINE	Stm;
	CAN_BUFFER	SendBuf;
	uint8_t		DataPtr;
	uint8_t		ExpectedSize;
	bool_t		ToggleBit;
	bool_t		NoResetCounter;

	ticks_t		RetryTimeout;	//!< Timeout mezi odeslanim pozadavku, paklize neprisla odpoved (0 = bez timeoutu)
	ticks_t		RetryTimer;		//!< Casovac pro odmerovani timeoutu
	uint8_t		RetryCount;		//!< Pocet opakovani pozadavku
	uint8_t		RetryCounter;	//!< Citac opakovani pozadavku

	// callbacky
	comiSDOReadDone_t	cbRead;
	comiSDOWriteDone_t	cbWrite;
	comiSDOError_t		cbError;
	void				*UserData;

	struct
	{
		CANOPEN_SDO_CMD	Cmd;
		CANIDTYPE		RqID;
		CANIDTYPE		RspID;
		sdo_index_t		Index;
		sdo_subindex_t	SubIndex;
		uint8_t			Data[CANOPEN_MAX_SDO_LENGTH];
		uint8_t			Size;
	} Cmd;
} CANOPEN_SDO_CTX;

//----------------------------------------------------
//!	Kontext CANOpen

typedef struct _CANOPEN_CTX
{
	CANDEVICE		Parent;						//!< Struktura CAN zarizeni (predek)

	CANOPEN_STATE	State;						//!< Stav CANOpen
	nmt_nodeid_t	NodeID;						//!< ID tohoto zarizeni v ramci CANOpen
	
	CANCOMMDESC 	*Cmm;						//!< Deskriptor pridruzene komunikace
	CAN_BUFFER		SendBuf;					//!< Odesilaci buffer tohoto zarizeni
	
	// metody
	comChangeSlaveState_t	ChangeSlaveState;	//!< Metoda zmeny stavu CANOpen zarizeni, je-li ve slave rezimu
	
	// callbacky
	comRecv_t				cbRecv;				//!< Callback volany pri prijmu zpravy

	CANOPEN_SDO_CTX			SDO;				//!< Kontext pro odesilani SDO
	CANOPEN_PERIOD_MSG		*PerMsgList;		//!< Seznam periodicky odesilanych zprav
	
	volatile bool_t			flRunning;			//!< Priznak, ze je knihovna inicializovana

} CANOPEN_CTX;


//----------------------------------------------------
/**
	\brief	Konverze CAN zpravy do PDO struktury

	\param	_dest	Cilovy buffer - CANOpen
	\param	_src	Zdrojovy buffer - CAN
*/
//-----------------------------------------------------

void CANOpenGetPDOMessage(CANOPENMSG_PDO * const _dest, const CAN_BUFFER * const _src);


//----------------------------------------------------
/**
	\brief	Konverze CAN zpravy do SDO struktury

	\param	_dest	Cilovy buffer - CANOpen
	\param	_src	Zdrojovy buffer - CAN
*/
//-----------------------------------------------------

void CANOpenGetSDOMessage(CANOPENMSG_SDO * const _dest, const CAN_BUFFER * const _src);


//----------------------------------------------------
/**
	\brief	Konverze CAN zpravy do NMT struktury

	\param	_dest	Cilovy buffer - CANOpen
	\param	_src	Zdrojovy buffer - CAN
*/
//-----------------------------------------------------

void CANOpenGetNMTMessage(CANOPENMSG_NMT * const _dest, const CAN_BUFFER * const _src);


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

	\param	_ctx		Kontext CANOpen
	\param	_nodeID		ID, kteremu se ma zprava odeslat
	\param	_command	Prikaz, ktery se ma odeslat
*/
//------------------------------------------------------

void CANOpenSendNMT(CANOPEN_CTX * const _ctx, const nmt_nodeid_t _nodeID, const nmt_cmd_t _command);


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

	\param	ctx		Kontext CANOpen
	\param	ID		CAN identifikator
	\param	Data	Zprava
	\param	Size	Pocet bytu zpravy (max 8)
*/
//----------------------------------------------------

void CANOpenSendPDO(CANOPEN_CTX * const _ctx, const CANIDTYPE _id, const void * const _data, const uint8_t _size);


//----------------------------------------------------
/**
	\brief	Funkce zadava pozadavek na cteni dat SDO 

	\param	_ctx			Kontext CANOpen
	\param	_rqId			CAN ID dotazu (download kanal)
	\param	_rspId			CAN ID odpovedi (upload kanal)
	\param	_index			Index parametru
	\param	_subIndex		Subindex parametru
	\param	_size			Ocekavana velikost ctenych dat
	\param	_userData		Data, ktera budou predana callbacku

	\return		TRUE, pokud byl pozadavek zarazen ke zpracovani

	Ukazatel na libovolny callback muze byt NULL, pak je pouzit Common Callback v kontextu CANOpen
*/
//-----------------------------------------------------

bool_t CANOpenSendSDOReadReq(
		CANOPEN_CTX * const _ctx, 
		const CANIDTYPE _rqId, 
		const CANIDTYPE _rspId, 
		const sdo_index_t _index, 
		const sdo_subindex_t _subIndex, 
		const uint8_t _size,
		void * const _userData);


//----------------------------------------------------
/**
	\brief	Funkce zadava pozadavek na zapis dat SDO

	\param	_ctx			Kontext CANOpen
	\param	_rqId			CAN ID dotazu (download kanal)
	\param	_rspId			CAN ID odpovedi (upload kanal)
	\param	_index			Index parametru
	\param	_subIndex		Subindex parametru
	\param	_data			Zapisovana data
	\param	_size			Ocekavana velikost ctenych dat
	\param	_userData		Data, ktera budou predana callbacku

	\return		TRUE, pokud byl pozadavek zarazen ke zpracovani

	Ukazatel na libovolny callback muze byt NULL, pak je pouzit Common Callback v kontextu CANOpen
*/
//-----------------------------------------------------

bool_t CANOpenSendSDOWriteReq(
		CANOPEN_CTX * const _ctx, 
		const CANIDTYPE _rqId,
		const CANIDTYPE _rspId,
		const sdo_index_t _index, 
		const sdo_subindex_t _subIndex, 
		const void * const _data, 
		const uint8_t _size,
		void * const _userData);


//----------------------------------------------------
/**
	\brief	Test, zda je knihovna CANOpen pripravena k vysilani

	\param	ctx		Kontext CANOpen

	\return	TRUE	Pokud je knihovna pripravena
*/
//----------------------------------------------------

bool_t CANOpenReady(CANOPEN_CTX * const _ctx);


//------------------------------------------------------
/**
	\brief	Vlozeni periodicky odesilane zpravy do seznamu

	\param	ctx		Kontext CANOpen
	\param	msg		Vyplnena struktura CANOPEN_PERIOD_MSG
*/
//------------------------------------------------------

void CANOpenInsertPeriodMsg(CANOPEN_CTX * const _ctx, CANOPEN_PERIOD_MSG * const _msg);


//------------------------------------------------------
/**
	\brief	Odstraneni periodicky odesilane zpravy ze seznamu

	\param	ctx		Kontext automatu
	\param	msg		Odstranovana zprava

	\return	TRUE	Pokud se zpravu podarilo odstranit
*/
//------------------------------------------------------

bool_t CANOpenRemovePeriodMsg(CANOPEN_CTX * const _ctx, const CANOPEN_PERIOD_MSG * const _msg);


//------------------------------------------------------
/**
	\brief	Nastavi vychozi hodnoty poctu opakovani a casu pri neprichodu pozadavku SDO

	\param	_ctx			Kontext CANOpen
	\param	_retryCount		Pocet opakovani pozadavku SDO pri chybe
	\param	_retryTimeout	Doba cekani na odpoved pred opakovanim; je-li 0, nevyhodnocuje se
*/
//------------------------------------------------------

void CANOpenSetSDORetry(CANOPEN_CTX * const _ctx, const uint8_t _retryCount, const ticks_t _retryTimeout);


//------------------------------------------------------
/**
	\brief	Inicializace zarizeni CANOpen

	\param	ctx			Kontext CANOpen
	\param	cmm			Pridruzeny komunikacni objekt
	\param	Priority	Priorita tohoto zarizeni v ramci seznamu CAN zarizeni

	\return	TRUE		pokud se inicializace zdarila
*/
//------------------------------------------------------

bool_t CANOpenInitialize(CANOPEN_CTX * const _ctx, CANCOMMDESC * const _cmm, const uint8_t _priority);


//------------------------------------------------------
/**
	\brief	Ukonceni zarizeni CANOpen

	\param	ctx		Kontext CANOpen

	\return	TRUE, pokud se ukonceni zdarilo
*/
//-----------------------------------------------------

bool_t CANOpenFinalize(CANOPEN_CTX * const _ctx);

#endif // _CANOPEN_H_
