/*
 * true's NRF24L01+ C library
 * shamelessly stolen and modified heavily from some other shit
 * basically: it's better than nothing
 * 
 * ====
 *
 * general instructions to set up:
 * - Edit any shit necessary for your driver to work, update "true_nRF24L01.h"
 *     with your driver include file
 * - #include "true_nRF24L01.h" in your radio setup code source file
 * - for RX devices, you should set nRF24_rx_cb to your data handler. this
 *     handler is called with the data buffer / buffer length and received pipe.
 * - call nRF24_init() which sets defaults, calls nRF24_driver_init() which
 *     sets up hardware tasks, peripherals, and interrupts
 * - set and update your configuration, tx/rx addresses in your code
 * - in your IRQ pin ISR, call nRF24_interrupt(). this might be in your driver.
 * - if you did not set nRF24_rx_cb, check for new data by reading status
 *     and RX FIFO, and receive data manually however you decide to do it
 * - no special shit for transmit, just set up and go
 */


/* nrf24 library includes */
#include "true_nRF24L01.h"


/* nrf24 configuration */
static uint32_t txdelay;
static nrfConfig config;


/* nrf24 data handling */
static uint8_t nRF24_pbuf[_TRNRF_MAX_PACKET_SIZE];
void (*nRF24_rx_cb)(uint8_t pipe, uint8_t *pBuf, uint8_t len);
void (*nRF24_tx_done_cb)();
void (*nRF24_tx_max_rt_cb)();


/* nrf24 feedback */
static uint8_t nrf_mode = _TRNRF_MODE_OFF;
static uint8_t rx_received_pipe;



/*********************************/
/**** NRF SETUP AND INTERRUPT ****/

/**
 * Initializes nRF24 config and hardware layer abstracted shit.
 */
void nRF24_init(uint32_t cpu_freq_hz) {
	// TX pin delay factor
	txdelay = cpu_freq_hz >> 19;

	// set some defaults
	config.channel 			= 2;
	config.rate_power 		= RF24_SPEED_1000 | RF24_POWER_FULL;
	config.addr_width 		= 5;
	config.payload_size[0] 	= 8;
	config.payload_size[1] 	= 8;
	config.crc 				= RF24_EN_CRC | RF24_CRCO;
	config.pipe_ena 		= RF24_ERX_P0 | RF24_ERX_P1; 	// this is NRF power-up default
	config.autoack_ena 		= 0x3f; 						// this is NRF power-up default
	config.retry 			= RF24_RETRY_500US | RF24_RETRANS_10X;
	config.tx_config 		= _TRNRF_MAX_RT_AUTOFLUSH | _TRNRF_MODE_RX;

	// start up the hardware shit
	nRF24_driver_init();

	// clear the RX/TX FIFOs
	nRF24_reg_write(RF24_FLUSH_RX);
	nRF24_reg_write(RF24_FLUSH_TX);
	// and clear interrupt sources
	nRF24_clear_irq_flags();
}


/* Note: at this time, this code requires the IRQ pin to operate */

/**
 * Handles doing shit (receive data, clear IRQ flags) when IRQ is asserted.
 *
 * Call this function from your IRQ handler.
 *
 * @return 	 			NRF status
 */
