|
|
package m9z
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
|
|
"tgk-touch/internal/global"
|
|
|
"tgk-touch/internal/module/maincontrollerClient"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
FrameHeader = 0xEE
|
|
|
FrameFooter = 0xFF
|
|
|
CmdRead = 0x01
|
|
|
CmdWrite = 0x02
|
|
|
ResponseFlag = 0x80 // D7位掩码
|
|
|
MaxPacketSize = 256
|
|
|
)
|
|
|
|
|
|
// 读取命令结构
|
|
|
type ReadCommand struct {
|
|
|
Instruction byte
|
|
|
Index byte
|
|
|
}
|
|
|
|
|
|
// 写入命令结构
|
|
|
type WriteCommand struct {
|
|
|
Instruction byte
|
|
|
Index byte
|
|
|
Data []byte
|
|
|
IsUpdate bool
|
|
|
}
|
|
|
|
|
|
// 读取响应结构
|
|
|
type ReadResponse struct {
|
|
|
Instruction byte
|
|
|
Index byte
|
|
|
Data []byte
|
|
|
}
|
|
|
|
|
|
// 写入响应结构
|
|
|
type WriteResponse struct {
|
|
|
Instruction byte
|
|
|
Index byte
|
|
|
Success bool
|
|
|
}
|
|
|
|
|
|
// 校验和计算
|
|
|
func calCheckSum(data []byte) byte {
|
|
|
sum := byte(0x33)
|
|
|
for _, b := range data {
|
|
|
sum += b
|
|
|
}
|
|
|
return sum
|
|
|
}
|
|
|
|
|
|
// 构建读取命令帧
|
|
|
func BuildReadCommand(cmd ReadCommand) []byte {
|
|
|
frame := []byte{FrameHeader}
|
|
|
payload := []byte{cmd.Instruction, CmdRead, cmd.Index}
|
|
|
frame = append(frame, payload...)
|
|
|
frame = append(frame, calCheckSum(payload))
|
|
|
frame = append(frame, FrameFooter)
|
|
|
return frame
|
|
|
}
|
|
|
|
|
|
// 构建写入命令帧
|
|
|
func BuildWriteCommand(cmd WriteCommand) []byte {
|
|
|
frame := []byte{FrameHeader}
|
|
|
payload := []byte{cmd.Instruction, CmdWrite, cmd.Index}
|
|
|
if len(cmd.Data) > 0 && !cmd.IsUpdate {
|
|
|
payload = append(payload, byte(len(cmd.Data)))
|
|
|
}
|
|
|
payload = append(payload, cmd.Data...)
|
|
|
frame = append(frame, payload...)
|
|
|
frame = append(frame, calCheckSum(payload))
|
|
|
frame = append(frame, FrameFooter)
|
|
|
return frame
|
|
|
}
|
|
|
|
|
|
// 解析接收帧
|
|
|
func ParseFrame(data []byte) (interface{}, error) {
|
|
|
g.Log().Debugf("ParseFrame:[% X]", data)
|
|
|
s := ""
|
|
|
for i, d := range data {
|
|
|
if i == 0 {
|
|
|
|
|
|
}
|
|
|
s += fmt.Sprintf("0x%X,", d)
|
|
|
}
|
|
|
//g.Log().Debugf("\n ParseFrame:{%s} ", s)
|
|
|
// 基本长度检查
|
|
|
if len(data) < 5 {
|
|
|
return nil,
|
|
|
NewReadError("frame too short")
|
|
|
}
|
|
|
|
|
|
// 验证帧头帧尾
|
|
|
if data[0] != FrameHeader || data[len(data)-1] != FrameFooter {
|
|
|
return nil, NewReadError("invalid frame header or footer")
|
|
|
}
|
|
|
|
|
|
// 提取有效载荷和校验和
|
|
|
payload := data[1 : len(data)-2]
|
|
|
receivedCS := data[len(data)-2]
|
|
|
|
|
|
// 计算校验和
|
|
|
if calCheckSum(payload) != receivedCS {
|
|
|
return nil, NewReadError("checksum mismatch")
|
|
|
}
|
|
|
|
|
|
// 解析指令类型
|
|
|
if len(payload) < 2 {
|
|
|
return nil, NewReadError("invalid payload length")
|
|
|
}
|
|
|
|
|
|
instruction := payload[0]
|
|
|
cmdType := payload[1]
|
|
|
|
|
|
// 判断是否是响应帧(D7位是否置1)
|
|
|
isResponse := (instruction & ResponseFlag) != 0
|
|
|
|
|
|
switch {
|
|
|
case isResponse && cmdType == CmdRead:
|
|
|
// 特殊处理实际通断情况查询指令 (0x19) 的响应
|
|
|
// 该指令返回的数据结构较为特殊,可能包含多个字段,需要专门的解析逻辑
|
|
|
// 或者复用 parseReadResponse,但在解析 Data 时进行特定处理
|
|
|
return parseReadResponse(payload)
|
|
|
case isResponse && cmdType == CmdWrite:
|
|
|
return parseWriteResponse(payload)
|
|
|
default:
|
|
|
return nil, gerror.New("unsupported command type")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解析读取响应
|
|
|
func parseReadResponse(payload []byte) (*ReadResponse, error) {
|
|
|
if len(payload) < 4 {
|
|
|
return nil, gerror.New("invalid read response format")
|
|
|
}
|
|
|
|
|
|
index := payload[2]
|
|
|
dataLength := payload[3]
|
|
|
|
|
|
if len(payload) < 4+int(dataLength) {
|
|
|
return nil, gerror.New("data length mismatch")
|
|
|
}
|
|
|
|
|
|
return &ReadResponse{
|
|
|
Instruction: payload[0],
|
|
|
Index: index,
|
|
|
Data: payload[4 : 4+dataLength],
|
|
|
}, nil
|
|
|
}
|
|
|
func parseReadBlockResponse(payload []byte) (*ReadResponse, error) {
|
|
|
if len(payload) < 4 {
|
|
|
return nil, gerror.New("invalid read response format")
|
|
|
}
|
|
|
|
|
|
index := payload[2]
|
|
|
dataLength := payload[3:5]
|
|
|
le := BytesToUint16LE(dataLength)
|
|
|
if len(payload) < 4+int(le) {
|
|
|
return nil, gerror.New("data length mismatch")
|
|
|
}
|
|
|
|
|
|
return &ReadResponse{
|
|
|
Instruction: payload[0],
|
|
|
Index: index,
|
|
|
Data: payload[5 : 5+le],
|
|
|
}, nil
|
|
|
}
|
|
|
|
|
|
// 解析写入响应
|
|
|
func parseWriteResponse(payload []byte) (*WriteResponse, error) {
|
|
|
if len(payload) < 5 {
|
|
|
return nil, gerror.New("invalid write response format")
|
|
|
}
|
|
|
|
|
|
index := payload[2]
|
|
|
dataLength := payload[3]
|
|
|
|
|
|
if dataLength != 1 {
|
|
|
return nil, gerror.New("invalid data length in write response")
|
|
|
}
|
|
|
|
|
|
success := payload[4] == 0x01
|
|
|
return &WriteResponse{
|
|
|
Instruction: payload[0],
|
|
|
Index: index,
|
|
|
Success: success,
|
|
|
}, nil
|
|
|
}
|
|
|
|
|
|
// 辅助函数:将数据转换为小端格式的uint16
|
|
|
func BytesToUint16LE(b []byte) uint16 {
|
|
|
if len(b) < 2 {
|
|
|
return 0
|
|
|
}
|
|
|
return uint16(b[0]) | uint16(b[1])<<8
|
|
|
}
|
|
|
|
|
|
// 辅助函数:将uint16转换为小端格式的字节切片
|
|
|
func Uint16ToBytesLE(n uint16) []byte {
|
|
|
return []byte{byte(n), byte(n >> 8)}
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
func pushCommand(cmd []byte) ([]byte, error) {
|
|
|
push.Lock()
|
|
|
defer push.Unlock()
|
|
|
fmt.Printf("Push Command :% X\n", cmd)
|
|
|
conn, err := net.Dial("tcp", "10.10.100.254:8899")
|
|
|
if err != nil {
|
|
|
g.Log().Infof("%+v\n", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
defer conn.Close()
|
|
|
_, err = conn.Write(cmd)
|
|
|
if err != nil {
|
|
|
g.Log().Infof("%+v\n", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
buf := make([]byte, 128)
|
|
|
n, err := conn.Read(buf)
|
|
|
if err != nil {
|
|
|
g.Log().Infof("%+v\n", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
return buf[:n], nil
|
|
|
}
|
|
|
*/
|
|
|
func pushCommand(deviceId string, cmd []byte, detail string) ([]byte, error) {
|
|
|
|
|
|
var result []byte
|
|
|
g.Log().Debugf("push cmd:[% X] ", cmd)
|
|
|
|
|
|
err := maincontrollerClient.DeviceCallByDeviceID(
|
|
|
deviceId,
|
|
|
"/lampControl",
|
|
|
"", cmd, &result, func(deviceID, method string, params, result interface{}) (deviceType, cmdDetail, cmdDisplay, resultDisplay string) {
|
|
|
deviceType = "tgk"
|
|
|
cmdDetail = detail
|
|
|
return
|
|
|
})
|
|
|
if err != nil {
|
|
|
return nil, gerror.Wrapf(err, "单灯控制命令 deviceID={%v} cmd={% X} 下发失败", deviceId, cmd)
|
|
|
}
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
func ReadCmd(deviceId string, cmd M9zCtrl, detail string, idx ...uint) (*ReadResponse, error) {
|
|
|
var idxEnd byte
|
|
|
idxEnd = 0x00
|
|
|
if len(idx) > 0 {
|
|
|
idxEnd = byte(idx[0])
|
|
|
}
|
|
|
readCmd := ReadCommand{Instruction: byte(cmd), Index: idxEnd}
|
|
|
readFrame := BuildReadCommand(readCmd)
|
|
|
cmdRespData, err := pushCommand(deviceId, readFrame, detail)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
frame, err := ParseFrame(cmdRespData)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
return frame.(*ReadResponse), err
|
|
|
}
|
|
|
|
|
|
// 定义专属的标记常量(根据业务场景选择合适的值,这里用0xff对应原逻辑的-1) 标记是升级
|
|
|
const UpdateMarker byte = 0xff // 特殊标记值,替代原逻辑中的-1
|
|
|
|
|
|
func WriteCmd(deviceId string, cmd M9zCtrl, data []byte, detail string, idx ...uint8) (*WriteResponse, error) {
|
|
|
isUpdate := false
|
|
|
// 4. 构建写入命令
|
|
|
wIdx := byte(0x00)
|
|
|
if len(idx) > 0 {
|
|
|
wIdx = idx[0]
|
|
|
// 准确判断是否是特殊标记值(类型完全匹配,无隐式转换)
|
|
|
if wIdx == UpdateMarker {
|
|
|
isUpdate = true
|
|
|
// 这里添加标记值的处理逻辑(根据你的业务需求定制)
|
|
|
// 示例:重置为默认值、执行特定业务逻辑等
|
|
|
wIdx = 0x00 // 示例操作:将标记值重置为默认值
|
|
|
}
|
|
|
}
|
|
|
writeCmd := WriteCommand{
|
|
|
Instruction: byte(cmd),
|
|
|
Index: wIdx,
|
|
|
Data: data,
|
|
|
IsUpdate: isUpdate,
|
|
|
}
|
|
|
writeFrame := BuildWriteCommand(writeCmd)
|
|
|
|
|
|
cmdRespData, err := pushCommand(deviceId, writeFrame, detail)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
resp, err := ParseFrame(cmdRespData)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
return resp.(*WriteResponse), nil
|
|
|
}
|
|
|
func percentToHex(percent int) byte {
|
|
|
if percent < 0 {
|
|
|
return 0x00 // 低于0%视为0%
|
|
|
}
|
|
|
if percent > 100 {
|
|
|
return 0x64 // 超过100%视为100%
|
|
|
}
|
|
|
return byte(percent) // 直接转换
|
|
|
}
|
|
|
func hexToPercent(hexValue byte) int {
|
|
|
if hexValue > 0x64 {
|
|
|
err := fmt.Errorf("非法十六进制值: 0x%02X(超过0x64)", hexValue)
|
|
|
g.Log().Infof("%+v", err)
|
|
|
return 0
|
|
|
}
|
|
|
return int(hexValue)
|
|
|
}
|