
#ifndef __MODBUS_TCP_H__
#define	__MODBUS_TCP_H__

#include "config.h"

/*
	数据帧
	__________________________________________________
	|                 Modbus ADU                     |
	|------------------------------------------------|
	|                MBAP             |        PDU   |
	|---------------------------------|--------------|
	|  事务元标识符 | 协议标识符 | 长度 | 单元标识符  |  功能码    | 数据    |
	|————————————————————————————————————————————————|


事务元标识符（2个字节）：用于事务处理配对。在响应中，MODBUS服务器复制请求的事务处理标识符。
	这里在以太网传输中存在一个问题，就是先发后至，我们可以利用这个事务处理标识符做一个TCP序列号，来防止这种情况所造成的数据收发错乱。
	（这里我们先不讨论这种情况，这个事务处理标识符我们统一使用0x00，0x01）
协议标识符（2个字节）：MODBUS 协议标识符为0x00，0x00
长度（2个字节）：长度域是下一个域的字节数，包括单元标识符和数据域。
单元标识符（1个字节）：该设备的编号。（可以使用控制器的IP地址标识）。
	在MODBUS MODBUS+串行链路子网中对设备进行寻址时，这个域是标识设备地址。在这种情况下，“单元标识符”携带一个远端设备的MODBUS从站地址：
	如果MODBUS服务器连接到MODBUS+或MODBUS串行链路子网，并通过一个桥或网关配置地址这个服务器，MODBUS单元标识符对识别连接到网桥或网关后的子网的从站设备是必需的。
	目的IP地址识别了网桥本身的地址，而网桥则使用MODBUS单元标识符将请求转交给正确的从站设备。
	分配串行链路上MODBUS从站设备地址为1～247（10进制），地址0作为广播地址。
	对 TCP/IP 来说，利用IP地址寻址MODBUS服务器；因此，MODBUS单元标识符是无用的。必需使用值0xFF。
	当对直接连接到TCP/IP网络上的MODBUS服务器寻址时，建议不要在“单元标识符”域使用有效的MODBUS从站地址。
	在一个自动系统中重新分配IP地址的情况下，并且如果以前分配给MODBUS服务器的IP地址又被指配给网关，使用一个有效的从站地址可能会由于网关的路由不畅而引起麻烦。
	使用无效的从站地址，网关仅是简单地废弃MODBUD PDU，而不会有任何问题。建议：在采用0xFF作为“单元标识符”的无效值。
	注：0也可以用作与MODBUS/TCP设备直接通信。

MODBUS 请求 （PDU）是 标准 MODBUS RTU 去掉CRC校验 的部分


*/

//------------------------------------------------------------------

#define LEN_MBAP			7
#define	MAX_RD_REG_NUMS		125
#define	MAX_WR_REG_NUMS		123

#define	MAX_MBDAT			MAX_RD_REG_NUMS

#define	MAX_LEN_PDU			(1+MAX_MBDAT*2)

#define MAX_RD_BIT_NUMS		(MAX_RD_REG_NUMS*16)

#define	MAX_WR_BIT_NUMS		(MAX_WR_REG_NUMS*16)

#define LEN_ERR_ANSWER		(LEN_MBAP + 2)
//------------------------------------------------------------------