uint8_t nRF24_interrupt()
{
	uint8_t status;
	uint8_t pipe;
	uint8_t len;

	status = nRF24_reg_write(RF24_NOP);

	if (status & RF24_TX_DS) {
		// packet sent successfully and should not be in our FIFO
		if (nRF24_tx_done_cb != 0) {
			nRF24_tx_done_cb();
		}
	}

	if (status & RF24_MAX_RT) {
		// packet not sent successfully and IS still in our FIFO
		if (config.tx_config & _TRNRF_MAX_RT_AUTOFLUSH) {
			nRF24_reg_write(RF24_FLUSH_TX);
		}

		if (nRF24_tx_max_rt_cb != 0) {
			nRF24_tx_max_rt_cb();
		}
	}

	if (status & RF24_RX_DR) {
		// process data
		if ((status & RF24_RX_P_NO) == RF24_RX_P_NO) {
			// fifo done interrupt, so what? we don't care
		} else {
			if (nRF24_data_is_ready()) {
				// looks like we have some data waiting for us
				// if no callback is set, assume we are handling this manually elsewhere
				if (nRF24_rx_cb != 0) {
					// but our callback is set, so receive data
					len = nRF24_packet_rx(status, nRF24_pbuf);
					pipe = nRF24_rx_received_pipe();
					// process data
					if (len <= _TRNRF_MAX_PACKET_SIZE) {
						nRF24_rx_cb(pipe, nRF24_pbuf, len);
					}
				}
			}
		}
	}

	// clear all pending interrupt flags
	nRF24_clear_irq_flags();

	// resume wtf we needed to do
	if (nrf_mode & _TRNRF_MODE_TX) {
		switch (config.tx_config & _TRNRF_MODE_MASK) {
			case _TRNRF_MODE_RX: {
				nRF24_mode_rx();
				break;
			}
			case _TRNRF_MODE_OFF: {
				nRF24_mode_off();
				break;
			}
			case _TRNRF_MODE_TX: {
				// this will put us into standby-ii. this mode is not recommended.
				_TRNRF_CE_H();
				break;
			}
		}
	} else if (nrf_mode & _TRNRF_MODE_RX) {
		nRF24_mode_rx();
	}

	return status;
}


/***************************/
/**** NRF SPI FUNCTIONS ****/

/**
 * Writes a value to NRF register or a single-byte argument to a command,
 * and reads the following byte (usually the result, see the datasheet)
 *
 * @param 	reg 	register / command to write to
 * @param 	value 	value to write to register / command
 *
 * @return 			value of the STATUS register
 */
uint8_t nRF24_reg_rw(uint8_t reg, uint8_t value) {
	uint8_t status;

	_TRNRF_CSN_L();

	status = nRF24_spi_rw(reg); // Select register/command
	nRF24_spi_rw(value); // Write value to register

	_TRNRF_CSN_H();

	return status;
}

/**
 * Writes a command byte to the NRF, and returns STATUS.
 *
 * @param 	cmd 	command to write
 *
 * @return 			value of the STATUS register
 */
uint8_t nRF24_reg_write(uint8_t cmd) {
	uint8_t status;

	_TRNRF_CSN_L();

	status = nRF24_spi_rw(cmd);

	_TRNRF_CSN_H();

	return status;
}

/**
 * Reads a byte from the NRF register or command.
 *
 * @param 	reg 	register / command to read from
 *
 * @return 			value of this register / command
 */
uint8_t nRF24_reg_read(uint8_t reg) {
	uint8_t value;

	_TRNRF_CSN_L();

	nRF24_spi_rw(reg);
	value = nRF24_spi_rw(0x00);

	_TRNRF_CSN_H();

	return value;
}

/**
 * Writes a buffer to NRF.
 *
 * @param 	reg 	register (buffer) to write info
 * @param 	*pBuf 	source buffer contents to read from
 * @param 	len 	bytes to read
 *
 * @return 			value of the STATUS register
 */
uint8_t nRF24_buf_write(uint8_t reg, void *pBuf, uint8_t bytes) {
	uint8_t status;
	uint8_t i;

	_TRNRF_CSN_L();

	status = nRF24_spi_rw(reg);

	for (i = 0; i < bytes; i++)
		nRF24_spi_rw(((uint8_t *)pBuf)[i]);

	_TRNRF_CSN_H();

	return status;
}

/**
 * Reads a buffer from NRF.
 *
 * @param 	reg 	register (buffer) to read from
 * @param 	*pBuf 	buffer to read register into
 * @param 	bytes 	bytes to read
 *
 * @return 			value of the STATUS register
 */
uint8_t nRF24_buf_read(uint8_t reg, void *pBuf, uint8_t bytes) {
	uint8_t status;
	uint8_t i;

	_TRNRF_CSN_L();

	status = nRF24_spi_rw(reg);

	for (i = 0; i < bytes; i++)
		((uint8_t *)pBuf)[i] = nRF24_spi_rw(0x00);

	_TRNRF_CSN_H();

	return status;
}


/***********************************/
/**** NRF USER CONFIG FUNCTIONS ****/

