/*
 * rtu_serial_io.c
 *
 *  Created on: 27.08.2020
 *      Author: mateusz
 */

#include "../include/etc/rtu_configuration.h"

#include "modbus_rtu/rtu_serial_io.h"
#include "modbus_rtu/rtu_crc.h"
#include "modbus_rtu/rtu_parser.h"
#include "modbus_rtu/rtu_return_values.h"
#include "modbus_rtu/rtu_register_data_t.h"
#include "modbus_rtu/rtu_request.h"

#include "drivers/serial.h"

#include "main.h"
#include "rte_wx.h"
#include "rte_main.h"
#include "rte_rtu.h"

#include <string.h>
#include <stdio.h>

#define INTERFRAME_SP	20


#ifndef _RTU_SLAVE_LENGHT_1
	#define _RTU_SLAVE_LENGHT_1 0x1
#endif

#ifndef _RTU_SLAVE_LENGHT_2
	#define _RTU_SLAVE_LENGHT_2 0x1
#endif

#ifndef _RTU_SLAVE_LENGHT_3
	#define _RTU_SLAVE_LENGHT_3 0x1
#endif

#ifndef _RTU_SLAVE_LENGHT_4
	#define _RTU_SLAVE_LENGHT_4 0x1
#endif

#ifndef _RTU_SLAVE_LENGHT_5
	#define _RTU_SLAVE_LENGHT_5 0x1
#endif

#ifndef _RTU_SLAVE_LENGHT_6
	#define _RTU_SLAVE_LENGHT_6 0x1
#endif

typedef enum rtu_pool_state {
	RTU_POOL_IDLE,
	RTU_POOL_TRANSMITTING,
	RTU_POOL_RECEIVING,
	RTU_POOL_WAIT_AFTER_RECEIVE,
	RTU_POOL_RECEIVE_ERROR,
	RTU_POOL_STOP
} rtu_pool_state_t;

rtu_pool_state_t rtu_pool_state = RTU_POOL_STOP;

/**
 * Set to one to switch I/O operations to blocking mode
 */
uint8_t rtu_blocking_io = 0;

/**
 * Timestamp of last received modbus RTU response with good CRC
 */
uint32_t rtu_time_of_last_successfull_comm = 0;

/**
 * CRC value after the last call to rtu_serial_callback
 */
uint16_t rtu_serial_previous_crc = 0xFFFF;

/**
 * Cleared by 'rtu_serial_callback' when first byte from range 0x1..0xF7 is received
 */
uint8_t rtu_waiting_for_slave_addr = 0x1;

rtu_pool_queue_t* rtu_used_queue;

srl_context_t* rtu_used_serial_context;

volatile uint8_t rtu_current_03_slave_address;

volatile uint16_t rtu_current_03_base_register;

volatile uint8_t rtu_current_03_number_of_registers;

/**
 * The callback for stream CRC calculation
 */
uint8_t rtu_serial_callback(uint8_t current_data, const uint8_t * const rx_buffer, uint16_t rx_bytes_counter) {

	uint8_t retval = 0;

	uint16_t new_crc = 0;

	// check if the callback still waits for first 'valid' byte to be received from RTU slave
	if (rtu_waiting_for_slave_addr == 0x1) {

		// check if the byte which was received from the slave is valid address
		if (current_data >= 0x01 && current_data <= 0xF7) {
			// clear this flag to start CRC calculation and data receiving
			rtu_waiting_for_slave_addr = 0;
		}
		else {
			// RTU slave cannot respond with the broadcast address (0x00), also 0xF8..0xFF
			// are not valid RTU address
			;
		}
	}

	// the second 'if' clause checks if the slave response has began
	if (rtu_waiting_for_slave_addr == 0x0) {

		// calculate new crc
		new_crc = rtu_crc_stream(rtu_serial_previous_crc, current_data);

		// if the new CRC value equals 0x0000 it means that this was MSB
		// of CRC from correctly received Modbus-RTU frame
		if (new_crc == 0) {
			// return '1' to terminate the transmission
			retval = 1;
		}
		else {
			rtu_serial_previous_crc = new_crc;
		}
	}

	return retval;
}

