|
|
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
|
|
|
}
|