183 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package command_generater
 | ||
| 
 | ||
| import (
 | ||
| 	"encoding/binary"
 | ||
| 	"fmt"
 | ||
| )
 | ||
| 
 | ||
| // ModbusFunctionCode 定义Modbus功能码的枚举类型
 | ||
| type ModbusFunctionCode byte
 | ||
| 
 | ||
| // 定义常用的Modbus功能码常量及其应用场景
 | ||
| const (
 | ||
| 	// ReadCoils 读取线圈状态 (0x01)
 | ||
| 	// 场景: 用于读取数字量输出(DO)或内部标志位的当前状态,这些状态通常是开关量。
 | ||
| 	ReadCoils ModbusFunctionCode = 0x01
 | ||
| 	// ReadDiscreteInputs 读取离散输入状态 (0x02)
 | ||
| 	// 场景: 用于读取数字量输入(DI)的当前状态,这些状态通常是外部传感器的开关量信号。
 | ||
| 	ReadDiscreteInputs ModbusFunctionCode = 0x02
 | ||
| 	// ReadHoldingRegisters 读取保持寄存器 (0x03)
 | ||
| 	// 场景: 用于读取设备内部可读写的参数或数据,例如温度设定值、电机速度等模拟量或配置数据。
 | ||
| 	ReadHoldingRegisters ModbusFunctionCode = 0x03
 | ||
| 	// ReadInputRegisters 读取输入寄存器 (0x04)
 | ||
| 	// 场景: 用于读取设备的模拟量输入(AI)数据,这些数据通常是只读的,例如当前温度、压力、电压等实时测量值。
 | ||
| 	ReadInputRegisters ModbusFunctionCode = 0x04
 | ||
| 	// WriteSingleCoil 写入单个线圈 (0x05)
 | ||
| 	// 场景: 用于控制单个数字量输出(DO),例如打开或关闭一个继电器、指示灯等。
 | ||
| 	WriteSingleCoil ModbusFunctionCode = 0x05
 | ||
| 	// WriteSingleRegister 写入单个保持寄存器 (0x06)
 | ||
| 	// 场景: 用于修改设备内部的单个可写参数,例如设置一个温度控制器的目标温度、调整一个阀门的开度等。
 | ||
| 	WriteSingleRegister ModbusFunctionCode = 0x06
 | ||
| 	// WriteMultipleCoils 写入多个线圈 (0x0F)
 | ||
| 	// 场景: 用于批量控制多个数字量输出(DO),例如同时打开或关闭一组继电器。
 | ||
| 	WriteMultipleCoils ModbusFunctionCode = 0x0F
 | ||
| 	// WriteMultipleRegisters 写入多个保持寄存器 (0x10)
 | ||
| 	// 场景: 用于批量修改设备内部的多个可写参数,例如一次性更新多个配置参数或模拟量输出值。
 | ||
| 	WriteMultipleRegisters ModbusFunctionCode = 0x10
 | ||
| )
 | ||
| 
 | ||
| // GenerateModbusRTUReadCommand 生成Modbus RTU读取指令
 | ||
| // 该函数主要用于生成Modbus RTU的读取类指令 (如 0x01, 0x02, 0x03, 0x04)。
 | ||
| // 其PDU结构为: 功能码 + 起始地址 + 数量。
 | ||
| //
 | ||
| // 参数:
 | ||
| //
 | ||
| //	slaveAddress: 从站地址 (1-247)。
 | ||
| //	functionCode: 功能码,使用 ModbusFunctionCode 枚举类型 (例如: ReadHoldingRegisters)。
 | ||
| //	              此函数仅支持读取类功能码。
 | ||
| //	startAddress: 寄存器/线圈的起始地址 (0-65535)。
 | ||
| //	quantity: 要读取的寄存器/线圈数量 (1-125)。
 | ||
| //
 | ||
| // 返回:
 | ||
| //
 | ||
| //	[]byte: 完整的Modbus RTU指令字节切片。
 | ||
| //	error: 如果参数无效或生成过程中出现错误,则返回错误信息。
 | ||
