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.

413 lines
13 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 (
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"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, gerror.Wrapf(err, "设备[%s]读取固件数据失败(地址:0x%X, 长度:%d", deviceId, address, length)
}
// 5. 解析响应帧
frameResp, err := ParseBlockFrame(respData)
if err != nil {
return nil, gerror.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, gerror.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, gerror.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, gerror.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, gerror.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, gerror.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, gerror.New("本地固件路径不能为空")
}
absPath, err := filepath.Abs(localPath)
if err != nil {
return nil, gerror.Wrapf(err, "读取固件绝对路径失败(%s", localPath)
}
// 检查文件是否存在
fileInfo, err := os.Stat(absPath)
if err != nil {
return nil, gerror.Wrapf(err, "固件文件不存在或无法访问(%s", absPath)
}
if fileInfo.IsDir() {
return nil, gerror.Newf("路径指向目录,不是固件文件(%s", absPath)
}
// 读取文件内容
firmwareData, err := os.ReadFile(absPath)
if err != nil {
return nil, gerror.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, gerror.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, gerror.Wrapf(err, "固件下载请求失败(%s", url)
}
defer resp.Body.Close() // 确保响应体关闭
// 校验响应状态
if resp.StatusCode != http.StatusOK {
return nil, gerror.Newf("固件下载失败HTTP状态码%d%s", resp.StatusCode, url)
}
// 读取响应内容
firmwareData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, gerror.Wrapf(err, "读取固件响应内容失败(%s", url)
}
g.Log().Infof("成功从网络下载固件URL%s大小%d字节", url, len(firmwareData))
return firmwareData, nil
}