int32_t rtu_serial_init(rtu_pool_queue_t* queue, uint8_t io_mode, srl_context_t* serial_context, const config_data_rtu_t * const config) {

	int32_t retval = MODBUS_RET_UNINITIALIZED;

	// zeroing the content of the structure
	memset(queue, 0x00, sizeof(rtu_pool_queue_t));

	rtu_blocking_io = io_mode;

	rtu_used_queue = queue;

	rtu_used_serial_context = serial_context;

//#ifdef _MODBUS_RTU

	queue->function_id[0] = config->slave_1_function;
	queue->function_parameter[0] = &rte_wx_modbus_rtu_f1;
	rte_wx_modbus_rtu_f1.slave_address = config->slave_1_bus_address;
	rte_wx_modbus_rtu_f1.base_address = config->slave_1_register_address;
	rte_wx_modbus_rtu_f1.number_of_registers = config->slave_1_lenght;

	queue->function_id[1] =config->slave_2_function;;
	queue->function_parameter[1] = &rte_wx_modbus_rtu_f2;
	rte_wx_modbus_rtu_f2.slave_address = config->slave_2_bus_address;
	rte_wx_modbus_rtu_f2.base_address = config->slave_2_register_address;
	rte_wx_modbus_rtu_f2.number_of_registers = config->slave_2_lenght;

	queue->function_id[2] =config->slave_3_function;;
	queue->function_parameter[2] = &rte_wx_modbus_rtu_f3;
	rte_wx_modbus_rtu_f3.slave_address = config->slave_3_bus_address;
	rte_wx_modbus_rtu_f3.base_address = config->slave_3_register_address;
	rte_wx_modbus_rtu_f3.number_of_registers = config->slave_3_lenght;

	queue->function_id[3] =config->slave_4_function;;
	queue->function_parameter[3] = &rte_wx_modbus_rtu_f4;
	rte_wx_modbus_rtu_f4.slave_address = config->slave_4_bus_address;
	rte_wx_modbus_rtu_f4.base_address = config->slave_4_register_address;
	rte_wx_modbus_rtu_f4.number_of_registers = config->slave_4_lenght;

	queue->function_id[4] =config->slave_5_function;;
	queue->function_parameter[4] = &rte_wx_modbus_rtu_f5;
	rte_wx_modbus_rtu_f5.slave_address = config->slave_5_bus_address;
	rte_wx_modbus_rtu_f5.base_address = config->slave_5_register_address;
	rte_wx_modbus_rtu_f5.number_of_registers = config->slave_5_lenght;


	queue->function_id[5] =config->slave_6_function;;
	queue->function_parameter[5] = &rte_wx_modbus_rtu_f6;
	rte_wx_modbus_rtu_f6.slave_address = config->slave_6_bus_address;
	rte_wx_modbus_rtu_f6.base_address = config->slave_6_register_address;
	rte_wx_modbus_rtu_f6.number_of_registers = config->slave_6_lenght;


//#endif

	return retval;

}

