You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
7.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package m9z
import (
"fmt"
"github.com/towgo/towgo/errors/terror"
"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, terror.New("unsupported command type")
}
}
// 解析读取响应
func parseReadResponse(payload []byte) (*ReadResponse, error) {
if len(payload) < 4 {
return nil, terror.New("invalid read response format")
}
index := payload[2]
dataLength := payload[3]
if len(payload) < 4+int(dataLength) {
return nil, terror.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, terror.New("invalid read response format")
}
index := payload[2]
dataLength := payload[3:5]
le := BytesToUint16LE(dataLength)
if len(payload) < 4+int(le) {
return nil, terror.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, terror.New("invalid write response format")
}
index := payload[2]
dataLength := payload[3]
if dataLength != 1 {
return nil, terror.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, terror.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)
}