/**
 * Updates the TX address to the radio.
 */
void nRF24_update_txaddr()
{
	_TRNRF_CE_L();

	if (config.tx_addr) {
		nRF24_buf_write(RF24_WREG | RF24_TX_ADDR, config.tx_addr, config.addr_width);
	}

	// were we in RX mode? yeah? then start listening again
	if ((nrf_mode & _TRNRF_MODE_MASK) == _TRNRF_MODE_RX) {
		_TRNRF_CE_H();
	}
}

/**
 * Sets the TX address buffer pointer, and updates the radio with this address.
 * Make sure this buffer is at least config.addr_width in size.
 *
 * @param 	*addr 	pointer to tx buffer
 */
void nRF24_set_txaddr(const uint8_t *addr)
{
	config.tx_addr = (uint8_t *)addr;
	nRF24_update_txaddr();
}


/**
 * Updates the specified RX address to the radio.
 *
 * Please remember, auto-acknowledge requires pipe 0's address to match the
 * transmit address.
 *
 * Please remember, pipes 2 through 5 copy pipe 1's most significant bytes.
 */
void nRF24_update_rxaddr(uint8_t pipe)
{
	_TRNRF_CE_L();

	if (pipe == 0 && config.rx_addr_p0) {
		nRF24_buf_write(RF24_WREG | RF24_RX_ADDR_P0, config.rx_addr_p0, config.addr_width);
	} else if (pipe == 1 && config.rx_addr_p1) {
		nRF24_buf_write(RF24_WREG | RF24_RX_ADDR_P1, config.rx_addr_p1, config.addr_width);
	} else if (pipe <= 5) {
		nRF24_reg_rw(RF24_WREG | (RF24_RX_ADDR_P0 + pipe), config.rx_pipe[pipe - 2]);
	}

	// were we in RX mode? yeah? then start listening again
	if ((nrf_mode & _TRNRF_MODE_MASK) == _TRNRF_MODE_RX) {
		_TRNRF_CE_H();
	}
}

/**
 * Sets the RX address buffer pointer for pipes 0 and 1, and sets the
 * unique byte for pipes 2 through 5. Afterwards, updates the radio
 * with the new address.
 *
 * For pipes 0 and 1, make sure this buffer is at least config.addr_width in size.
 *
 * @param 	*addr 	pointer to rx buffer (can be temporary for pipes 2 through 5)
 */
void nRF24_set_rxaddr(uint8_t pipe, const uint8_t *addr)
{
	if (pipe == 0) {
		config.rx_addr_p0 = (uint8_t *)addr;
	} else if (pipe == 1) {
		config.rx_addr_p1 = (uint8_t *)addr;
	} else if (pipe <= 5) {
		config.rx_pipe[pipe - 2] = *addr;
	}

	nRF24_update_rxaddr(pipe);
}


/**
 * Sets the active channel between 2400MHz (0) and 2.525MHz (125).
 * At 2Mbps channel width is larger than 1MHz, so use 2MHz spacings
 * if you have multiple channels with multiple devices in use.
 */
void nRF24_set_channel(uint8_t chan)
{
	if (chan > 125) chan = 125;
	config.channel = chan;

}

/**
 * Sets the data rate between RF_SPEED_250, RF_SPEED_1000, and RF_SPEED_2000.
 */
void nRF24_set_rate(uint8_t rate)
{
	config.rate_power &= ~(RF24_SPEED_250 | RF24_SPEED_1000 | RF24_SPEED_2000);
	config.rate_power |= (rate & (RF24_SPEED_250 | RF24_SPEED_1000 | RF24_SPEED_2000));
}

/**
 * Sets the output power level.
 */
void nRF24_set_power(uint8_t power)
{
	config.rate_power &= ~(RF24_POWER_FULL);
	config.rate_power |= (power & (RF24_POWER_FULL));
}

/**
 * Sets the address width from 3 to 5 bytes wide.
 */
void nRF24_set_addr_width(uint8_t width)
{
	if (width < 3) width = 3;
	if (width > 5) width = 5;
	config.addr_width = width;
}

/**
 * Sets the payload size.
 */
