reader.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. package uhf
  2. import (
  3. "context"
  4. "encoding/binary"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "sync"
  9. "time"
  10. "wails-app/internal/dao"
  11. "wails-app/internal/global"
  12. common "wails-app/internal/model/common"
  13. incidentService "wails-app/internal/modules/incident/service"
  14. "wails-app/internal/service"
  15. "wails-app/internal/service/parking"
  16. "go.uber.org/zap"
  17. )
  18. // Reader 通用读写接口(串口和TCP都实现它)
  19. type Reader interface {
  20. Connect() error
  21. Disconnect() error
  22. SendData([]byte) error
  23. ReadData() ([]byte, error)
  24. IsConnected() bool
  25. // ===================== 加入继电器接口 =====================
  26. CloseRelay1(validTime byte) error
  27. ReleaseRelay1() error
  28. CloseRelay2(validTime byte) error
  29. ReleaseRelay2() error
  30. }
  31. // ChannelEvent 通道事件(RFID/车牌识别后推送给前端)
  32. type ChannelEvent struct {
  33. ID uint64 `json:"id"`
  34. DeviceCode string `json:"device_code"`
  35. RFIDTag string `json:"rfid_tag"`
  36. PlateNumber string `json:"plate_number"`
  37. Direction string `json:"direction"`
  38. ChannelName string `json:"channel_name"`
  39. Timestamp int64 `json:"timestamp"`
  40. Status string `json:"status"`
  41. Message string `json:"message"`
  42. Fee float64 `json:"fee"`
  43. StayTime int64 `json:"stay_time"`
  44. }
  45. var (
  46. channelEventMu sync.Mutex
  47. ChannelEventQueue = make([]ChannelEvent, 0, 50)
  48. channelEventNextID uint64
  49. )
  50. // PushChannelEvent 追加通道事件(保留最近 50 条)
  51. func PushChannelEvent(e ChannelEvent) {
  52. channelEventMu.Lock()
  53. defer channelEventMu.Unlock()
  54. channelEventNextID++
  55. e.ID = channelEventNextID
  56. if e.Timestamp == 0 {
  57. e.Timestamp = time.Now().Unix()
  58. }
  59. if e.Status == "" {
  60. e.Status = "completed"
  61. }
  62. if len(ChannelEventQueue) >= 50 {
  63. ChannelEventQueue = ChannelEventQueue[1:]
  64. }
  65. ChannelEventQueue = append(ChannelEventQueue, e)
  66. }
  67. // GetChannelEvents 获取事件编号大于 afterID 的通道结果。
  68. func GetChannelEvents(afterID uint64) []ChannelEvent {
  69. channelEventMu.Lock()
  70. defer channelEventMu.Unlock()
  71. result := make([]ChannelEvent, 0)
  72. for _, e := range ChannelEventQueue {
  73. if e.ID > afterID {
  74. result = append(result, e)
  75. }
  76. }
  77. return result
  78. }
  79. // 上报数据模型
  80. type ReportData struct {
  81. DeviceCode string `json:"device_code"` // 设备编码
  82. Hex string `json:"hex"` // 原始报文
  83. Epcs []string `json:"epcs"` // 解析出的EPC列表
  84. RSSI int `json:"rssi"` // 信号强度
  85. Antenna int `json:"antenna"` // 天线号
  86. Timestamp int64 `json:"timestamp"` // 上报时间
  87. }
  88. // 全局设备管理器(管理所有已连接的设备)
  89. var DeviceManager = &Manager{
  90. devices: make(map[string]*DeviceHandler),
  91. }
  92. // init 注入统一道闸控制器(避免停车业务依赖具体设备协议)。
  93. func init() {
  94. parking.SetGateController(DeviceManager)
  95. }
  96. type Manager struct {
  97. mu sync.RWMutex
  98. devices map[string]*DeviceHandler
  99. }
  100. // DeviceHandler 每个设备的处理实例(包含读写器和协程)
  101. type DeviceHandler struct {
  102. reader Reader
  103. device *dao.UHFReader
  104. ctx context.Context
  105. cancel context.CancelFunc
  106. dataChan chan *ReportData
  107. ioMu sync.Mutex
  108. reportMu sync.Mutex
  109. lastReport map[string]time.Time
  110. stopOnce sync.Once
  111. done chan struct{}
  112. isRunning bool
  113. }
  114. const (
  115. // reportDebounceWindow prevents readers that continuously report the same tag
  116. // from filling the business queue and hitting SQLite for every radio frame.
  117. reportDebounceWindow = 3 * time.Second
  118. maxReportCacheSize = 1024
  119. )
  120. // acceptReport returns false when the same device/EPC was accepted recently.
  121. // The check runs before the report enters the business queue, so duplicate
  122. // radio frames do not generate log, database, or passage-processing load.
  123. func (h *DeviceHandler) acceptReport(report *ReportData, now time.Time) bool {
  124. if report == nil || len(report.Epcs) == 0 || report.Epcs[0] == "" {
  125. return false
  126. }
  127. key := report.DeviceCode + "\x00" + report.Epcs[0]
  128. h.reportMu.Lock()
  129. defer h.reportMu.Unlock()
  130. if last, ok := h.lastReport[key]; ok && now.Sub(last) < reportDebounceWindow {
  131. return false
  132. }
  133. if h.lastReport == nil {
  134. h.lastReport = make(map[string]time.Time)
  135. }
  136. h.lastReport[key] = now
  137. // Bound memory for installations with many tags. Cleanup is opportunistic
  138. // and only runs after the cache reaches the configured upper bound.
  139. if len(h.lastReport) > maxReportCacheSize {
  140. cutoff := now.Add(-reportDebounceWindow)
  141. for cachedKey, cachedAt := range h.lastReport {
  142. if cachedAt.Before(cutoff) {
  143. delete(h.lastReport, cachedKey)
  144. }
  145. }
  146. }
  147. return true
  148. }
  149. // Register 注册设备
  150. func (m *Manager) Register(code string, h *DeviceHandler) {
  151. m.mu.Lock()
  152. defer m.mu.Unlock()
  153. m.devices[code] = h
  154. }
  155. // Unregister 注销设备
  156. func (m *Manager) Unregister(code string) {
  157. m.mu.Lock()
  158. defer m.mu.Unlock()
  159. delete(m.devices, code)
  160. }
  161. // Get 获取设备
  162. func (m *Manager) Get(code string) (*DeviceHandler, bool) {
  163. m.mu.RLock()
  164. defer m.mu.RUnlock()
  165. h, ok := m.devices[code]
  166. return h, ok
  167. }
  168. // StopDeviceHandler 停止设备读循环、关闭底层连接并等待退出。
  169. func StopDeviceHandler(code string) error {
  170. h, ok := DeviceManager.Get(code)
  171. if !ok {
  172. return nil
  173. }
  174. h.stopOnce.Do(func() {
  175. if h.cancel != nil {
  176. h.cancel()
  177. }
  178. if h.reader != nil {
  179. _ = h.reader.Disconnect()
  180. }
  181. })
  182. if h.done == nil {
  183. DeviceManager.Unregister(code)
  184. return nil
  185. }
  186. select {
  187. case <-h.done:
  188. case <-time.After(5 * time.Second):
  189. return fmt.Errorf("设备停止超时: %s", code)
  190. }
  191. return nil
  192. }
  193. // OpenGate 开闸(公开方法,供外部触发源调用,如摄像头识别、手动进出场)
  194. func (h *DeviceHandler) OpenGate(validTime byte) error {
  195. h.ioMu.Lock()
  196. defer h.ioMu.Unlock()
  197. return h.reader.CloseRelay1(validTime)
  198. }
  199. // CloseGate 关闸。
  200. func (h *DeviceHandler) CloseGate(validTime byte) error {
  201. h.ioMu.Lock()
  202. defer h.ioMu.Unlock()
  203. return h.reader.CloseRelay2(validTime)
  204. }
  205. // OpenGate 通过设备编码开闸,实现 parking.GateController。
  206. func (m *Manager) OpenGate(deviceCode string, validTime byte) error {
  207. h, ok := m.Get(deviceCode)
  208. if !ok {
  209. return fmt.Errorf("设备未连接: %s", deviceCode)
  210. }
  211. return h.OpenGate(validTime)
  212. }
  213. // CloseGate 通过设备编码关闸,实现 parking.GateController。
  214. func (m *Manager) CloseGate(deviceCode string, validTime byte) error {
  215. h, ok := m.Get(deviceCode)
  216. if !ok {
  217. return fmt.Errorf("设备未连接: %s", deviceCode)
  218. }
  219. return h.CloseGate(validTime)
  220. }
  221. // IsGateConnected 判断设备是否已注册到当前进程。
  222. func (m *Manager) IsGateConnected(deviceCode string) bool {
  223. h, ok := m.Get(deviceCode)
  224. return ok && h.reader != nil && h.reader.IsConnected()
  225. }
  226. // OpenGateByDeviceCode 通过设备编码直接开闸(便捷方法)
  227. func (m *Manager) OpenGateByDeviceCode(deviceCode string, validTime byte) error {
  228. return m.OpenGate(deviceCode, validTime)
  229. }
  230. // CloseGateByDeviceCode 通过设备编码直接关闸。
  231. func (m *Manager) CloseGateByDeviceCode(deviceCode string, validTime byte) error {
  232. return m.CloseGate(deviceCode, validTime)
  233. }
  234. // ===================== 复用原有CRC和解析函数(无需修改)=====================
  235. func uiCrc16Cal(pucY []byte, ucX uint8) uint16 {
  236. const PRESET_VALUE = 0xFFFF
  237. const POLYNOMIAL = 0x8408
  238. var uiCrcValue uint16 = PRESET_VALUE
  239. for ucI := uint8(0); ucI < ucX; ucI++ {
  240. uiCrcValue = uiCrcValue ^ uint16(pucY[ucI])
  241. for ucJ := uint8(0); ucJ < 8; ucJ++ {
  242. if uiCrcValue&0x0001 != 0 {
  243. uiCrcValue = (uiCrcValue >> 1) ^ POLYNOMIAL
  244. } else {
  245. uiCrcValue = uiCrcValue >> 1
  246. }
  247. }
  248. }
  249. return (uiCrcValue << 8) | (uiCrcValue >> 8)
  250. }
  251. // StartDeviceHandler 启动单个设备的读写协程
  252. func StartDeviceHandler(device *dao.UHFReader) error {
  253. if device == nil || device.DeviceCode == "" {
  254. return errors.New("设备编码不能为空")
  255. }
  256. if existing, ok := DeviceManager.Get(device.DeviceCode); ok {
  257. if existing.reader != nil && existing.reader.IsConnected() {
  258. return fmt.Errorf("设备已连接: %s", device.DeviceCode)
  259. }
  260. if err := StopDeviceHandler(device.DeviceCode); err != nil {
  261. return err
  262. }
  263. }
  264. // MQTT 设备由设备主动连接 broker,系统不主动建立连接。
  265. if device.ConnectType == dao.ConnectTypeMQTT {
  266. return nil
  267. }
  268. if global.GVA_LOG != nil {
  269. global.GVA_LOG.Info("启动 UHF/RFID 设备连接",
  270. zap.String("device_code", device.DeviceCode),
  271. zap.String("device_name", device.DeviceName),
  272. zap.String("connect_type", device.ConnectType),
  273. zap.String("tcp_address", readerTCPAddress(device)),
  274. zap.Uint("channel_id", device.ChannelID),
  275. )
  276. }
  277. // 1. 根据连接类型创建读写器
  278. var r Reader
  279. switch device.ConnectType {
  280. case "serial":
  281. r = NewSerialReader(device.COMPort, device.BaudRate)
  282. case "tcp":
  283. r = NewTCPReader(device.IPAddress, device.Port)
  284. default:
  285. return fmt.Errorf("不支持的连接类型: %s", device.ConnectType)
  286. }
  287. // 2. 连接设备
  288. if err := r.Connect(); err != nil {
  289. if global.GVA_LOG != nil {
  290. global.GVA_LOG.Error("UHF/RFID 设备连接失败",
  291. zap.String("device_code", device.DeviceCode),
  292. zap.String("connect_type", device.ConnectType),
  293. zap.String("tcp_address", readerTCPAddress(device)),
  294. zap.Error(err),
  295. )
  296. }
  297. return err
  298. }
  299. if global.GVA_LOG != nil {
  300. global.GVA_LOG.Info("UHF/RFID 设备连接成功",
  301. zap.String("device_code", device.DeviceCode),
  302. zap.String("connect_type", device.ConnectType),
  303. zap.String("tcp_address", readerTCPAddress(device)),
  304. )
  305. }
  306. // 3. 创建上下文和handler
  307. ctx, cancel := context.WithCancel(context.Background())
  308. h := &DeviceHandler{
  309. reader: r,
  310. device: device,
  311. ctx: ctx,
  312. cancel: cancel,
  313. dataChan: make(chan *ReportData, 200),
  314. lastReport: make(map[string]time.Time),
  315. done: make(chan struct{}),
  316. isRunning: true,
  317. }
  318. // 4. 注册到管理器
  319. DeviceManager.Register(device.DeviceCode, h)
  320. // 5. 启动读写协程
  321. go func() {
  322. defer func() {
  323. h.stopOnce.Do(func() { h.cancel() })
  324. h.isRunning = false
  325. _ = r.Disconnect()
  326. close(h.dataChan)
  327. DeviceManager.Unregister(device.DeviceCode)
  328. close(h.done)
  329. if global.GVA_LOG != nil {
  330. global.GVA_LOG.Info("设备已停止: " + device.DeviceCode)
  331. }
  332. }()
  333. for {
  334. select {
  335. case <-ctx.Done():
  336. return
  337. default:
  338. h.ioMu.Lock()
  339. buf, err := r.ReadData()
  340. h.ioMu.Unlock()
  341. if errors.Is(err, ErrReadTimeout) || errors.Is(err, ErrIncompleteFrame) {
  342. continue
  343. }
  344. if err != nil {
  345. if global.GVA_LOG != nil {
  346. global.GVA_LOG.Warn("UHF/RFID 读取失败,准备重连",
  347. zap.String("device_code", device.DeviceCode),
  348. zap.String("connect_type", device.ConnectType),
  349. zap.String("tcp_address", readerTCPAddress(device)),
  350. zap.Error(err),
  351. )
  352. }
  353. if global.GVA_DB != nil {
  354. global.GVA_DB.Model(device).Update("status", "offline")
  355. }
  356. // 离线异常埋点(best-effort,同设备未关闭不重复生成)
  357. incidentService.NewIncidentService().RecordIncident(incidentService.RecordIncidentRequest{
  358. Category: incidentService.CategoryDeviceOffline,
  359. Source: incidentService.SourceDevice,
  360. ParkingLotID: device.ParkingLotID,
  361. DeviceCode: device.DeviceCode,
  362. Description: "UHF 读卡器离线",
  363. Detail: err.Error(),
  364. })
  365. _ = r.Disconnect() // 先断开
  366. if !waitDeviceRetry(ctx, 2*time.Second) {
  367. return
  368. }
  369. if err := r.Connect(); err != nil {
  370. if global.GVA_LOG != nil {
  371. global.GVA_LOG.Warn("UHF/RFID 设备重连失败",
  372. zap.String("device_code", device.DeviceCode),
  373. zap.String("tcp_address", readerTCPAddress(device)),
  374. zap.Error(err),
  375. )
  376. }
  377. continue
  378. }
  379. if global.GVA_LOG != nil {
  380. global.GVA_LOG.Info("UHF/RFID 设备重连成功",
  381. zap.String("device_code", device.DeviceCode),
  382. zap.String("tcp_address", readerTCPAddress(device)),
  383. )
  384. }
  385. if global.GVA_DB != nil {
  386. global.GVA_DB.Model(device).Updates(map[string]interface{}{"status": "online", "last_online_time": time.Now()})
  387. }
  388. // 设备恢复在线:自动关闭未处理的离线异常
  389. incidentService.NewIncidentService().ResolveDeviceOffline(device.DeviceCode)
  390. continue
  391. }
  392. if len(buf) == 0 {
  393. continue
  394. }
  395. if global.GVA_LOG != nil {
  396. global.GVA_LOG.Debug("收到 UHF/RFID 原始帧",
  397. zap.String("device_code", device.DeviceCode),
  398. zap.Int("packet_bytes", len(buf)),
  399. zap.String("packet_hex", hex.EncodeToString(buf)),
  400. )
  401. }
  402. // 解析上报数据(继电器指令应答等控制帧不走标签解析)
  403. if !isTagReportFrame(buf) {
  404. if global.GVA_LOG != nil {
  405. global.GVA_LOG.Debug("UHF/RFID 忽略非标签帧",
  406. zap.String("device_code", device.DeviceCode),
  407. zap.String("packet_hex", hex.EncodeToString(buf)),
  408. )
  409. }
  410. continue
  411. }
  412. report, err := parseReportData(device.DeviceCode, buf)
  413. if err != nil {
  414. if global.GVA_LOG != nil {
  415. global.GVA_LOG.Warn("UHF/RFID 报文解析失败",
  416. zap.String("device_code", device.DeviceCode),
  417. zap.String("packet_hex", hex.EncodeToString(buf)),
  418. zap.Error(err),
  419. )
  420. }
  421. continue
  422. }
  423. if global.GVA_LOG != nil {
  424. global.GVA_LOG.Debug("UHF/RFID 标签解析成功",
  425. zap.String("device_code", report.DeviceCode),
  426. zap.Strings("epcs", report.Epcs),
  427. zap.Int("rssi", report.RSSI),
  428. zap.Int("antenna", report.Antenna),
  429. )
  430. }
  431. if !h.acceptReport(report, time.Now()) {
  432. continue
  433. }
  434. // 推送(不丢死,也不阻塞)
  435. select {
  436. case h.dataChan <- report:
  437. default:
  438. if global.GVA_LOG != nil {
  439. global.GVA_LOG.Warn("通道已满,丢弃数据: " + device.DeviceCode)
  440. }
  441. }
  442. }
  443. }
  444. }()
  445. // 6. 启动业务处理协程
  446. go func() {
  447. for {
  448. select {
  449. case <-ctx.Done():
  450. return
  451. case report, ok := <-h.dataChan:
  452. if !ok {
  453. return
  454. }
  455. handleReportData(report)
  456. }
  457. }
  458. }()
  459. if global.GVA_DB != nil {
  460. global.GVA_DB.Model(device).Updates(map[string]interface{}{"status": "online", "last_online_time": time.Now()})
  461. }
  462. // 启动时兜底:关闭历史遗留的同设备离线异常
  463. incidentService.NewIncidentService().ResolveDeviceOffline(device.DeviceCode)
  464. return nil
  465. }
  466. // waitDeviceRetry 等待重连间隔,同时允许停止设备时立即退出。
  467. func waitDeviceRetry(ctx context.Context, delay time.Duration) bool {
  468. timer := time.NewTimer(delay)
  469. defer timer.Stop()
  470. select {
  471. case <-ctx.Done():
  472. return false
  473. case <-timer.C:
  474. return true
  475. }
  476. }
  477. // extractUHFFrame 从缓冲区提取一帧校验通过的协议帧。
  478. // 帧结构: CF ADDR CMD_H CMD_L LEN PAYLOAD... CRC16_LO CRC16_HI,总长 7+LEN。
  479. // 标签上报帧(0x0001)固定25字节,继电器指令应答等控制帧长度可变(如 0x0077 应答为9字节)。
  480. // 只有 CRC 校验通过才认帧,否则跳过 1 字节重新同步,避免半帧/应答帧污染拼包缓冲区。
  481. func extractUHFFrame(buffer []byte) (frame []byte, rest []byte, ok bool) {
  482. for {
  483. if len(buffer) == 0 {
  484. return nil, buffer, false
  485. }
  486. if buffer[0] != 0xCF {
  487. buffer = buffer[1:]
  488. continue
  489. }
  490. if len(buffer) < 7 {
  491. return nil, buffer, false
  492. }
  493. total := 7 + int(buffer[4])
  494. if total > 135 {
  495. // LEN 超出协议合理范围,视为伪帧头
  496. buffer = buffer[1:]
  497. continue
  498. }
  499. if len(buffer) < total {
  500. return nil, buffer, false
  501. }
  502. candidate := buffer[:total]
  503. recvCRC := binary.LittleEndian.Uint16(candidate[total-2:])
  504. calcCRC := uiCrc16Cal(candidate, uint8(total-2))
  505. if recvCRC != calcCRC {
  506. buffer = buffer[1:]
  507. continue
  508. }
  509. return candidate, buffer[total:], true
  510. }
  511. }
  512. // isTagReportFrame 判断是否为标签上报帧(命令字 0x0001,25字节)。
  513. func isTagReportFrame(frame []byte) bool {
  514. return len(frame) == 25 && frame[2] == 0x00 && frame[3] == 0x01
  515. }
  516. // parseReportData 解析上报数据。
  517. func parseReportData(deviceCode string, buf []byte) (*ReportData, error) {
  518. if len(buf) < 25 || buf[0] != 0xCF {
  519. return nil, errors.New("无效帧")
  520. }
  521. dataWithoutCRC := buf[:len(buf)-2]
  522. recvCRC := binary.LittleEndian.Uint16(buf[len(buf)-2:])
  523. calcCRC := uiCrc16Cal(dataWithoutCRC, uint8(len(dataWithoutCRC)))
  524. if recvCRC != calcCRC {
  525. return nil, errors.New("CRC校验失败")
  526. }
  527. rssi := int(buf[6])
  528. antenna := int(buf[22])
  529. epc := hex.EncodeToString(buf[11:23])
  530. return &ReportData{
  531. DeviceCode: deviceCode,
  532. Hex: hex.EncodeToString(buf),
  533. Epcs: []string{epc},
  534. RSSI: rssi,
  535. Antenna: antenna,
  536. Timestamp: time.Now().Unix(),
  537. }, nil
  538. }
  539. // handleReportData 处理UHF读取到的标签(精简版:只做解析+防抖,业务逻辑委托给 PassageService)
  540. func handleReportData(report *ReportData) {
  541. if len(report.Epcs) == 0 {
  542. return
  543. }
  544. epc := report.Epcs[0]
  545. if global.GVA_LOG != nil {
  546. global.GVA_LOG.Info("开始处理 RFID 通行",
  547. zap.String("device_code", report.DeviceCode),
  548. zap.String("epc", epc),
  549. zap.Int("rssi", report.RSSI),
  550. zap.Int("antenna", report.Antenna),
  551. )
  552. }
  553. // 委托给统一的进出场服务(所有触发方式共用同一入口)
  554. result, err := service.ServiceGroupApp.ParkingServiceGroup.PassageService.HandlePassage(common.PassageRequest{
  555. RFIDTag: epc,
  556. DeviceCode: report.DeviceCode,
  557. TriggerSource: "rfid",
  558. })
  559. if err != nil {
  560. if global.GVA_LOG != nil {
  561. global.GVA_LOG.Warn("RFID 通行处理失败",
  562. zap.String("device_code", report.DeviceCode),
  563. zap.String("epc", epc),
  564. zap.Error(err),
  565. )
  566. }
  567. if result != nil {
  568. PushChannelEvent(ChannelEvent{
  569. DeviceCode: report.DeviceCode, RFIDTag: epc, PlateNumber: result.PlateNumber,
  570. Direction: result.Direction, Timestamp: time.Now().Unix(), Status: result.GateStatus,
  571. Message: result.Message, Fee: result.Fee, StayTime: result.StayTime,
  572. })
  573. }
  574. return
  575. }
  576. if global.GVA_LOG != nil {
  577. global.GVA_LOG.Info("RFID 通行处理完成",
  578. zap.String("device_code", report.DeviceCode),
  579. zap.String("epc", epc),
  580. zap.String("direction", result.Direction),
  581. zap.String("plate_number", result.PlateNumber),
  582. zap.Uint("session_id", result.SessionID),
  583. zap.Float64("fee", result.Fee),
  584. zap.Bool("gate_opened", result.GateOpened),
  585. zap.String("gate_status", result.GateStatus),
  586. )
  587. }
  588. // 推送通道事件给前端
  589. var device dao.UHFReader
  590. if err := global.GVA_DB.Preload("Channel").First(&device, "device_code = ?", report.DeviceCode).Error; err == nil {
  591. PushChannelEvent(ChannelEvent{
  592. DeviceCode: report.DeviceCode,
  593. RFIDTag: epc,
  594. PlateNumber: result.PlateNumber,
  595. Direction: result.Direction,
  596. ChannelName: device.Channel.ChannelName,
  597. Timestamp: time.Now().Unix(),
  598. Status: "completed",
  599. Message: result.Message,
  600. Fee: result.Fee,
  601. StayTime: result.StayTime,
  602. })
  603. }
  604. }
  605. func readerTCPAddress(device *dao.UHFReader) string {
  606. if device.ConnectType != dao.ConnectTypeTCP {
  607. return ""
  608. }
  609. return fmt.Sprintf("%s:%d", device.IPAddress, device.Port)
  610. }
  611. // ==============================
  612. // 继电器控制(完整支持 Relay1 & Relay2)
  613. // ==============================
  614. const (
  615. RELAY_OP_RELEASE = 0x01
  616. RELAY_OP_CLOSE = 0x02
  617. )
  618. // CloseRelay1 开闸
  619. func (s *SerialReader) CloseRelay1(validTime byte) error {
  620. frame := buildRelayFrame(0x0077, RELAY_OP_CLOSE, validTime)
  621. _, err := s.SendAndRecv(frame)
  622. return err
  623. }
  624. // ReleaseRelay1
  625. func (s *SerialReader) ReleaseRelay1() error {
  626. frame := buildRelayFrame(0x0077, RELAY_OP_RELEASE, 0)
  627. _, err := s.SendAndRecv(frame)
  628. return err
  629. }
  630. // CloseRelay2 关闸
  631. func (s *SerialReader) CloseRelay2(validTime byte) error {
  632. frame := buildRelayFrame(0x0078, RELAY_OP_CLOSE, validTime)
  633. _, err := s.SendAndRecv(frame)
  634. return err
  635. }
  636. // ReleaseRelay2
  637. func (s *SerialReader) ReleaseRelay2() error {
  638. frame := buildRelayFrame(0x0078, RELAY_OP_RELEASE, 0)
  639. _, err := s.SendAndRecv(frame)
  640. return err
  641. }
  642. // buildRelayFrame 构建继电器控制指令(手册 2.2.14 / 2.2.15)。
  643. // 继电器号由命令字区分:0x0077=继电器1,0x0078=继电器2;
  644. // LEN 固定为 2,PAYLOAD = [Option, ValidTime],不含继电器号字节。
  645. func buildRelayFrame(cmd uint16, option byte, validTime byte) []byte {
  646. frame := []byte{
  647. 0xCF, 0xFF,
  648. byte(cmd >> 8), byte(cmd & 0xFF),
  649. 0x02, // len: Option + ValidTime
  650. option, // 0x01 释放 / 0x02 闭合
  651. validTime,
  652. }
  653. crc := uiCrc16Cal(frame, uint8(len(frame)))
  654. frame = append(frame, byte(crc&0xFF), byte(crc>>8))
  655. return frame
  656. }