int32_t rtu_serial_pool(void) {

	int32_t retval = MODBUS_RET_UNINITIALIZED;
//#ifdef _MODBUS_RTU
	int32_t result = MODBUS_RET_UNINITIALIZED;

	uint8_t output_data_lenght = 0;

	rtu_register_data_t* ptr_func_03;

	// if there were any serial I/O error
	if (rte_rtu_number_of_serial_io_errors > 0) {

		// check how many serial I/O erros have been detected so far
		if ((rte_rtu_number_of_serial_io_errors % RTU_NUMBER_OF_ERRORS_TO_TRIG_STATUS) == 0) {

			// increment the error counter artificially to protect sending status in the loop
			rte_rtu_number_of_serial_io_errors++;

			// stupid workaround. If there is a lot of I/O errors reset the controller
			// no more stupid workarounds here
//			if (rte_rtu_number_of_serial_io_errors >= 0xF0)
//			{
//				rte_main_reboot_req = 1;
//			}

		}
	}

	if (rtu_used_queue->it >= RTU_POOL_QUEUE_LENGHT) {
		rtu_used_queue->it = 0;

		// all queued modbus functions have been serviced
		rtu_pool_state = RTU_POOL_STOP;
	}

	switch (rtu_pool_state) {
		case RTU_POOL_IDLE: {

			// initialize the serial port.
			srl_init(	rtu_used_serial_context,
					rtu_used_serial_context->port,
					rtu_used_serial_context->srl_rx_buf_pointer,
					rtu_used_serial_context->srl_rx_buf_ln,
					rtu_used_serial_context->srl_tx_buf_pointer,
					rtu_used_serial_context->srl_tx_buf_ln,
					rtu_used_serial_context->port_baurate,
					rtu_used_serial_context->port_stopbits);

			// Enabling the timeout for Modbus-RTU.
			// This timeout starts after first received byte and triggers if
			// the slave will hang up and stop the transmission before the end of the frame
			// It doesn't need to be called each time but this is the only function which takes
			// the pointer to serial context
			srl_switch_timeout(rtu_used_serial_context, 1, 0);

			srl_switch_tx_delay(rtu_used_serial_context, 1);

			// check the function it at current queue position
			if (rtu_used_queue->function_id[rtu_used_queue->it] == 0x03) {
				// read holding registers
				ptr_func_03 = ((rtu_register_data_t*)rtu_used_queue->function_parameter[rtu_used_queue->it]);

				rtu_current_03_slave_address = ptr_func_03->slave_address;
				rtu_current_03_base_register = ptr_func_03->base_address;
				rtu_current_03_number_of_registers = ptr_func_03->number_of_registers;

				// generate request content
				result = rtu_request_03_04_registers(
						1,
						rtu_used_serial_context->srl_tx_buf_pointer,
						rtu_used_serial_context->srl_tx_buf_ln,
						&output_data_lenght,
						rtu_current_03_slave_address,
						rtu_current_03_base_register,
						rtu_current_03_number_of_registers);
			}
			else if (rtu_used_queue->function_id[rtu_used_queue->it] == 0x04) {
				// read input registers

				// generate request content
				result = rtu_request_03_04_registers(
						0,
						rtu_used_serial_context->srl_tx_buf_pointer,
						rtu_used_serial_context->srl_tx_buf_ln,
						&output_data_lenght,
						((rtu_register_data_t*)rtu_used_queue->function_parameter[rtu_used_queue->it])->slave_address,
						((rtu_register_data_t*)rtu_used_queue->function_parameter[rtu_used_queue->it])->base_address,
						((rtu_register_data_t*)rtu_used_queue->function_parameter[rtu_used_queue->it])->number_of_registers);
			}
			else {
				// any other unsupported or wrong function id. It will also stop at the last element
				// on the last element of the queue
				rtu_pool_state = RTU_POOL_STOP;

				// rewind the iterator back to the begining
				rtu_used_queue->it = 0;

				retval = MODBUS_RET_WRONG_FUNCTION;
			}


			// if request has been generated correctly
			if (result == MODBUS_RET_OK) {

				// check if block I/O mode shall be used for the communication
				if (rtu_blocking_io) {
					// call the function which will process the same stuff than
					// RTU_POOL_IDLE (from transmitting), RTU_POOL_TRANSMITTING
					// and partially RTU_POOL_RECEIVING
					result = rtu_serial_blocking_io(rtu_used_serial_context, output_data_lenght);

					if (result == MODBUS_RET_OK) {
						// if transmission and reception was successful switch to the
						// receiving state. The 'serial_context->srl_rx_state' will be
						// alredy set to 'SRL_RX_DONE'
						rtu_pool_state = RTU_POOL_RECEIVING;
					}
					else {
						rtu_pool_state = RTU_POOL_RECEIVE_ERROR;
					}
				}
				else {
					// trigger the transmission itself
					result = srl_start_tx(rtu_used_serial_context, output_data_lenght);

					// reset the CRC value to default
					rtu_serial_previous_crc = 0xFFFF;

					rtu_waiting_for_slave_addr = 1;

					// if serial transmission has been starter
					if (result == SRL_OK) {
						// proceed to the next state (transmitting)
						rtu_pool_state = RTU_POOL_TRANSMITTING;

						retval = MODBUS_RET_OK;
					}
					else {
						// if not do nothing and try in next pooler call
						;
					}
				}


			}
			else {
				retval = MODBUS_RET_REQUEST_GEN_ERR;
			}

			break;
		}
		case RTU_POOL_TRANSMITTING: {

			// if transmission is still pending
			if (rtu_used_serial_context->srl_tx_state == SRL_TXING || rtu_used_serial_context->srl_tx_state == SRL_TX_WAITING) {
				// wait until it will finish
				;
			}
			else {
				// trigger reception
				srl_receive_data_with_callback(rtu_used_serial_context, rtu_serial_callback);

				// enable the timeout in case the RTU slave won't respond
				// at all or there is no slaves connected to RS485 bus
				srl_switch_timeout_for_waiting(rtu_used_serial_context, 1);

				// switch the state
				rtu_pool_state = RTU_POOL_RECEIVING;

			}

			retval = MODBUS_RET_OK;

			break;
		}
		case RTU_POOL_RECEIVING: {

			// if data reception still took place
			if (rtu_used_serial_context->srl_rx_state == SRL_WAITING_TO_RX || rtu_used_serial_context->srl_rx_state == SRL_RXING || rtu_used_serial_context->srl_rx_state == SRL_RX_IDLE) {
				// wait
				;
			}
			else if (rtu_used_serial_context->srl_rx_state == SRL_RX_DONE) {
				// parse the response from RTU slave // here there is a problem with changing slave address
				if (rtu_used_queue->function_id[rtu_used_queue->it] == 0x03 || rtu_used_queue->function_id[rtu_used_queue->it] == 0x04) {
					result = rtu_parser_03_04_registers(
							rtu_used_serial_context->srl_rx_buf_pointer,
							rtu_used_serial_context->srl_rx_bytes_counter,
							((rtu_register_data_t*)rtu_used_queue->function_parameter[rtu_used_queue->it]),
							&rte_rtu_last_modbus_exception);
				}
				else {
					retval = MODBUS_RET_WRONG_FUNCTION;
				}

				// check parsing result
				if (result == MODBUS_RET_OK) {
					// store the current time
					rtu_used_queue->last_successfull_call_to_function[rtu_used_queue->it] = main_get_master_time();

					// switch the state to inter-frame silence period
					rtu_pool_state = RTU_POOL_WAIT_AFTER_RECEIVE;
				}
				else if (result == MODBUS_RET_GOT_EXCEPTION) {
					// in case of an excetpion store the current timestamp
					rte_rtu_last_modbus_exception_timestamp = main_get_master_time();

					// switch the state to inter-frame silence period
					rtu_pool_state = RTU_POOL_WAIT_AFTER_RECEIVE;
				}
				else {
					// Receive error state will switch to the next function
					rtu_pool_state = RTU_POOL_RECEIVE_ERROR;
				}

				// get current time to start the inter-frame delay
				rtu_time_of_last_successfull_comm = main_get_master_time();

				rte_rtu_number_of_successfull_serial_comm++;

				// Close the serial port. This is a part of the stupid workaround of the problem
				// with a serial port which leads to receiving a lot of idle frames of unknown origin
				// and corrupting some part of data at the begining of some Modbus-RTU frames
				srl_close(rtu_used_serial_context);

			}

			// in case of any error during data reception or the serial driver have fallen into unknown & unexpected
			// state
			else {

				rtu_pool_state = RTU_POOL_RECEIVE_ERROR;

				srl_close(rtu_used_serial_context);
			}

			break;
		}
		case RTU_POOL_WAIT_AFTER_RECEIVE: {

			// check if required interframe silence period elapsed
			if (main_get_master_time() - rtu_time_of_last_successfull_comm > INTERFRAME_SP) {
				rtu_pool_state = RTU_POOL_IDLE;

				rtu_used_queue->it++;
			}
			break;
		}
		case RTU_POOL_RECEIVE_ERROR: {
			// if the response from the slave was corrupted or any other serial I/O error
			// occured switch to the next function in the queue
			rtu_pool_state = RTU_POOL_IDLE;

			// increasing the global counter of io errors
			rte_rtu_number_of_serial_io_errors++;

			rte_rtu_last_modbus_rx_error_timestamp = main_get_master_time();

			// icrease the error counter for this queue element
			rtu_used_queue->number_of_errors[rtu_used_queue->it] = rtu_used_queue->number_of_errors[rtu_used_queue->it] + 1;

			// move to the next function queued
			rtu_used_queue->it++;

			retval = MODBUS_RET_OK;

			break;
		}
		case RTU_POOL_STOP: {
			break;
		}
		default: {
			rtu_pool_state = RTU_POOL_STOP;
			break;
		}
	}
//#endif
	return retval;
}