void nRF24_set_payload_size(uint8_t pipe, uint8_t size)
{
	if (pipe <= 5) {
		config.payload_size[pipe] = size;
	}
}

/**
 * Sets the CRC options.
 */
void nRF24_set_crc(uint8_t crc)
{
	config.crc = crc & (RF24_EN_CRC | RF24_CRCO);
}

/**
 * Enables and disables receive pipes.
 */
void nRF24_set_pipe(uint8_t pipe, uint8_t onoff)
{
	if (pipe <= 5) {
		if (onoff) {
			config.pipe_ena |= (1 << pipe);
		} else {
			config.pipe_ena &= ~(1 << pipe);
		}
	}
}

/**
 * Enables and disables Shockburst (auto-acknowledge) per pipe.
 */
void nRF24_set_autoack(uint8_t pipe, uint8_t onoff)
{
	if (pipe <= 5) {
		if (onoff) {
			config.autoack_ena |= (1 << pipe);
		} else {
			config.autoack_ena &= ~(1 << pipe);
		}
	}
}

/**
 * Enables or disables the Dynamic ACK transmit option.
 */
void nRF24_set_dynack(uint8_t onoff)
{
	if (onoff) {
		config.autoack_ena |= 0x80;
	} else {
		config.autoack_ena &= 0x7f;
	}
}

/**
 * Sends the active configuration to the radio.
 */
void nRF24_update_config(uint8_t mask)
{
	uint8_t set[3];
	uint8_t bit;
	uint8_t i;

	set[0] = set[1] = set[2] = 0;

	_TRNRF_CE_L();

	// set SETUP_AW address width
	if (mask & _TRNRF_UPDATE_SETUP_AW) {
		nRF24_reg_rw(RF24_WREG | RF24_SETUP_AW, config.addr_width - 2);
	}

	// set up various per-pipe configurations
	if (mask & (_TRNRF_UPDATE_EN_RXADDR | _TRNRF_UPDATE_EN_AA | _TRNRF_UPDATE_DYNPD | _TRNRF_UPDATE_FEATURE)) {
		for (i = 0; i <= 5; i++) {
			bit = 1 << i;

			if (config.pipe_ena & bit) {
				set[0] |= bit;

				nRF24_reg_rw(RF24_WREG | (RF24_RX_PW_P0 + i),
						(config.payload_size[i] > 32 ? 32 : config.payload_size[i]));
			}
			if (config.autoack_ena & bit) {
				set[1] |= bit;
			}
			if (config.payload_size[i] == _TRNRF_DYN_PAYLOAD) {
				set[2] |= bit;
			}
		}

		if (mask & _TRNRF_UPDATE_EN_AA)     nRF24_reg_rw(RF24_WREG | RF24_EN_AA, set[1]);
		if (mask & _TRNRF_UPDATE_EN_RXADDR) nRF24_reg_rw(RF24_WREG | RF24_EN_RXADDR, set[0]);

		if (mask & (_TRNRF_UPDATE_DYNPD | _TRNRF_UPDATE_FEATURE)) {
			nRF24_reg_rw(RF24_WREG | RF24_DYNPD, set[2]);

			// set up FEATURE register (dynamic payload length, ack payload, dynamic ack [noack])
			set[0] = ((set[2] ? RF24_EN_DPL : 0) | ((config.autoack_ena & 0x80) ? RF24_EN_DYN_ACK : 0));
			nRF24_reg_rw(RF24_WREG | RF24_FEATURE, set[0]);
		}
	}

	// set SETUP_RETR auto-ack retry settings
	if (mask & _TRNRF_UPDATE_SETUP_RETR) {
		nRF24_reg_rw(RF24_WREG | RF24_SETUP_RETR, config.retry);
	}

	// set RF_CH channel
	if (mask & _TRNRF_UPDATE_RF_CH) {
		nRF24_reg_rw(RF24_WREG | RF24_RF_CH, config.channel);
	}

	// set RF_SETUP data rate, power output
	if (mask & _TRNRF_UPDATE_RF_SETUP) {
		nRF24_reg_rw(RF24_WREG | RF24_RF_SETUP, config.rate_power);
	}

	// set CONFIG crc
	if (mask & _TRNRF_UPDATE_CRC) {
		set[0] = nRF24_reg_read(RF24_CONFIG) & (RF24_PWR_UP | RF24_PRIM_RX);
		nRF24_reg_rw(RF24_WREG | RF24_CONFIG, config.crc | set[0]);
	}

	// were we in RX mode? yeah? then start listening again
	if ((nrf_mode & _TRNRF_MODE_MASK) == _TRNRF_MODE_RX) {
		_TRNRF_CE_H();
	}
}