typedef union
{
	u8 datbuff[LEN_MBAP+MAX_LEN_PDU];	// dat buff 方式

	struct
	{
		// MBAP
		u8 mbap[LEN_MBAP];
		// MODBUS PDU
		u8 pdu[MAX_LEN_PDU];		// 数据

	} __attribute__ ((packed)) mbappdu;			// 通用方式

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码
		u16 data[MAX_MBDAT];	// 数据

	} __attribute__ ((packed)) normal;			// 通用方式

	// 读线圈(离散量输出) 0x01     读离散量输入 0x02
	/*
		在一个远程设备中，使用该功能码读取线圈(输入)的1 至2000 连续状态。请求PDU 详细说明了起始地址，即指定的第一个线圈(输入)地址和线圈(输入)编号。
		寻址线圈(输入)从零开始，因此寻址线圈(输入)1-16 为0-15。
		根据数据域的每个比特将响应报文中的线圈(输入)分成为一个位。指示状态为1= ON 和0= OFF。
		第一个数据字节的LSB（最低有效位）包括在询问中寻址的线圈(输入)。其它线圈(输入)依次类推，一直到这个字节的高位端为止，并在后续字节中从低位到高位的顺序。
		如果返回的线圈(输入)数量不是八的倍数，将用零填充最后数据字节中的剩余比特（一直到字节的高位端）。字节数量域说明了数据的完整字节数。
	*/

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码  = 0x01 或 0x02
		u16 bitBegAddr;			// 位起始地址 0x0000至0xFFFF
		u16 bitsNum;			// 位数量 1至2000（0x7D0）
	} __attribute__ ((packed)) readBitsStatusRequest;		// 读取位请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 正确应答功能码 = 请求功能码 = 0x01 或 0x02
		u8 bytes;				// 字节数。 N = 输出数量/8，如果余数不等于0，那么 N=N+1
		u8 bitsStatus[MAX_RD_BIT_NUMS/8];			// 位状态,
	} __attribute__ ((packed)) readBitsStatusAnswer;		// 读取位响应包

	// 读保持寄存器(输出寄存器) 0x03 ， 读输入寄存器 0x04
	/*
		在一个远程设备中，使用该功能码读取保持寄存器连续块的内容。请求PDU说明了起始寄存器地址和寄存器数量。从零开始寻址寄存器。因此，寻址寄存器1-16 为0-15。
		将响应报文中的寄存器数据分成每个寄存器有两字节，在每个字节中直接地调整二进制内容。
		对于每个寄存器，第一个字节包括高位比特，并且第二个字节包括低位比特。
	*/

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码 = 0x03 或 0x04
		u16 begAddr;			// 起始地址 (MSB)    0x0000至0xFFFF
		u16 regsNum;			// 寄存器数量 (MSB)   1至125（0x7D）

	} __attribute__ ((packed)) readRegisterRequest;		// 读取寄存器请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;						// 正确应答功能码 = 请求功能码 = 0x03 或 0x04
		u8 bytes;						// 字节数。 2xN N为输入寄存器的数量
		u16 regsValue[MAX_RD_REG_NUMS];	// 读取的寄存器的内容，每个寄存器内容占用两个字节，高字节在前，低字节在后 (MSB)

	} __attribute__ ((packed)) readRegisterAnswer;		// 读取寄存器应答包

	// 写单个线圈 0x05
	/*
		在一个远程设备上，使用该功能码写单个输出为ON 或OFF。
		请求数据域中的常量说明请求的ON/OFF状态。十六进制值FF 00请求输出为ON。十六进制值00 00 请求输出为OFF。其它所有值均是非法的，并且对输出不起作用。
		请求PDU说明了强制的线圈地址。从零开始寻址线圈。因此，寻址线圈1 为0。线圈值域的常量说明请求的ON/OFF 状态。
		十六进制值0XFF00请求线圈为ON。十六进制值0X0000请求线圈为OFF。其它所有值均为非法的，并且对线圈不起作用。
		正常响应是请求的应答，在写入线圈状态之后返回这个正常响应。
	*/
	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码  = 0x05
		u16 bitBegAddr;			// 起始地址 0x0000至0xFFFF
		u8 bitValue;			// 位的值 0x00 或 0xFF
	} __attribute__ ((packed)) writeBitRequest;		// 写位请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 正确应答功能码 = 请求功能码 = 0x05
		u8 bitStatus;			// 位的状态  0x00 或 0xFF
	} __attribute__ ((packed)) writeBitAnswer;		// 写位响应包

	// 写单个寄存器 0x06
	/*
		在一个远程设备中，使用该功能码写单个保持寄存器。
		请求PDU 说明了被写入寄存器的地址。从零开始寻址寄存器。因此，寻址寄存器1 为0。
		正常响应是请求的应答，在写入寄存器内容之后返回这个正常响应。
	*/
	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码  = 0x06
		u16 regBegAddr;			// 起始地址 0x0000至0xFFFF
		u16 regValue;			// 寄存器的值 0x0000 至 0xFFFF
	} __attribute__ ((packed)) writeRegisterRequest;		// 写位请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 正确应答功能码 = 请求功能码 = 0x06
		u16 regValue;			// 寄存器的值 0x0000 至 0xFFFF
	} __attribute__ ((packed)) writeRegisterAnswer;		// 写位响应包

	// 写多个线圈 0x0F
	/*
		在一个远程设备中，使用该功能码强制线圈序列中的每个线圈为ON 或OFF。请求PDU说明了强制的线圈参考。从零开始寻址线圈。因此，寻址线圈1 为0。
		请求数据域的内容说明了被请求的ON/OFF 状态。域比特位置中的逻辑“1”请求相应输出为ON。域比特位置中的逻辑“0”请求相应输出为OFF。
		正常响应返回功能码、起始地址和强制的线圈数量。
	*/
	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码  = 0x05
		u16 bitBegAddr;			// 起始位地址 0x0000至0xFFFF
		u16 bitsNum;			// 位的数量 0x0001至0x07B0（2000）
		u8 bytes;				// 字节数  N = 输出数量/8，如果余数不等于0，那么N=N+1
		u8 bitsArray[MAX_WR_BIT_NUMS/8];	// 位的值，位在字节中是LSB
	} __attribute__ ((packed)) writeBitArrayRequest;		// 写位请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 正确应答功能码 = 请求功能码 = 0x05
		u16 bitBegAddr;			// 起始位地址 0x0000至0xFFFF
		u16 bitsNum;			// 位的数量 0x0001至0x07B0（2000）
	} __attribute__ ((packed)) writeBitArrayAnswer;		// 写位响应包

	// 写多个寄存器 0x10
	/*
		在一个远程设备中，使用该功能码写连续寄存器块(1 至约120 个寄存器)。
		在请求数据域中说明了请求写入的值。每个寄存器将数据分成两字节。
		正常响应返回功能码、起始地址和被写入寄存器的数量。
	*/
	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码  = 0x10
		u16 regBegAddr;			// 寄存器起始位地址 0x0000至0xFFFF
		u16 regsNum;			// 寄存器的数量 0x0001至0x007B（123）
		u8 bytes;				// 字节数  N = 寄存器数量 * 2
		u16 regsArray[MAX_WR_REG_NUMS];	// 寄存器的值，MSB
	} __attribute__ ((packed)) writeRegArrayRequest;		// 写位请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 正确应答功能码 = 请求功能码 = 0x05
		u16 regBegAddr;			// 寄存器起始位地址 0x0000至0xFFFF
		u16 regsNum;			// 寄存器的数量 0x0001至0x007B（123）
	} __attribute__ ((packed)) writeRegArrayAnswer;		// 写位响应包

