#include "bsp_modbus.h"

#include "kit_time.h"
#include "kit_data.h"
#include "kit_debug.h"

/********************************************************************************************************************************************************************************
1.Modbus数据结构
0x01	读线圈          设备地址/1B	    功能码/1B	起始地址/2B	     线圈数量/2B			                        设备地址/1B     功能码/1B      字节数/1B	    线圈状态/NB
0x02	读离散量输入	设备地址/1B	    功能码/1B	起始地址/2B	     输入数量/2B			                        设备地址/1B	    功能码/1B      字节数/1B	    输入状态/NB
0x03	读保持寄存器	设备地址/1B	    功能码/1B	起始地址/2B	     寄存器数量/2B			                        设备地址/1B	    功能码/1B      字节数/1B        寄存器值/2NB
0x04	读输入寄存器	设备地址/1B	    功能码/1B	起始地址/2B	     输入寄存器数量/2B                              设备地址/1B	    功能码/1B      字节数/1B        输入寄存器/2NB
0x05	写单个线圈	    设备地址/1B	    功能码/1B	输出地址/2B	     输出值/2B			                            设备地址/1B	    功能码/1B      输出地址/2B      输出值/2B
0x06	写单个寄存器	设备地址/1B	    功能码/1B	寄存器地址/2B    寄存器值/2B			                        设备地址/1B	    功能码/1B      寄存器地址/2B    寄存器值/2B
0x0F	写多个线圈	    设备地址/1B	    功能码/1B	起始地址/2B	     输出数量/2B	    字节数/1B	 输出值/NB	    设备地址/1B     功能码/1B      起始地址/2B      输出数量/2B
0x10	写多个寄存器	设备地址/1B	    功能码/1B	起始地址/2B	     寄存器数量/2B	    字节数/1B	 寄存器值/2NB	设备地址/1B     功能码/1B      起始地址/2B	    寄存器数量/2B

2.Modbus TCP MBAP报文头
                                  地址        功能码  数据  CRC校验                                  
事务处理标识符  协议标识符  长度  单元标识符  功能码  数据
0001           0000         0006  01          04      00000005
0001           0000         000D  01          04      0A00000001000200030004
-----------------------------------------------------------------------------------
事务处理标识符	2字节	Modbus请求/响应事务处理的标识	客户机启动	复制响应
协议标识符	    2字节	0=Modbus协议	                客户机启动	复制响应
长度	        2字节	长度之后的字节总数	            客户机启动	服务器启动
单元标识符	    1字节	串行链路或其它总线的从站识别	客户端启动	复制响应

3.写线圈
十六进制值0xFF00，请求线圈为 ON。十六进制值0x0000请求线圈为OFF。其它所有值均为非法的，并且对线圈不起作用。

********************************************************************************************************************************************************************************/
#define BRROCAST_ADDR		    (0u)
#define MB_DEV_ADDR_POS		    (0u)
#define MB_FUN_CODE_POS		    (1u)
#define MB_REG_ADDR_POS         (2u)
#define MB_DATA_POS			    (2u)
#define MB_REG_NUM_POS          (4u)

#define MB_MIN_LEN			    (3u)
//TCP比RTU多了事务处理标识符、协议标识符和长度，总共6B
#define MB_TCP_OFFSET			(6u)
#define MB_TCP_DATA_LEN_POS		(4u)
#define MB_TCP_DEV_ADDR_POS		(6u)


ModbusCommData modbus_comm_data __attribute__((section (".CCM_RAM")));
const uint8_t modbus_offset_len[kModbusType_End] = {0, MB_TCP_OFFSET};
static void bsp_modbus_task_poll(uint32_t base_time, ModbusItem * const item, ModbusTask *cur_task, GateItem *gate);

