| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- package main
- import (
- "bufio"
- "flag"
- "fmt"
- "fwh-toolbox-server/domain"
- "os"
- "regexp"
- "strconv"
- "strings"
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
- "gorm.io/gorm/clause"
- "gorm.io/gorm/logger"
- )
- type MacroItem struct {
- MacroName string // 宏名 VID_XXX PID_XXX
- SubNo uint // 子序号-在本组ID中的序号
- TotalNo uint // 总序号-在总内存中的序号
- Type string // 数据类型 UINT32 INT32 FLOAT CHAR*
- Description string // 描述
- }
- // 由 vid_macro.h 和 pid_macro.h 生成 EMS 参数字典 SQLite(每次全新生成,不保留旧数据)。
- // 在 service/ 目录下执行:go run ./utils/emspara/updateCmd -out ./emspara.db
- // 部署时由 cmd/deploy_emspara.sh 调用,生成后拷贝到部署目录,进程启动时加载到 Redis(utils/emspara)。
- func main() {
- vidPath := flag.String("vid", "./utils/emspara/updateCmd/vid_macro.h", "VID 头文件")
- pidPath := flag.String("pid", "./utils/emspara/updateCmd/pid_macro.h", "PID 头文件")
- outPath := flag.String("out", "./emspara.db", "输出的 SQLite 文件")
- flag.Parse()
- vidCount, pidCount, err := generate(*vidPath, *pidPath, *outPath)
- if err != nil {
- fmt.Fprintf(os.Stderr, "生成 EMS 参数字典失败: %v\n", err)
- os.Exit(1)
- }
- fmt.Printf("已生成 %s:VID %d 条,PID %d 条\n", *outPath, vidCount, pidCount)
- }
- // generate 先写入同目录的临时文件,全部成功后再改名替换 outPath,失败时不影响已有文件
- func generate(vidPath, pidPath, outPath string) (int64, int64, error) {
- vidFile, err := os.Open(vidPath)
- if err != nil {
- return 0, 0, err
- }
- defer vidFile.Close()
- pidFile, err := os.Open(pidPath)
- if err != nil {
- return 0, 0, err
- }
- defer pidFile.Close()
- tmpPath := outPath + ".tmp"
- _ = os.Remove(tmpPath)
- defer os.Remove(tmpPath)
- db, err := gorm.Open(sqlite.Open(tmpPath), &gorm.Config{Logger: logger.Discard})
- if err != nil {
- return 0, 0, err
- }
- if err := fillMacros(db, vidFile, pidFile); err != nil {
- return 0, 0, err
- }
- var vidCount, pidCount int64
- db.Model(&domain.ParamsVidMacroModel{}).Count(&vidCount)
- db.Model(&domain.ParamsPidMacroModel{}).Count(&pidCount)
- if sqlDB, err := db.DB(); err == nil {
- sqlDB.Close()
- }
- if vidCount == 0 || pidCount == 0 {
- return 0, 0, fmt.Errorf("没有解析到参数(VID %d 条,PID %d 条),请检查头文件格式", vidCount, pidCount)
- }
- return vidCount, pidCount, os.Rename(tmpPath, outPath)
- }
- func fillMacros(db *gorm.DB, vidFile, pidFile *os.File) error {
- if err := db.AutoMigrate(&domain.ParamsVidMacroModel{}, &domain.ParamsPidMacroModel{}); err != nil {
- return err
- }
- // 先解析为列表(同一编号以最后出现的为准),再分表批量写入
- var vids []domain.ParamsVidMacroModel
- if err := parserMacroFile(vidFile, macroHandler(func(it MacroItem) {
- vids = append(vids, domain.ParamsVidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description})
- })); err != nil {
- return fmt.Errorf("解析 VID 头文件: %w", err)
- }
- var pids []domain.ParamsPidMacroModel
- if err := parserMacroFile(pidFile, macroHandler(func(it MacroItem) {
- pids = append(pids, domain.ParamsPidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description})
- })); err != nil {
- return fmt.Errorf("解析 PID 头文件: %w", err)
- }
- return db.Transaction(func(tx *gorm.DB) error {
- // 每次写入都从 tx 新建链式调用:复用同一个带 Clauses 的 *gorm.DB 会共用 Statement,后一张表的数据会写进前一张表
- if err := tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(vids, 500).Error; err != nil {
- return err
- }
- return tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(pids, 500).Error
- })
- }
- // macroHandler 解析一行宏定义,按分组(PE、MPPT、APbox2.0 等)展开后逐个交给 add
- func macroHandler(add func(it MacroItem)) func(line string) error {
- return func(line string) error {
- item, err := parserMacroLine(line)
- if err != nil {
- return err
- }
- // 非 macro 数据行
- if item == nil {
- return nil
- }
- for _, it := range checkMacroDataGroup(*item) {
- if it.TotalNo == 0 {
- // 0无法插入,抛弃
- continue
- }
- add(it)
- }
- return nil
- }
- }
- func parserMacroFile(file *os.File, fn func(line string) error) error {
- // 使用 transform 将 GBK 转成 UTF-8
- // reader := transform.NewReader(file, simplifiedchinese.GBK.NewDecoder())
- // scanner := bufio.NewScanner(reader)
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- line := scanner.Text() // 已经是 UTF-8 的字符串
- line = strings.TrimSpace(line) // 去掉前后空格、制表符和换行符
- if err := fn(line); err != nil {
- fmt.Printf("[Warn]\nline: %s\nerror: %s\n", line, err.Error())
- }
- }
- return scanner.Err()
- }
- var macroLineRe = regexp.MustCompile(`^(\w+)\s*,\s*///<\s*\((\d+)/(\d+)\)\s*(UINT32|UNIT32|INT32|FLOAT|CHAR\*|None)\s+(.*)$`)
- func parserMacroLine(line string) (*MacroItem, error) {
- if strings.HasPrefix(line, "VID_") || strings.HasPrefix(line, "PID_") {
- // macro 数据
- // 正则匹配:
- // ^(\w+),\s*///<\s*\((\d+)/(\d+)\)\s*(\w+)\s+(.*)$
- // 解释:
- // 1. ^(\w+), -> 宏名 + 逗号
- // 2. \s*///<\s* -> 注释标记,空格可有可无
- // 3. \((\d+)/(\d+)\) -> 括号中的两个数字
- // 4. (UINT32|...|None) -> 类型;头文件中有 UNIT32 笔误,按 UINT32 处理;None 为 *_MAX_NUM 等占位行
- // 5. (.*)$ -> 剩余描述
- // 宏名与逗号之间允许有空格(头文件中存在 "PID_AWS_4G_SWITCH ," 这样的写法)
- matches := macroLineRe.FindStringSubmatch(line)
- if len(matches) == 6 {
- macroName := matches[1]
- subNoStr := matches[2]
- totalNoStr := matches[3]
- typ := matches[4]
- desc := matches[5]
- if typ == "None" {
- return nil, nil
- }
- if typ == "UNIT32" {
- typ = "UINT32"
- }
- subNo, err := strconv.ParseUint(subNoStr, 10, 64)
- if err != nil {
- return nil, err
- }
- totalNo, err := strconv.ParseUint(totalNoStr, 10, 64)
- if err != nil {
- return nil, err
- }
- return &MacroItem{
- MacroName: macroName,
- SubNo: uint(subNo),
- TotalNo: uint(totalNo),
- Type: typ,
- Description: desc,
- }, nil
- } else {
- return nil, fmt.Errorf("无法匹配该行")
- }
- }
- // 非macro 数据
- return nil, nil
- }
- func checkMacroDataGroup(item MacroItem) []MacroItem {
- // 排除_MAX_NUM
- if strings.HasSuffix(item.MacroName, "_MAX_NUM") {
- return []MacroItem{}
- }
- var subMaxNum uint = 0
- var groupNum uint = 1
- if strings.HasPrefix(item.MacroName, "VID_PE_") {
- // VID_PE_ 每组128个数据,共16组
- subMaxNum = 128
- groupNum = 16
- } else if strings.HasPrefix(item.MacroName, "VID_BMS_") { // NOSONAR
- // VID_BMS_ 每组128个数据,共16组
- subMaxNum = 128
- groupNum = 16
- } else if strings.HasPrefix(item.MacroName, "VID_MPPT_") { // NOSONAR
- // VID_MPPT_ 每组128个数据,共16组
- subMaxNum = 128
- groupNum = 16
- } else if strings.HasPrefix(item.MacroName, "VID_APBOX20_") {
- // VID_APBOX20_ 每组256个数据,共4组
- subMaxNum = 256
- groupNum = 4
- } else if strings.HasPrefix(item.MacroName, "PID_APOWER_") { // NOSONAR
- // PID_APOWER_ 每组128个数据,共16组
- subMaxNum = 128
- groupNum = 16
- } else if strings.HasPrefix(item.MacroName, "PID_APBOX20_") { // NOSONAR
- // VID_APBOX20_ 每组256个数据,共4组
- subMaxNum = 256
- groupNum = 4
- }
- if groupNum == 1 {
- return []MacroItem{item}
- }
- var itemList []MacroItem = []MacroItem{}
- for i := uint(0); i < groupNum; i++ {
- itemList = append(itemList, MacroItem{
- MacroName: fmt.Sprintf("%s_%s", item.MacroName, strconv.FormatUint(uint64(i+1), 10)),
- SubNo: item.SubNo,
- TotalNo: item.TotalNo + (i * subMaxNum),
- Type: item.Type,
- Description: item.Description,
- })
- }
- return itemList
- }
|