package m9z import ( "crypto/sha256" "encoding/base64" "encoding/binary" "fmt" "github.com/towgo/towgo/errors/terror" "io" "net/http" "os" "path/filepath" g "tgk-touch/internal/global" "time" ) type FirmwareReadBlock struct { Address uint32 Length uint16 Data []byte } // FirmwareRead 读取设备指定地址和长度的固件数据 // 参数: // - deviceId: 设备ID // - address: 读取起始地址(32位,如0x08000000) // - length: 读取数据长度(16位,如512字节) // // 返回: 读取到的固件数据及可能的错误 func FirmwareRead(deviceId string, address uint32, length uint16) (*FirmwareReadBlock, error) { // 1. 构建读取固件的payload // 结构说明: [指令类型(1字节)][操作类型(1字节)][索引(1字节)][数据长度(1字节)][地址(4字节小端)][读取长度(2字节小端)] payload := make([]byte, 0, 10) // 固定10字节payload payload = append(payload, byte(firmwareData)) // 指令类型: 0x0E (固件数据相关) payload = append(payload, CmdRead) // 操作类型: 0x01 (读取) payload = append(payload, 0x00) // 索引: 固定0x00 // 数据长度字段: 后续地址(4字节) + 长度(2字节) = 6字节,故为0x06 payload = append(payload, 0x06) // 地址字段: 32位小端序 addrBytes := make([]byte, 4) binary.LittleEndian.PutUint32(addrBytes, address) payload = append(payload, addrBytes...) // 读取长度字段: 16位小端序 lenBytes := make([]byte, 2) binary.LittleEndian.PutUint16(lenBytes, length) payload = append(payload, lenBytes...) // 2. 计算校验和 checksum := calCheckSum(payload) // 3. 构建完整帧: [帧头][payload][校验和][帧尾] frame := []byte{FrameHeader} // 帧头: 0xEE frame = append(frame, payload...) // 加入payload frame = append(frame, checksum) // 加入校验和 frame = append(frame, FrameFooter) // 帧尾: 0xFF // 4. 发送命令并获取响应 respData, err := pushCommand(deviceId, frame, "读取固件数据") if err != nil { return nil, terror.Wrapf(err, "设备[%s]读取固件数据失败(地址:0x%X, 长度:%d)", deviceId, address, length) } // 5. 解析响应帧 frameResp, err := ParseBlockFrame(respData) if err != nil { return nil, terror.Wrapf(err, "设备[%s]解析固件响应失败", deviceId) } block := &FirmwareReadBlock{} block.Address = binary.LittleEndian.Uint32(frameResp.Data[0:4]) block.Length = BytesToUint16LE(frameResp.Data[4:6]) block.Data = frameResp.Data[6:] // 校验返回数据长度是否匹配请求长度 if block.Length != uint16(length) { return nil, fmt.Errorf("设备[%s]固件数据长度不匹配(期望:%d, 实际:%d)", deviceId, length, len(block.Data)) } return block, nil } // 解析接收帧 func ParseBlockFrame(data []byte) (*ReadResponse, 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] _ = payload[1] // 判断是否是响应帧(D7位是否置1) _ = (instruction & ResponseFlag) != 0 return parseReadBlockResponse(payload) } // GenerateFirmwareShortID 生成固件的短唯一标识 // 参数: // // firmware - 固件字节数组 // // 返回: // // 短唯一字符串、错误(长度非法时) func GenerateFirmwareShortID(firmware []byte) (shortID string) { length := 8 // 1. 校验长度合法性 //if length <= 0 || length > 43 { // Base64URL编码SHA256最大43位,十六进制最大64位 // return "", fmt.Errorf("短ID长度需在1~43之间(推荐8~16)") //} // 2. 计算固件的SHA256哈希(核心:内容变则哈希变) firmware = append(firmware, byte(len(firmware))) hash := sha256.Sum256(firmware) hashBytes := hash[:] // 32字节的哈希值 // 3. Base64URL编码(无特殊字符,比十六进制更短) // Base64URL:去掉+、/,替换为-、_,不补=,适合URL/文件名 base64URL := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(hashBytes) // 4. 截取指定长度(前length位),保证短小且唯一 shortID = base64URL[:length] return shortID } // 固件升级起始地址 const FirmwareStartAddr = 0x00000000 // FirmwareUpgrade 设备固件完整升级流程 func FirmwareUpgrade(deviceId string, firmware []byte, startAddr uint32) (uniqueId string, addr uint32, err error) { blockSize := 512 initialOffset := int(startAddr - FirmwareStartAddr) addr = startAddr // 起始地址 uniqueId = GenerateFirmwareShortID(firmware) if startAddr == FirmwareStartAddr { // 1. 擦除扇区 g.Log().Debug("开始擦除分区") _, err = EraseSector(deviceId) if err != nil { return uniqueId, addr, fmt.Errorf("擦除扇区阶段失败: %v", err) } } else { /* // 不是起始就要判断完整性 blockData := firmware[initialOffset : initialOffset+blockSize] blockHash := sha256.Sum256(blockData) frimwareVer := hex.EncodeToString(blockHash[:]) read, err := FirmwareRead(deviceId, startAddr, uint16(blockSize)) if err != nil { return uniqueId, addr, terror.Wrapf(err, "验证设备上最新固件包失败") } deviceHash := sha256.Sum256(read.Data) deviceVer := hex.EncodeToString(deviceHash[:]) if deviceVer != frimwareVer { os.WriteFile("./blockData.txt", []byte(fmt.Sprintf("% X", blockData)), 0666) os.WriteFile("./readData.txt", []byte(fmt.Sprintf("% X", read.Data)), 0666) return uniqueId, addr, terror.Newf("设备最新不完整,建议擦除分区重试 %s != %s %d %d", frimwareVer, deviceVer, len(blockData), len(read.Data)) }*/ } // 2. 分块下发固件数据(每块512字节,不足补零) totalLen := len(firmware) blockCount := 0 // 3. 计算起始地址对应的固件偏移量(核心:地址→偏移量映射) g.Log().Debugf("起始地址0x%X对应固件偏移量: %d字节", startAddr, initialOffset) for offset := initialOffset; offset < totalLen; offset += blockSize { blockCount++ // 计算当前块数据 end := offset + blockSize if end > totalLen { end = totalLen } blockData := firmware[offset:end] // 补零逻辑:如果块大小不足512字节,自动补零到512字节 if len(blockData) < blockSize { paddingLen := blockSize - len(blockData) blockData = append(blockData, make([]byte, paddingLen)...) } blockLen := uint16(len(blockData)) // 补零后长度应为512(最后一块可能补零后等于blockSize) // 计算扩展长度(总长度高8位)和数据长度(低8位) totalBlockDataLen := 6 + int(blockLen) // 1(ext) + 4(addr) + 2(len) + dataLen extDataLen := byte(totalBlockDataLen >> 8) // 下发当前块(带重试机制) _, err = FirmwareDataWrite(deviceId, extDataLen, addr, blockLen, blockData) /* for i := 0; i < 100; i++ { _, err = FirmwareDataWrite(deviceId, extDataLen, addr, blockLen, blockData) if err == nil { break } else { err = fmt.Errorf("下发固件块(偏移%d)失败: %v", offset, err) g.Log().Error(err.Error()) } time.Sleep(3 * time.Second) }*/ if err != nil { return uniqueId, addr, err } // 更新地址(按实际补零后的块长度递增) addr += uint32(blockLen) } // 3. 加锁固件 _, err = LockFirmware(deviceId) if err != nil { return uniqueId, addr, fmt.Errorf("固件加锁阶段失败: %v", err) } // 4. 重启设备完成升级 if err := Restart(deviceId); err != nil { return uniqueId, addr, fmt.Errorf("重启设备失败: %v", err) } return uniqueId, addr, err } // EraseSector 擦除设备扇区 // 指令: CMD=0x0D, IDX=0x00 func EraseSector(deviceId string) (*WriteResponse, error) { // 固定数据格式: 04 77 88 99 00(参考示例指令) data := []byte{0x04, 0x77, 0x88, 0x99, 0x00} // 发送写入命令 resp, err := WriteCmd(deviceId, eraseSector, data, "擦除扇区", UpdateMarker) if err != nil { return nil, terror.Wrapf(err, "设备[%s]擦除扇区失败", deviceId) } // 验证响应状态 if !resp.Success { return nil, fmt.Errorf("设备[%s]擦除扇区响应失败", deviceId) } return resp, nil } // FirmwareDataWrite 下发固件数据到设备 // 参数: // - deviceId: 设备ID // - extDataLen: 扩展数据长度(高8位,与dataLen组成16位总长度) // - addr: 写入地址(4字节) // - writeLen: 写入数据长度(2字节) // - fileData: 实际固件数据 func FirmwareDataWrite(deviceId string, extDataLen byte, addr uint32, writeLen uint16, fileData []byte) (*WriteResponse, error) { // 1. 校验参数合法性 totalDataLen := uint16(extDataLen)<<8 | uint16(len(fileData)+6) // 7 = 1(ext) + 4(addr) + 2(writeLen) if totalDataLen != uint16(extDataLen)<<8|uint16(len(fileData)+6) { return nil, fmt.Errorf("数据长度不匹配,扩展长度0x%02X,实际总长度0x%04X", extDataLen, len(fileData)+7) } if int(writeLen) != len(fileData) { return nil, fmt.Errorf("写入长度与文件数据长度不匹配(%d vs %d)", writeLen, len(fileData)) } // 2. 构造升级数据 upgradeData := make([]byte, 0, 6+len(fileData)) upgradeData = append(upgradeData, extDataLen) // 扩展数据长度(1字节) // 写入地址(4字节,小端序) addrBytes := make([]byte, 4) binary.LittleEndian.PutUint32(addrBytes, addr) upgradeData = append(upgradeData, addrBytes...) // 写入长度(2字节,小端序) lenBytes := make([]byte, 2) binary.LittleEndian.PutUint16(lenBytes, writeLen) upgradeData = append(upgradeData, lenBytes...) // 固件文件数据 upgradeData = append(upgradeData, fileData...) // 3. 构造命令数据(数据长度字段 + 升级数据) dataLen := byte(len(upgradeData) - 1&0xFF) // 数据长度(低8位) cmdData := append([]byte{dataLen}, upgradeData...) // 4. 发送写入命令 resp, err := WriteCmd(deviceId, firmwareData, cmdData, "下发固件数据", UpdateMarker) if err != nil { return nil, terror.Wrapf(err, "\n设备[%s]下发固件数据失败", deviceId) } if !resp.Success { return nil, fmt.Errorf("设备[%s]固件数据写入响应失败", deviceId) } return resp, nil } // LockFirmware 固件数据下发完成后加锁 // 指令: CMD=0x0F, IDX=0x00 func LockFirmware(deviceId string) (*WriteResponse, error) { // 无数据 payload(参考示例指令:数据长度0x00) resp, err := WriteCmd(deviceId, lockFirmware, []byte{00}, "固件加锁") if err != nil { return nil, terror.Wrapf(err, "设备[%s]固件加锁失败", deviceId) } if !resp.Success { return nil, fmt.Errorf("设备[%s]固件加锁响应失败", deviceId) } return resp, nil } // GetFirmwareFromLocal 从本地文件读取固件 // 参数: localPath 本地固件文件路径(如 "./firmware_v1.2.bin") func GetFirmwareFromLocal(localPath string) ([]byte, error) { // 校验路径合法性 if localPath == "" { return nil, terror.New("本地固件路径不能为空") } absPath, err := filepath.Abs(localPath) if err != nil { return nil, terror.Wrapf(err, "读取固件绝对路径失败(%s)", localPath) } // 检查文件是否存在 fileInfo, err := os.Stat(absPath) if err != nil { return nil, terror.Wrapf(err, "固件文件不存在或无法访问(%s)", absPath) } if fileInfo.IsDir() { return nil, terror.Newf("路径指向目录,不是固件文件(%s)", absPath) } // 读取文件内容 firmwareData, err := os.ReadFile(absPath) if err != nil { return nil, terror.Wrapf(err, "读取固件文件失败(%s)", absPath) } g.Log().Infof("成功从本地读取固件,路径:%s,大小:%d字节", absPath, len(firmwareData)) return firmwareData, nil } // GetFirmwareFromURL 从网络URL下载固件 // 参数: url 固件下载地址(如 "http://example.com/firmware_v1.2.bin") func GetFirmwareFromURL(url string) ([]byte, error) { // 校验URL合法性 if url == "" { return nil, terror.New("固件下载URL不能为空") } if filepath.Ext(url) != ".bin" { g.Log().Warnf("固件URL后缀非.bin,可能不是有效固件文件(%s)", url) } // 发送HTTP GET请求(设置超时时间) client := &http.Client{ Timeout: 30 * time.Second, // 30秒超时 } resp, err := client.Get(url) if err != nil { return nil, terror.Wrapf(err, "固件下载请求失败(%s)", url) } defer resp.Body.Close() // 确保响应体关闭 // 校验响应状态 if resp.StatusCode != http.StatusOK { return nil, terror.Newf("固件下载失败,HTTP状态码:%d(%s)", resp.StatusCode, url) } // 读取响应内容 firmwareData, err := io.ReadAll(resp.Body) if err != nil { return nil, terror.Wrapf(err, "读取固件响应内容失败(%s)", url) } g.Log().Infof("成功从网络下载固件,URL:%s,大小:%d字节", url, len(firmwareData)) return firmwareData, nil }