void bsp_modbus_poll(ModbusItem * const item, bool is_enable_call)
{
    uint8_t  fun_code;
	uint16_t tmp, pos, addr, reg_num;
	uint32_t i, data, tick_diff;
	bool cond[kModbusType_End];
    GateItem *gate;
    ModbusTask *cur_task = NULL;
    BspMdExCode err = kBspMdEx_InvalidFunc;
    
	if((item != NULL) && (item->ctrl_rx_int_call != NULL))
    {
        if(item->task_num > 0)
        {
            cur_task = &item->modbus_task[item->cur_task_idx];
        }
        //判断超时时关闭接收中断，防止刚判断完超时后，马上收到数据，导致数据丢失
        item->ctrl_rx_int_call(item->comm_dev, false);
        tick_diff = kit_time_get_interval_by_now(item->tick);
        if(tick_diff >= 20)
        {		      	
            pos = item->buf_pos;
            item->buf_pos = 0;
            item->tick = kit_time_get_tick();
            item->ctrl_rx_int_call(item->comm_dev, true);
            tmp = modbus_offset_len[item->type];
            addr = item->buf[MB_DEV_ADDR_POS + tmp];
            //当为主设备时接收任何addr设备数据，从设备时需要判断地址
            if (((item->is_master == true) || ((item->is_master == false) && ((item->addr == addr) || (addr == BRROCAST_ADDR)))) 
             && (pos > MB_MIN_LEN))
            {
                if(addr < MODBUS_MAX_DEV)
                {
                    item->time_out[addr] = 0;
                }
                //modbus RTU校验CRC
                cond[kModbusType_RTU] = (kit_check_crc16(item->buf, pos) == 0);
                cond[kModbusType_TCP] = (pos == (READ_BT_INT16U_BY_CONST_POS(item->buf, MB_TCP_DATA_LEN_POS) + 6));
                if(cond[item->type] == true)
                {
                    //做从机，起始地址直接从数据中获取
                    //做主机，主动发送数据时会记录起始地址，用于接收从设备数据时使用
                    addr = (item->is_master == true) ? addr = item->start_addr : READ_BT_INT16U_BY_CONST_POS(item->buf, MB_REG_ADDR_POS + tmp);
                    fun_code = item->buf[MB_FUN_CODE_POS + tmp];
                    if((item->fun_call_array[fun_code] != NULL) || (item->task_num != 0))
                    {
                        gate = item->gate;
                        reg_num = READ_BT_INT16U_BY_CONST_POS(item->buf, MB_REG_NUM_POS + tmp);
                        switch(fun_code)
                        {
                            case kModbusFun_0x01:
                            case kModbusFun_0x02:
                            case kModbusFun_0x03:
                            case kModbusFun_0x04:
                                //对于接收RTC数据长度 = 报文总长度-（地址 功能码 CRC校验），TCP数据长度 = 报文总长度-（MBAP报文头 功能码）
                                pos = (item->type == kModbusType_RTU)? (pos - 1 - 1 - 2 - 1) : (pos - 7 - 1);
                                if(item->task_num == 0)
                                {
                                    //对于Master addr为请求起始地址  reg_num 无意义           buf为数据                pos为buf长度
                                    //对于Slaver addr为请求起始地址  reg_num 线圈/寄存器数量  buf为要写入回复数据地址  pos无意义，更具reg_num填数据
                                    //MB_DATA_POS + tmp + 1，+1为字节数
                                    err = item->fun_call_array[fun_code](addr, reg_num, &item->buf[MB_DATA_POS + tmp + 1], &pos);
                                    //数据长度
                                    pos = (fun_code < kModbusFun_0x03) ? ((reg_num  + 7) >> 3) : (reg_num << 1);
                                    item->buf[MB_DATA_POS + tmp] = pos;
                                    pos++; //增加字节数
                                }
                                //通过建模方式进行modbus通信
                                else if(cur_task != NULL)
                                {
                                    cur_task->tick += 900;
                                    //读读数据
                                    if(cur_task->head->fun_code < 3)
                                    {
                                        tmp = (pos == 1) ? ((uint16_t)item->buf[MB_DATA_POS + tmp + 1] << 8) : READ_BT_INT16U_BY_CONST_POS(item->buf, MB_DATA_POS + tmp + 1);
                                        item->gate->read_buf[cur_task->buf_addr] = tmp;                                    
                                    } 
                                    else if(cur_task->head->fun_code < 5)
                                    {
                                        item->gate->read_buf[cur_task->buf_addr] = tmp;
                                        bsp_gate_push_read_data(item->gate, cur_task->buf_addr, &item->buf[MB_DATA_POS + tmp + 1], pos);
                                    }
                                    //读写数据
                                    else
                                    {
                                        if(fun_code < kModbusFun_0x03)
                                        {
                                            //一个task最多限制32个线圈，每个线圈在写buf中占2字节
                                            data = kit_bt_read_buf(&item->buf[MB_DATA_POS + tmp + 1], 0, pos);
                                            for(i = 0; i < cur_task->head->reg_num; i++)
                                            {
                                                pos = (KIT_GET_BIT_32(data, i) != 0) << 8;
                                                bsp_gate_push_write_data(gate, cur_task->buf_addr + i, (uint8_t *)&pos, 2);
                                            }
                                        }
                                        else
                                        {
                                            bsp_gate_push_write_data(gate, cur_task->buf_addr, &item->buf[MB_DATA_POS + tmp + 1], pos);
                                        }
                                    }
                                }
                                break;
                            case kModbusFun_0x05:
                            case kModbusFun_0x06:
                                if(item->task_num == 0)
                                {
                                    //对于Master addr为请求起始地址  reg_num 输出值  buf无意义 pos 无意义
                                    //对于Slaver addr为请求起始地址  reg_num 输出值  buf无意义 pos 无意义
                                    err = item->fun_call_array[fun_code](addr, reg_num, &item->buf[MB_REG_NUM_POS + tmp], &pos);
                                    pos = 4;//起始地址+输出数量
                                }
								//通过建模方式进行modbus通信
                                else if(cur_task != NULL)
                                {
                                    cur_task->tick += 900;
                                    pos = modbus_offset_len[item->type] + 4;
                                    data = READ_LT_INT16U_BY_CONST_POS(item->buf, pos);
                                    if(item->gate_write_data_absolute_addr == addr)
                                    {
                                        if(fun_code == kModbusFun_0x05)
                                        {
                                            data = (data == 0xFF) ? 0x0100 : 0;
                                        }
                                        if(data == item->gate_write_data_value)
                                        {
                                            gate->write_buf[item->gate_write_data_addr] = data;
                                            item->gate_write_data_absolute_addr = MODBUS_GATE_FREE_ADDR;
                                        }
                                    }
                                }
                                break;
                            case kModbusFun_0x0F:
                            case kModbusFun_0x10:
                                //对于Master addr为请求起始地址  reg_num 线圈/寄存器数量    buf无意义    pos无意义
                                //对于Slaver addr为请求起始地址  reg_num 线圈/寄存器数量    buf寄存器值  pos为buf长度
                                tmp += 7; //buf 地址7为要写数据
                                err = item->fun_call_array[fun_code](addr, reg_num, &item->buf[tmp], &pos);
                                pos = 4;//起始地址+输出数量
                                break;
                            default:
                                break;
                        }
                    }
                    else
                    {
                        err = kBspMdEx_InvalidFunc;
                    }
                    
                    //做主机时不回复从机报文
                    if(item->is_master == false)
                    {
                        if(err == kBspMdEx_None)
                        {
                            bsp_modbus_pos_send(item, pos);
                            
                        }
                        else
                        {
                            bsp_modbus_neg_send(item, err);
                        }
                    }
                }
            }            
            
            if(cur_task != NULL)
            {
                bsp_modbus_task_poll(tick_diff, item, cur_task, item->gate);
            }
            
            if((item->master_call != NULL) && (is_enable_call == true))
            {
                item->master_call(item, tick_diff);
            }
            
            for(i = 0; i < MODBUS_MAX_DEV; i++)
            {
                if(item->time_out[i] < 60000)
                {
                    item->time_out[i] += tick_diff;
                }
            }
        }
        else
        {
            item->ctrl_rx_int_call(item->comm_dev, true);
        }
    }
}


