updateEmspara.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "fwh-toolbox-server/domain"
  7. "os"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "gorm.io/driver/sqlite"
  12. "gorm.io/gorm"
  13. "gorm.io/gorm/clause"
  14. "gorm.io/gorm/logger"
  15. )
  16. type MacroItem struct {
  17. MacroName string // 宏名 VID_XXX PID_XXX
  18. SubNo uint // 子序号-在本组ID中的序号
  19. TotalNo uint // 总序号-在总内存中的序号
  20. Type string // 数据类型 UINT32 INT32 FLOAT CHAR*
  21. Description string // 描述
  22. }
  23. // 由 vid_macro.h 和 pid_macro.h 生成 EMS 参数字典 SQLite(每次全新生成,不保留旧数据)。
  24. // 在 service/ 目录下执行:go run ./utils/emspara/updateCmd -out ./emspara.db
  25. // 部署时由 cmd/deploy_emspara.sh 调用,生成后拷贝到部署目录,进程启动时加载到 Redis(utils/emspara)。
  26. func main() {
  27. vidPath := flag.String("vid", "./utils/emspara/updateCmd/vid_macro.h", "VID 头文件")
  28. pidPath := flag.String("pid", "./utils/emspara/updateCmd/pid_macro.h", "PID 头文件")
  29. outPath := flag.String("out", "./emspara.db", "输出的 SQLite 文件")
  30. flag.Parse()
  31. vidCount, pidCount, err := generate(*vidPath, *pidPath, *outPath)
  32. if err != nil {
  33. fmt.Fprintf(os.Stderr, "生成 EMS 参数字典失败: %v\n", err)
  34. os.Exit(1)
  35. }
  36. fmt.Printf("已生成 %s:VID %d 条,PID %d 条\n", *outPath, vidCount, pidCount)
  37. }
  38. // generate 先写入同目录的临时文件,全部成功后再改名替换 outPath,失败时不影响已有文件
  39. func generate(vidPath, pidPath, outPath string) (int64, int64, error) {
  40. vidFile, err := os.Open(vidPath)
  41. if err != nil {
  42. return 0, 0, err
  43. }
  44. defer vidFile.Close()
  45. pidFile, err := os.Open(pidPath)
  46. if err != nil {
  47. return 0, 0, err
  48. }
  49. defer pidFile.Close()
  50. tmpPath := outPath + ".tmp"
  51. _ = os.Remove(tmpPath)
  52. defer os.Remove(tmpPath)
  53. db, err := gorm.Open(sqlite.Open(tmpPath), &gorm.Config{Logger: logger.Discard})
  54. if err != nil {
  55. return 0, 0, err
  56. }
  57. if err := fillMacros(db, vidFile, pidFile); err != nil {
  58. return 0, 0, err
  59. }
  60. var vidCount, pidCount int64
  61. db.Model(&domain.ParamsVidMacroModel{}).Count(&vidCount)
  62. db.Model(&domain.ParamsPidMacroModel{}).Count(&pidCount)
  63. if sqlDB, err := db.DB(); err == nil {
  64. sqlDB.Close()
  65. }
  66. if vidCount == 0 || pidCount == 0 {
  67. return 0, 0, fmt.Errorf("没有解析到参数(VID %d 条,PID %d 条),请检查头文件格式", vidCount, pidCount)
  68. }
  69. return vidCount, pidCount, os.Rename(tmpPath, outPath)
  70. }
  71. func fillMacros(db *gorm.DB, vidFile, pidFile *os.File) error {
  72. if err := db.AutoMigrate(&domain.ParamsVidMacroModel{}, &domain.ParamsPidMacroModel{}); err != nil {
  73. return err
  74. }
  75. // 先解析为列表(同一编号以最后出现的为准),再分表批量写入
  76. var vids []domain.ParamsVidMacroModel
  77. if err := parserMacroFile(vidFile, macroHandler(func(it MacroItem) {
  78. vids = append(vids, domain.ParamsVidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description})
  79. })); err != nil {
  80. return fmt.Errorf("解析 VID 头文件: %w", err)
  81. }
  82. var pids []domain.ParamsPidMacroModel
  83. if err := parserMacroFile(pidFile, macroHandler(func(it MacroItem) {
  84. pids = append(pids, domain.ParamsPidMacroModel{ID: it.TotalNo, MacroName: it.MacroName, Description: it.Description})
  85. })); err != nil {
  86. return fmt.Errorf("解析 PID 头文件: %w", err)
  87. }
  88. return db.Transaction(func(tx *gorm.DB) error {
  89. // 每次写入都从 tx 新建链式调用:复用同一个带 Clauses 的 *gorm.DB 会共用 Statement,后一张表的数据会写进前一张表
  90. if err := tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(vids, 500).Error; err != nil {
  91. return err
  92. }
  93. return tx.Clauses(clause.OnConflict{UpdateAll: true}).CreateInBatches(pids, 500).Error
  94. })
  95. }
  96. // macroHandler 解析一行宏定义,按分组(PE、MPPT、APbox2.0 等)展开后逐个交给 add
  97. func macroHandler(add func(it MacroItem)) func(line string) error {
  98. return func(line string) error {
  99. item, err := parserMacroLine(line)
  100. if err != nil {
  101. return err
  102. }
  103. // 非 macro 数据行
  104. if item == nil {
  105. return nil
  106. }
  107. for _, it := range checkMacroDataGroup(*item) {
  108. if it.TotalNo == 0 {
  109. // 0无法插入,抛弃
  110. continue
  111. }
  112. add(it)
  113. }
  114. return nil
  115. }
  116. }
  117. func parserMacroFile(file *os.File, fn func(line string) error) error {
  118. // 使用 transform 将 GBK 转成 UTF-8
  119. // reader := transform.NewReader(file, simplifiedchinese.GBK.NewDecoder())
  120. // scanner := bufio.NewScanner(reader)
  121. scanner := bufio.NewScanner(file)
  122. for scanner.Scan() {
  123. line := scanner.Text() // 已经是 UTF-8 的字符串
  124. line = strings.TrimSpace(line) // 去掉前后空格、制表符和换行符
  125. if err := fn(line); err != nil {
  126. fmt.Printf("[Warn]\nline: %s\nerror: %s\n", line, err.Error())
  127. }
  128. }
  129. return scanner.Err()
  130. }
  131. var macroLineRe = regexp.MustCompile(`^(\w+)\s*,\s*///<\s*\((\d+)/(\d+)\)\s*(UINT32|UNIT32|INT32|FLOAT|CHAR\*|None)\s+(.*)$`)
  132. func parserMacroLine(line string) (*MacroItem, error) {
  133. if strings.HasPrefix(line, "VID_") || strings.HasPrefix(line, "PID_") {
  134. // macro 数据
  135. // 正则匹配:
  136. // ^(\w+),\s*///<\s*\((\d+)/(\d+)\)\s*(\w+)\s+(.*)$
  137. // 解释:
  138. // 1. ^(\w+), -> 宏名 + 逗号
  139. // 2. \s*///<\s* -> 注释标记,空格可有可无
  140. // 3. \((\d+)/(\d+)\) -> 括号中的两个数字
  141. // 4. (UINT32|...|None) -> 类型;头文件中有 UNIT32 笔误,按 UINT32 处理;None 为 *_MAX_NUM 等占位行
  142. // 5. (.*)$ -> 剩余描述
  143. // 宏名与逗号之间允许有空格(头文件中存在 "PID_AWS_4G_SWITCH ," 这样的写法)
  144. matches := macroLineRe.FindStringSubmatch(line)
  145. if len(matches) == 6 {
  146. macroName := matches[1]
  147. subNoStr := matches[2]
  148. totalNoStr := matches[3]
  149. typ := matches[4]
  150. desc := matches[5]
  151. if typ == "None" {
  152. return nil, nil
  153. }
  154. if typ == "UNIT32" {
  155. typ = "UINT32"
  156. }
  157. subNo, err := strconv.ParseUint(subNoStr, 10, 64)
  158. if err != nil {
  159. return nil, err
  160. }
  161. totalNo, err := strconv.ParseUint(totalNoStr, 10, 64)
  162. if err != nil {
  163. return nil, err
  164. }
  165. return &MacroItem{
  166. MacroName: macroName,
  167. SubNo: uint(subNo),
  168. TotalNo: uint(totalNo),
  169. Type: typ,
  170. Description: desc,
  171. }, nil
  172. } else {
  173. return nil, fmt.Errorf("无法匹配该行")
  174. }
  175. }
  176. // 非macro 数据
  177. return nil, nil
  178. }
  179. func checkMacroDataGroup(item MacroItem) []MacroItem {
  180. // 排除_MAX_NUM
  181. if strings.HasSuffix(item.MacroName, "_MAX_NUM") {
  182. return []MacroItem{}
  183. }
  184. var subMaxNum uint = 0
  185. var groupNum uint = 1
  186. if strings.HasPrefix(item.MacroName, "VID_PE_") {
  187. // VID_PE_ 每组128个数据,共16组
  188. subMaxNum = 128
  189. groupNum = 16
  190. } else if strings.HasPrefix(item.MacroName, "VID_BMS_") { // NOSONAR
  191. // VID_BMS_ 每组128个数据,共16组
  192. subMaxNum = 128
  193. groupNum = 16
  194. } else if strings.HasPrefix(item.MacroName, "VID_MPPT_") { // NOSONAR
  195. // VID_MPPT_ 每组128个数据,共16组
  196. subMaxNum = 128
  197. groupNum = 16
  198. } else if strings.HasPrefix(item.MacroName, "VID_APBOX20_") {
  199. // VID_APBOX20_ 每组256个数据,共4组
  200. subMaxNum = 256
  201. groupNum = 4
  202. } else if strings.HasPrefix(item.MacroName, "PID_APOWER_") { // NOSONAR
  203. // PID_APOWER_ 每组128个数据,共16组
  204. subMaxNum = 128
  205. groupNum = 16
  206. } else if strings.HasPrefix(item.MacroName, "PID_APBOX20_") { // NOSONAR
  207. // VID_APBOX20_ 每组256个数据,共4组
  208. subMaxNum = 256
  209. groupNum = 4
  210. }
  211. if groupNum == 1 {
  212. return []MacroItem{item}
  213. }
  214. var itemList []MacroItem = []MacroItem{}
  215. for i := uint(0); i < groupNum; i++ {
  216. itemList = append(itemList, MacroItem{
  217. MacroName: fmt.Sprintf("%s_%s", item.MacroName, strconv.FormatUint(uint64(i+1), 10)),
  218. SubNo: item.SubNo,
  219. TotalNo: item.TotalNo + (i * subMaxNum),
  220. Type: item.Type,
  221. Description: item.Description,
  222. })
  223. }
  224. return itemList
  225. }