Files
pig-farm-controller/internal/infra/utils/command_generater/modbus_rtu.go
2025-09-30 00:07:16 +08:00

183 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}