uint16_t bsp_modbus_get_time_out(ModbusItem * const item, uint8_t addr)
{
    uint16_t tmp = 65000;
    if(addr < MODBUS_MAX_DEV)
    {
        tmp = item->time_out[addr];
    }
    return tmp;
}

static void bsp_modbus_task_poll(uint32_t base_time, ModbusItem * const item, ModbusTask *cur_task, GateItem *gate)
{
    uint8_t fun_code;
    uint16_t tmp, data;
    int32_t addr;
    uint32_t cur_idx;
    ModbusHead *head;

    cur_task->tick += base_time;
    if(cur_task->tick >= cur_task->head->time_out + 1000)
    {
        cur_task->tick = 0;
        if(item->task_cashe != 0xFF)
        {
            cur_idx = item->task_cashe;
            item->task_cashe = 0xFF;
        }
        else
        {
            //查找下一个读指令，当轮询完一轮后首个即默认为读指令，故第一个task必须为读指令
            cur_idx = item->cur_task_idx;
            if(item->is_read_write_data == true)
            {
                if(++cur_idx >= item->task_num)
                {
                    cur_idx = 0;
                }
            }
            else
            {
                while(true)
                {
                    if(++cur_idx >= item->task_num)
                    {
                        cur_idx = 0;
                    }
                    if(item->modbus_task[cur_idx].head->fun_code < 0x05)
                    {
                        break;
                    }
                }
            }
        }

        addr = item->gate_write_data_absolute_addr;
        if((addr != MODBUS_GATE_FREE_ADDR) && (cur_task->head->slave_addr == item->gate_write_dev_addr))
        {
            //每一次请求写数据gate 重试次数+1
            if(item->gate_write_try_cnt++ >= 3)
            {
                item->gate_write_data_absolute_addr = MODBUS_GATE_FREE_ADDR;
            }
            else
            {
                head = item->gate_write_data_head;
                tmp = item->gate_write_data_value;
                if(head->fun_code == 0x05)
                {
                    data = (tmp != 0)? 0xFF00 : 0;
                }
                else
                {
                    data = tmp >> 8;
                    data |= tmp << 8;
                }
                bsp_modbus_master_single_write(item, head->slave_addr, head->fun_code, addr, data);
                item->task_cashe = cur_idx;
                return;
            }             
        }

        item->cur_task_idx = cur_idx;
        head = item->modbus_task[cur_idx].head;
        
        fun_code = head->fun_code;
        if(fun_code > 4)
        {
            fun_code = head->time_out;
        }
        //head->time_out 为0时为主动上送
        if(head->time_out != 0)
        {
            bsp_modbus_master_read(item, head->slave_addr, fun_code, head->reg_addr, head->reg_num);
        }
    }
}