/*
	洛源驱动器特殊功能码
*/
	// 读取子参数 0x65
	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码
		u16 begAddr;			// 起始地址
		u16 subAddr;			// 子地址

	} __attribute__ ((packed)) readSubRegRequest;		// 子参数请求包

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码
		u8 bytes;				// 字节数  N = 寄存器数量 * 2
		u16 subRegsArray[MAX_RD_REG_NUMS];	// 寄存器的值，MSB

	} __attribute__ ((packed)) readSubRegAnswer;		// 子参数应答

	// 写入子参数 0x69
	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 功能码 0x69
		u16 regBegAddr;			// 寄存器起始位地址 0x0000至0xFFFF
		u16 subAddr;			// 寄存器子地址
		u8 bytes;				// 字节数  N = 寄存器数量 * 2
		u16 subRegsArray[MAX_RD_REG_NUMS];	// 寄存器的值，MSB 实际寄存器数量 N = (len - 6) / 2

	} __attribute__ ((packed)) writeSubRegRequest;		// 子参数请求

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 cmd;					// 功能码
		u16 regBegAddr;			// 寄存器起始位地址 0x0000至0xFFFF
		u16 subAddr;			// 寄存器子地址

	} __attribute__ ((packed)) writeSubRegAnswer;		// 子参数应答

	struct
	{
		// MBAP
		u16 transId;			// 事务元标识符。MODBUS请求/响应事务处理的识别码。
		u16 protId;				// 协议标识符。MODBUS 协议标识符为0x00，0x00
		u16 len;				// 帧长度。
		u8  unitId;				// 单元标识符。

		// MODBUS PDU
		u8 acmd;				// 错误应答功能码 = 请求功能码+0x80
		u8 errCode;				// 错误码

	} __attribute__ ((packed)) answerError;		// 错误应答

} ModbusTcpADU;