/**
 * Sets the state to go into after transmit is done.
 * This mode is applied after a TX is complete in the interrupt handler.
 */
void nRF24_set_tx_config(uint8_t max_rt_flush, uint8_t new_done_state)
{
	config.tx_config = (max_rt_flush & 0x80) | (new_done_state & 0x7f);
}


/**
 * Checks to see if an NRF24L01 is present by setting and reading a fake address.
 * After completion, it resets to the configured address parameters.
 *
 * @return 			nRF24 is present (1 = yes, 0 = no)
 */
uint8_t nRF24_is_present(void) {
	uint8_t buf[3];

	// write address length and fake address
	buf[2] = 0xa9;
	nRF24_reg_rw(RF24_WREG | RF24_SETUP_AW, 0x01); 	// 3 bytes
	nRF24_buf_write(RF24_WREG | RF24_TX_ADDR, buf, 3);
	// clear some address data
	buf[2] = 0x00;

	// try to read TX_ADDR register back
	nRF24_buf_read(RF24_TX_ADDR, buf, 3);

	// reset SETUP_AW and TX addr
	nRF24_reg_rw(RF24_WREG | RF24_SETUP_AW, config.addr_width - 2);
	nRF24_update_txaddr();

	// verify address matches fake address
	if (buf[2] == 0xa9)
		return 1;

	// seems to be missing...
    return 0;
}


/*************************/
/**** OPERATING MODES ****/

/**
 * Puts the NRF into RX mode, turns on the radio, and starts listening.
 *
 * Only sets registers necessary to do this. Configure the radio
 * with nRF24_update_rxaddr, nRF24_update_config before putting
 * it into RX mode.
 */
void nRF24_mode_rx() {
	_TRNRF_CE_L();

	nRF24_reg_rw(RF24_WREG | RF24_CONFIG, config.crc | RF24_PWR_UP | RF24_PRIM_RX);

	// put into active receive mode immediately
	_TRNRF_CE_H();
	nrf_mode = _TRNRF_MODE_RX;
}

/**
 * Prepares for standby, then puts the NRF into TX mode and turns on the radio.
 *
 * This is called from nRF24_packet_tx() automatically if the mode is
 * currently not in a transmit mode. Transmit will occur when
 * nRF24_packet_tx() is used.
 *
 * Only sets registers necessary to complete changing the mode. Configure any
 * other settings for the radio with nRF24_update_rxaddr (if auto-ack is on),
 * nRF24_update_txaddr, nRF24_update_config before putting it into TX mode.
 */
void nRF24_mode_tx() {
	// we will set high when ready to transmit
	_TRNRF_CE_L();

	nRF24_reg_rw(RF24_WREG | RF24_CONFIG, config.crc | RF24_PWR_UP);
	nrf_mode = _TRNRF_MODE_TX;
}

/**
 * Puts the radio into standby (useful when in RX mode to save power).
 */
void nRF24_mode_standby()
{
	_TRNRF_CE_L();
	nrf_mode |= _TRNRF_MODE_STANDBY;
}

/**
 * Powers down the NRF24L01.
 */
void nRF24_mode_off()
{
	_TRNRF_CE_L();

	nRF24_reg_rw(RF24_WREG | RF24_CONFIG, nRF24_reg_read(RF24_CONFIG) & ~(RF24_PWR_UP));
	nrf_mode = _TRNRF_MODE_OFF;
}

/**
 * Returns the currently operating NRF mode.
 */
uint8_t nRF24_mode_get()
{
	return nrf_mode;
}


/**************************/
/**** DATA TRANSCIEVER ****/