void bsp_modbus_pos_send(ModbusItem * const item, uint16_t len)
{
    uint16_t mb_crc;
	uint32_t cnt;
    
    if((item != NULL) && (item->send_call != NULL)
     &&(item->buf[MB_DEV_ADDR_POS + modbus_offset_len[item->type]] != BRROCAST_ADDR))
    {
        cnt = len + 1 + 1;//addr and code
        if(item->type == kModbusType_RTU)
        {
            mb_crc = kit_check_crc16(item->buf, cnt);
            WRITE_LT_INT16U(item->buf, cnt, mb_crc);
        }
        else
        {
            WRITE_BT_INT16U_BY_CONST_POS(item->buf, MB_TCP_DATA_LEN_POS, cnt)
        }
        item->send_call(item->comm_dev, item->buf, cnt + modbus_offset_len[item->type]);
    }
}

void bsp_modbus_neg_send(ModbusItem * const item, BspMdExCode ex_code)
{
    uint16_t mb_crc;
	uint32_t cnt;
	if((item != NULL) && (item->send_call != NULL)
     &&item->buf[MB_DEV_ADDR_POS + modbus_offset_len[item->type]] != BRROCAST_ADDR)
	{
		cnt = 3;
		item->buf[1 + modbus_offset_len[item->type]] |= 0x80;
		item->buf[2 + modbus_offset_len[item->type]] = ex_code;
		
		if(item->type == kModbusType_RTU)
		{
			mb_crc = kit_check_crc16(item->buf, cnt);
			WRITE_LT_INT16U(item->buf, cnt, mb_crc);
		}
        else
        {
            item->buf[5] = 3;
        }

		item->send_call(item->comm_dev, item->buf, cnt + modbus_offset_len[item->type]);
	}
}

