2025-09-29 23:27:40 +08:00
|
|
|
|
package command_generater
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
2025-11-07 21:39:24 +08:00
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
2025-09-29 23:27:40 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 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: 如果参数无效或生成过程中出现错误,则返回错误信息。
|
2025-11-07 21:39:24 +08:00
|
|
|
|
func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode models.ModbusFunctionCode, startAddress uint16, quantity uint16) ([]byte, error) {
|
2025-09-29 23:27:40 +08:00
|
|
|
|
// 1. 校验输入参数
|
|
|
|
|
|
if slaveAddress == 0 || slaveAddress > 247 {
|
|
|
|
|
|
return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 校验功能码是否为读取类型
|
|
|
|
|
|
switch functionCode {
|
2025-11-07 21:39:24 +08:00
|
|
|
|
case models.ReadCoils, models.ReadDiscreteInputs, models.ReadHoldingRegisters, models.ReadInputRegisters:
|
2025-09-29 23:27:40 +08:00
|
|
|
|
// 这些是支持的读取功能码
|
2025-11-07 21:39:24 +08:00
|
|
|
|
case models.WriteSingleCoil, models.WriteSingleRegister, models.WriteMultipleCoils, models.WriteMultipleRegisters:
|
2025-09-29 23:27:40 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-30 00:07:16 +08:00
|
|
|
|
// GenerateModbusRTUSwitchCommand 生成Modbus RTU写入单个线圈的指令
|
2025-09-29 23:27:40 +08:00
|
|
|
|
// 该函数专门用于生成 Modbus RTU 的写入单个线圈 (0x05) 指令,用于控制开关。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 参数:
|
|
|
|
|
|
//
|
|
|
|
|
|
// slaveAddress: 从站地址 (1-247)。
|
|
|
|
|
|
// coilAddress: 要写入的线圈地址 (0-65535)。
|
|
|
|
|
|
// onOffState: 开关状态,true 表示开启 (ON, 0xFF00),false 表示关闭 (OFF, 0x0000)。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 返回:
|
|
|
|
|
|
//
|
|
|
|
|
|
// []byte: 完整的Modbus RTU指令字节切片。
|
|
|
|
|
|
// error: 如果参数无效或生成过程中出现错误,则返回错误信息。
|
2025-09-30 00:07:16 +08:00
|
|
|
|
func GenerateModbusRTUSwitchCommand(slaveAddress uint8, coilAddress uint16, onOffState bool) ([]byte, error) {
|
2025-09-29 23:27:40 +08:00
|
|
|
|
// 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)
|
2025-11-07 21:39:24 +08:00
|
|
|
|
pdu[0] = byte(models.WriteSingleCoil)
|
2025-09-29 23:27:40 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|