| func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode ModbusFunctionCode, startAddress uint16, quantity uint16) ([]byte, error) {
 | ||
| 	// 1. 校验输入参数
 | ||
| 	if slaveAddress == 0 || slaveAddress > 247 {
 | ||
| 		return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 校验功能码是否为读取类型
 | ||
| 	switch functionCode {
 | ||
| 	case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters:
 | ||
| 		// 这些是支持的读取功能码
 | ||
| 	case WriteSingleCoil, WriteSingleRegister, WriteMultipleCoils, WriteMultipleRegisters:
 | ||
| 		return nil, fmt.Errorf("功能码 %X 是写入操作,请使用 GenerateModbusRTUWriteCoilCommand 或其他写入函数", functionCode)
 | ||
| 	default:
 | ||
| 		return nil, fmt.Errorf("不支持的功能码: %X", functionCode)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 对于读取类功能码,数量通常限制在1到125之间。
 | ||
| 	if quantity == 0 || quantity > 125 {
 | ||
| 		return nil, fmt.Errorf("功能码 %X (读取操作) 的数量无效: %d, 必须在1-125之间", functionCode, quantity)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 2. 构建PDU (协议数据单元)
 | ||
| 	// PDU结构: 功能码 (1字节) + 起始地址 (2字节) + 数量 (2字节)
 | ||
| 	pdu := make([]byte, 5)
 | ||
| 	pdu[0] = byte(functionCode) // 将枚举类型转换为byte
 | ||
| 	// Modbus协议中,地址和数量都是大端字节序 (高位在前)
 | ||
| 	binary.BigEndian.PutUint16(pdu[1:3], startAddress)
 | ||
| 	binary.BigEndian.PutUint16(pdu[3:5], quantity)
 | ||
| 
 | ||
| 	// 3. 构建ADU (应用数据单元)
 | ||
| 	// ADU结构: 从站地址 (1字节) + PDU
 | ||
| 	adu := make([]byte, 1+len(pdu))
 | ||
| 	adu[0] = slaveAddress
 | ||
| 	copy(adu[1:], pdu)
 | ||
| 
 | ||
| 	// 4. 计算CRC16校验码
 | ||
| 	crc := calculateCRC16(adu)
 | ||
| 
 | ||
| 	// 5. 组装完整的Modbus RTU指令
 | ||
| 	// 完整指令结构: ADU + CRC (2字节)
 | ||
| 	command := make([]byte, len(adu)+2)
 | ||
| 	copy(command, adu)
 | ||
| 	// Modbus RTU的CRC是低字节在前,高字节在后 (小端字节序)
 | ||
| 	binary.LittleEndian.PutUint16(command[len(adu):], crc)
 | ||
| 
 | ||
| 	return command, nil
 | ||
| }
 | ||
| 
 | ||
| // GenerateModbusRTUSwitchCommand 生成Modbus RTU写入单个线圈的指令
 | ||
| // 该函数专门用于生成 Modbus RTU 的写入单个线圈 (0x05) 指令,用于控制开关。
 | ||
| //
 | ||
| // 参数:
 | ||
| //
 | ||
| //	slaveAddress: 从站地址 (1-247)。
 | ||
| //	coilAddress: 要写入的线圈地址 (0-65535)。
 | ||
| //	onOffState: 开关状态,true 表示开启 (ON, 0xFF00),false 表示关闭 (OFF, 0x0000)。
 | ||
| //
 | ||
| // 返回:
 | ||
| //
 | ||
| //	[]byte: 完整的Modbus RTU指令字节切片。
 | ||
| //	error: 如果参数无效或生成过程中出现错误,则返回错误信息。
 | ||
| func GenerateModbusRTUSwitchCommand(slaveAddress uint8, coilAddress uint16, onOffState bool) ([]byte, error) {
 | ||
| 	// 1. 校验从站地址
 | ||
| 	if slaveAddress == 0 || slaveAddress > 247 {
 | ||
| 		return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 根据布尔值确定写入的Modbus值
 | ||
| 	var writeValue uint16
 | ||
| 	if onOffState {
 | ||
| 		writeValue = 0xFF00 // ON
 | ||
| 	} else {
 | ||
| 		writeValue = 0x0000 // OFF
 | ||
| 	}
 | ||
| 
 | ||
| 	// 2. 构建PDU (协议数据单元) for WriteSingleCoil (0x05)
 | ||
| 	// PDU结构: 功能码 (1字节) + 线圈地址 (2字节) + 写入值 (2字节)
 | ||
| 	pdu := make([]byte, 5)
 | ||
| 	pdu[0] = byte(WriteSingleCoil)
 | ||
| 	// Modbus协议中,地址和值都是大端字节序 (高位在前)
 | ||
| 	binary.BigEndian.PutUint16(pdu[1:3], coilAddress)
 | ||
| 	binary.BigEndian.PutUint16(pdu[3:5], writeValue)
 | ||
| 
 | ||
| 	// 3. 构建ADU (应用数据单元)
 | ||
| 	// ADU结构: 从站地址 (1字节) + PDU
 | ||
| 	adu := make([]byte, 1+len(pdu))
 | ||
| 	adu[0] = slaveAddress
 | ||
| 	copy(adu[1:], pdu)
 | ||
| 
 | ||
| 	// 4. 计算CRC16校验码
 | ||
| 	crc := calculateCRC16(adu)
 | ||
| 
 | ||
| 	// 5. 组装完整的Modbus RTU指令
 | ||
| 	// 完整指令结构: ADU + CRC (2字节)
 | ||
| 	command := make([]byte, len(adu)+2)
 | ||
| 	copy(command, adu)
 | ||
| 	// Modbus RTU的CRC是低字节在前,高字节在后 (小端字节序)
 | ||
| 	binary.LittleEndian.PutUint16(command[len(adu):], crc)
 | ||
| 
 | ||
| 	return command, nil
 | ||
| }
 | ||
| 
 | ||
| // calculateCRC16 计算Modbus RTU的CRC-16校验码
 | ||
| //
 | ||
| // 参数:
 | ||
| //
 | ||
| //	data: 需要计算CRC的字节切片 (通常是ADU,即从站地址+PDU)。
 | ||
| //
 | ||
| // 返回:
 | ||
| //
 | ||
| //	uint16: 16位的CRC校验码。
 | ||
| func calculateCRC16(data []byte) uint16 {
 | ||
| 	var crc uint16 = 0xFFFF      // CRC初始值
 | ||
| 	polynomial := uint16(0xA001) // Modbus RTU CRC-16多项式 (反向表示)
 | ||
| 
 | ||
| 	for _, b := range data {
 | ||
| 		crc ^= uint16(b) // 将数据字节与CRC寄存器异或
 | ||
| 		for i := 0; i < 8; i++ {
 | ||
| 			if (crc & 0x0001) != 0 { // 检查最低位是否为1
 | ||
| 				crc >>= 1         // 右移一位
 | ||
| 				crc ^= polynomial // 与多项式异或
 | ||
| 			} else {
 | ||
| 				crc >>= 1 // 否则只右移一位
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return crc
 | ||
| }
 |