/**
 * Checks if data is ready by seeing if there is anything in the RX FIFO.
 */
uint8_t nRF24_data_is_ready(void) {
    uint8_t status;

    status = nRF24_reg_read(RF24_FIFO_STATUS);
    return (status & RF24_RX_EMPTY) ? 0 : 1;
}

/**
 * Loads a packet for transmit, puts the radio into transmit mode, then signals
 * radio to begin transmitting.
 *
 * @param 	*pBuf 	pointer to transmit buffer
 * @param 	bytes 	bytes to send
 * @param 	noack 	if not zero, and if dynack enabled, flags the packet NO_ACK
 */
void nRF24_packet_tx(void *pBuf, uint8_t bytes, uint8_t noack) {
    int i;

    // make sure we are in TX mode
    if ((nrf_mode & _TRNRF_MODE_TX) == 0) {
    	nRF24_mode_tx();
    }

    // write TX buffer to FIFO
	// note: noack mode will only work if nRF24_set_dynack(1); nRF24_config_update(); has been done
    // also make sure that you have nRF24_set_rxaddr(0, txaddr) if using shockburst
	nRF24_buf_write((noack && config.autoack_ena & 0x80) ?
			RF24_W_TX_PAYLOAD_NOACK : RF24_W_TX_PAYLOAD, pBuf, bytes);

    // CE pin high => start transmit
    _TRNRF_CE_H();

    // if we're going into standard standby-I mode, need to do a little more
    if (!(config.tx_config & _TRNRF_MODE_TX)) {
    	// needs to be high for at least 10us for tx to function, per datasheet
		// this keeps it high for about 16us
		for (i = txdelay; i; i--) {
			asm volatile ("nop");
		}

		_TRNRF_CE_L();
    }
}

/**
 * Receives a packet.
 *
 * @param 	status 	nRF24 status (please get with nRF24_reg_read(RF24_STATUS))
 * @param 	*pBuf 	pointer to receive buffer
 *
 * @return 			length of data received
 */
int8_t nRF24_packet_rx(uint8_t status, void *pBuf) {
	uint8_t pipe;
	uint8_t payload_len;

	// status must indicate RX ready and RX FIFO not empty
	if ((status & RF24_RX_DR) && ((status & RF24_RX_P_NO) != RF24_RX_P_NO)) {
		// get pipe
		pipe = (status & RF24_RX_P_NO) >> 1;

		// payload is static, unless it isn't, in which case get the received payload length
		payload_len = config.payload_size[pipe];
		if (payload_len == _TRNRF_DYN_PAYLOAD) {
			payload_len = nRF24_reg_read(RF24_R_RX_PL_WID);

			// is it valid?
			if (payload_len > 32) {
				// no, flush buffer and quit
				nRF24_reg_write(RF24_FLUSH_RX);
				return -1;
			}
		}

		// everything else done, so set pipe, clear buffer, read payload from RX FIFO buffer
		// NRF automatically removes this data from FIFO per datasheet
		rx_received_pipe = pipe;
		nRF24_buf_read(RF24_R_RX_PAYLOAD, pBuf, payload_len);
		return payload_len;
    }

	// requests to receive that are not ready will clear RX FIFO
	nRF24_reg_write(RF24_FLUSH_RX);
	return -2;
}

/**
 * Returns the last received data's pipe number.
 */
uint8_t nRF24_rx_received_pipe()
{
	return rx_received_pipe;
}

uint8_t nRF24_packet_ackreply(uint8_t *pBuf, uint8_t tx_payload_len)
{
	if (config.payload_size[0] != _TRNRF_DYN_PAYLOAD) {
		// ack reply payloads require dynamic payload to be enabled
		return 0;
	}

	// not yet implemented
	return 1;
}


/*****************/
/**** IRQ PIN ****/

/**
 * Clear all IRQ status flags.
 */
void nRF24_clear_irq_flags(void) {
	// Clear RX_DR, TX_DS, MAX_RT flags. cleared by writing 1 to these bits
    nRF24_reg_rw(RF24_WREG | RF24_STATUS, (RF24_RX_DR | RF24_TX_DS | RF24_MAX_RT));
}