void bsp_modbus_push_data(ModbusItem * const item, uint8_t *buf, uint16_t len)
{
    uint32_t i;

    if((item != NULL) && (item->buf_pos + len < item->buf_size))
    {
        for (i = 0; i < len; i++)
        {
            item->buf[item->buf_pos++] = buf[i];
        }

        if(item->type == kModbusType_RTU)
        {
            item->tick = kit_time_get_tick();
        }
        else
        {
            item->tick = 0;
			//tcp使用同步接口时，不用poll
            //bsp_modbus_poll(item);
        }
    }
}

void bsp_modbus_set_slaver_addr(ModbusItem * const item, uint8_t addr)
{
    if(item != NULL)
    {
        item->addr = addr;
    }
}

uint16_t bsp_modbus_fill_mbap_head(ModbusItem * const item, uint8_t dev_addr, uint16_t len)
{
    uint32_t cnt = 0;
    if(item->type == kModbusType_TCP)
    {
        //事务元标识符：服务器从接收的请求中复制 客户端每次++
        item->tcp_transaction++;
        WRITE_BT_INT16U(item->buf, cnt, item->tcp_transaction);
        //协议标识符：0=MODBUS 协议
        WRITE_BT_INT16U(item->buf, cnt, 0);
        //长度：单元标识符1B + 功能码1B + 数据4B
        WRITE_BT_INT16U(item->buf, cnt, len);
    }
    //单元标识符：从设备地址 RTU TCP都需要填地址
    WRITE_BT_INT8U(item->buf, cnt, dev_addr);
    
    return cnt;
}

//start_addr 用于做主站时记录请求的地址  支持0x01 0x02 0x03 0x04
void bsp_modbus_master_read(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint16_t start_addr, uint16_t reg_num)
{
	uint16_t mb_crc;
	uint32_t cnt;
	
    if((item != NULL) && (item->send_call != NULL))
    {
        item->buf_pos = 0;
        item->is_master = true;
        item->start_addr = start_addr;
        //6 = 单元标识符1Byte + 功能码1Byte + 起始地址2Byte + 寄存器数2Byte
        cnt = bsp_modbus_fill_mbap_head(item, dev_addr, 6);

        item->buf[cnt++] = fun_code;
        WRITE_BT_INT16U(item->buf, cnt, start_addr);
        WRITE_BT_INT16U(item->buf, cnt, reg_num);
        if(item->type != kModbusType_TCP)
        {
            mb_crc = kit_check_crc16(item->buf, cnt);
            WRITE_LT_INT16U(item->buf, cnt, mb_crc);
        }
        item->send_call(item->comm_dev, item->buf, cnt);
    }
}

//做从站时主动发送数据  支持0x01 0x02 0x03 0x04
void bsp_modbus_slaver_send_read(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint8_t *buf, uint16_t len)
{
	uint16_t mb_crc;
	uint32_t cnt;
	
    if((item != NULL) && (item->send_call != NULL))
    {
        item->buf_pos = 0;
        cnt = bsp_modbus_fill_mbap_head(item, dev_addr, 1 + 1 + 1 + len);
        item->buf[cnt++] = fun_code;

        WRITE_BT_INT8U(item->buf, cnt, len);
        
        kit_copy_buf(&item->buf[cnt], buf, len);
        cnt += len;
        if(item->type != kModbusType_TCP)
        {
            mb_crc = kit_check_crc16(item->buf, cnt);
            WRITE_LT_INT16U(item->buf, cnt, mb_crc);
        }
        item->send_call(item->comm_dev, item->buf, cnt);
    }
}