/*
// MODBUS　数据类型
	________________________________________________________________
	|	基本表格	|	对象类型 	|	访问类型	|		内容					|
	|———————————————————————————————————————————————————————————————|
	|	离散量输入	|	单个比特	|	只读		|	I/O系统提供这种类型数据		|
	|-----------|-----------|-----------|---------------------------|
	|	线圈		|	单个比特	|	读写		|	通过应用程序改变这种类型数据		|
	|-----------|-----------|-----------|---------------------------|
	|	输入寄存器	|	16比特字	|	只读		|	I/O系统提供这种类型数据		|
	|-----------|-----------|-----------|---------------------------|
	|	保持寄存器	|	16比特字	|	读写		|	通过应用程序改变这种类型数据		|
	|———————————————————————————————————————————————————————————————|
*/

// MODBUS 功能码
#define	READ_COIL_STATUS			0x01	// 读取线圈状态（内部比特或物理线圈）
#define	READ_INPUT_STATUS			0x02	// 读取输入状态（物理离散量输入）
#define	READ_HOLDING_REGISTER		0x03	// 读保持寄存器（内部存储器或物理输出存储器）
#define	READ_INPUT_REGISTER			0x04	// 读取输入寄存器（输入寄存器）
#define	WRITE_SINGLE_COIL			0x05	// 写单线圈（内部比特或物理线圈）
#define	WRITE_SINGLE_REGISTER		0x06	// 写单个寄存器（内部存储器或物理输出存储器）
#define	WRITE_MULTIPLE_COIL			0x0F	// 写多个线圈（内部比特或物理线圈）
#define	WRITE_MULTIPLE_REGISTER		0x10	// 写多个寄存器（内部存储器或物理输出存储器）

#define READ_FILE_RECORD			0x14	// 读文件记录（文件记录访问）
#define WRITE_FILE_RECORD			0x15	// 写文件记录（文件记录访问）
#define WRITE_MASK_REGISTER			0x16	// 屏蔽写寄存器（内部存储器或物理输出存储器）
#define RW_MULTIPLE_REGISTER		0x17	// 读/写多个寄存器（内部存储器或物理输出存储器）

#define READ_FILE_RECORD_SUBCODE	0x06	// 读文件记录（文件记录访问）
#define WRITE_FILE_RECORD_SUBCODE	0x06	// 写文件记录（文件记录访问）


// 洛源驱动器功能码
#define READ_REGISTER_SUBPARA		0x65	// 读取子参数
#define WRITE_REGISTER_SUBPARA		0x69	// 写入子参数


// MODBUS TCP 异常码
#define MODBUS_TCP_ERR01			0x01	// 非法的功能码, 服务器不了解功能码
#define MODBUS_TCP_ERR02			0x02	// 非法的数据地址, 与请求有关
#define MODBUS_TCP_ERR03			0x03	// 非法的数据值, 与请求有关
#define MODBUS_TCP_ERR04			0x04	// 服务器故障, 在执行过程中，服务器故障
#define MODBUS_TCP_ERR05			0x05	// 确认, 服务器接受服务调用，但是需要相对长的时间完成服务。因此，服务器仅返回一个服务调用接收的确认。
#define MODBUS_TCP_ERR06			0x06	// 服务器繁忙, 服务器不能接受MODBUS请求PDU。客户应用由责任决定是否和何时重发请求。
#define MODBUS_TCP_ERR0A			0x0A	// 网关故障, 网关路经是无效的。
#define MODBUS_TCP_ERR0B			0x0B	// 网关故障, 目标设备没有响应。网关生成这个异常信息。

//------------------------------------------------------------------


//------------------------------------------------------------------

#ifndef MAX_MODBUS_TCP_NODE
#define	MAX_MODBUS_TCP_NODE		0
#endif

//------------------------------------------------------------------

void InitModbusTcp(void);

void ModbusTcpServerRun(void);

// 功能: 写入多个寄存器
int ModbusTcpWriteRegs(int nidx, u16 addr, u16 * regAry, u16 nums);

u16 SwapHighLow(u16 data);

//------------------------------------------------------------------

#endif