int32_t rtu_serial_blocking_io(srl_context_t* serial_context, uint8_t query_ln) {

	int32_t retval = MODBUS_RET_UNINITIALIZED;

	uint8_t serial_result = SRL_UNINITIALIZED;

	// reset the CRC value to default
	rtu_serial_previous_crc = 0xFFFF;

	rtu_waiting_for_slave_addr = 1;

	// sending data
	serial_result = srl_start_tx(serial_context, query_ln);

	// if tx has been triggered successfully
	if (serial_result == SRL_OK) {

		// wait for transmission to complete
		srl_wait_for_tx_completion(serial_context);

		// trigger reception
		serial_result = srl_receive_data_with_callback(serial_context, rtu_serial_callback);

		if (serial_result == SRL_OK) {

			// enable the timeout in case the RTU slave won't respond
			// at all or there is no slaves connected to RS485 bus
			srl_switch_timeout_for_waiting(serial_context, 1);

			// wait for the slave response
			srl_wait_for_rx_completion_or_timeout(serial_context, &serial_result);

			if (serial_result == SRL_OK) {
				retval = MODBUS_RET_OK;
			}
			else {
				retval = MODBUS_RET_GOT_EXCEPTION;
			}
		}

	}

	return retval;
}

int32_t rtu_serial_start(void) {

	int32_t retval = MODBUS_RET_UNINITIALIZED;

	rtu_pool_state = RTU_POOL_IDLE;

	return retval;
}