//支持 0x05 0x06
void bsp_modbus_master_single_write(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint16_t start_addr, uint16_t value)
{
	bsp_modbus_master_read(item, dev_addr, fun_code, start_addr, value);
}

//支持 0x0F 0x10
void bsp_modbus_master_series_write(ModbusItem * const item, uint8_t dev_addr, uint8_t fun_code, uint16_t start_addr, uint16_t reg_num, uint8_t *buf)
{
	uint16_t mb_crc, len;
	uint32_t cnt = 0;
	
    if((item != NULL) && (item->send_call != NULL))
    {
        item->buf_pos = 0;
        item->is_master = true;
        item->start_addr = start_addr;
        
        //数据计算字节数
        if(fun_code == 0x10)
        {
            len = reg_num << 1;
        }
        else
        {
            len = reg_num / 8;
            len = ((reg_num % 8) == 0) ? len : (len + 1);
        }
        cnt = bsp_modbus_fill_mbap_head(item, dev_addr, 2 + 4 + 1 + len);

        item->buf[cnt++] = fun_code;
        WRITE_BT_INT16U(item->buf, cnt, start_addr);
        WRITE_BT_INT16U(item->buf, cnt, reg_num);
        
        WRITE_BT_INT8U(item->buf, cnt, len);
        kit_copy_buf(&item->buf[cnt], buf, len);
        cnt += len;
        if(item->type != kModbusType_TCP)
        {
            mb_crc = kit_check_crc16(item->buf, cnt);
            WRITE_LT_INT16U(item->buf, cnt, mb_crc);
        }
        item->send_call(item->comm_dev, item->buf, cnt);
    }
}

void bsp_modbus_link_list(ModbusItem * const item)
{
    ModbusItem ** cur_item = &modbus_comm_data.head_item;
    while(*cur_item != NULL)
    {
        if(*cur_item == item)
        {
            return;
        }
        else
        {
            cur_item = &(*cur_item)->next_item;
        }
    }
    *cur_item = item;
}

void bsp_modbus_register_master_fun(ModbusItem * const item, MasterCall master_call)
{
    if((item != NULL) && (master_call != NULL))
    {
        item->master_call = master_call;
        bsp_modbus_link_list(item);
    }
}


void register_modbus_fun(ModbusItem * const item, ModbusFun fun_code, ModbusFunCall register_call)
{
    if((item != NULL) && (fun_code < kModbusFun_End))
    {
        item->fun_call_array[fun_code] = register_call;

        bsp_modbus_link_list(item);
    }
}

//为了方便统一处理
//05写单个线圈也占用2个字节，ON为1 OFF为0，发送时将数据转换ON为0xFF00 OFF为0
//
bool bsp_modbus_register_task(ModbusItem * const item, ModbusHead *head)
{
    uint16_t len;
    bool res = false;
    ModbusTask *task;
    if((item != NULL) && (item->gate != NULL) && (modbus_comm_data.max_task_num < MODBUS_MAX_TASK))
    {
        res = true;
        item->is_master = true;
        if(item->modbus_task == NULL)
        {
            item->gate_write_data_end_addr = item->gate_write_data_start_addr = modbus_comm_data.gate_write_data_cnt;
            item->modbus_task = &modbus_comm_data.modbus_task_buf[modbus_comm_data.max_task_num];
        }
        modbus_comm_data.max_task_num++;
        
        task = &item->modbus_task[item->task_num++];
        
        len = head->reg_num;
        if(head->fun_code < 3)
        {
            len = (len + 15) / 16;
            task->buf_addr = bsp_gate_get_read_buf_addr(item->gate, len);
        }
        else if(head->fun_code < 5)
        {
            task->buf_addr = bsp_gate_get_read_buf_addr(item->gate, len);
        }
        else
        {
            modbus_comm_data.gate_write_data_cnt += len;
            item->gate_write_data_end_addr += len;
            task->buf_addr = bsp_gate_get_write_buf_addr(item->gate, len);
        }
        
        task->tick = 0;
        task->head = head;
        bsp_modbus_link_list(item);
    }
    return res;
}
