screen.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. package lc
  2. import (
  3. "encoding/hex"
  4. "fmt"
  5. "github.com/sirupsen/logrus"
  6. "golang.org/x/text/encoding/simplifiedchinese"
  7. "lc-smartX/bx"
  8. "net"
  9. "strconv"
  10. "time"
  11. "unicode/utf8"
  12. )
  13. // Screener 屏接口
  14. type Screener interface {
  15. Display(Color, RunMode, DisplayMode, int, int, string)
  16. }
  17. type Screen struct {
  18. Name string
  19. Addr string
  20. conn net.Conn
  21. liveState bool
  22. StateInfo *bx.StateInfo //状态信息
  23. Params *bx.Params //屏参
  24. sendQueue *CommandQueue // 发送队列,用于串行处理Send操作
  25. }
  26. // 初始化屏幕时创建并启动发送队列
  27. func NewScreen(name string, ip, port string) *Screen {
  28. s := &Screen{
  29. Name: name,
  30. Addr: fmt.Sprintf("%s:%s", ip, port),
  31. StateInfo: &bx.StateInfo{},
  32. Params: &bx.Params{},
  33. sendQueue: NewCommandQueue(100), // 缓冲区大小100,可调整
  34. }
  35. // 启动队列,传入实际发送处理函数
  36. s.sendQueue.Start(s.handleSend)
  37. s.Reconnect()
  38. return s
  39. }
  40. // 实际执行发送的处理函数(由队列goroutine调用)
  41. func (s *Screen) handleSend(data []byte) {
  42. if !s.getLiveState() || s.conn == nil {
  43. return
  44. }
  45. _, err := s.conn.Write(data)
  46. if err != nil {
  47. logrus.WithFields(map[string]interface{}{"设备名": s.Name}).Error("tcp write error:", err)
  48. s.liveState = false
  49. }
  50. //resp := s.ReadResp()
  51. //if !resp.IsAck() {
  52. // logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
  53. // return
  54. //}
  55. }
  56. // 修改Send方法,将数据添加到队列而非直接发送
  57. func (s *Screen) Send(data []byte) {
  58. if !s.getLiveState() {
  59. return
  60. }
  61. // 将发送任务加入队列,由队列串行处理
  62. s.sendQueue.AddTask(data)
  63. }
  64. // 添加Close方法,用于释放队列资源
  65. func (s *Screen) Close() {
  66. if s.sendQueue != nil {
  67. s.sendQueue.Stop()
  68. }
  69. if s.conn != nil {
  70. s.conn.Close()
  71. }
  72. }
  73. func (s *Screen) Display(color Color, runMode RunMode, displayMode DisplayMode, isProgram, speed int, text string) {
  74. if !s.getLiveState() {
  75. return
  76. }
  77. s.SendRam(color, runMode, displayMode, isProgram, speed, text)
  78. }
  79. // Correct 校正时间
  80. func (s *Screen) Correct() {
  81. if !s.getLiveState() {
  82. return
  83. }
  84. now := time.Now()
  85. cmd := bx.NewBxCmdSystemClockCorrect(now)
  86. data := bx.NewBxDataPackCmd(cmd)
  87. s.Send(data.Pack())
  88. }
  89. // Reconnect方法中添加连接关闭时的队列处理(可选增强)
  90. func (s *Screen) Reconnect() {
  91. if s.getLiveState() {
  92. return
  93. }
  94. // 关闭旧连接(如果存在)
  95. if s.conn != nil {
  96. s.conn.Close()
  97. }
  98. conn, err := net.DialTimeout("tcp", s.Addr, 5*time.Second)
  99. if err != nil {
  100. logrus.Error(s.Name, "-", s.Addr, "[屏]重连接失败! error:", err)
  101. return
  102. }
  103. logrus.Info(s.Name, "-", s.Addr, "[屏]连接成功!")
  104. s.setConn(conn)
  105. //读取屏信息
  106. state := s.State()
  107. s.StateInfo.Parse(state.Data)
  108. params := s.Param()
  109. s.Params.Parse(params.Data)
  110. }
  111. func (s *Screen) getLiveState() bool {
  112. return s.liveState
  113. }
  114. func (s *Screen) setConn(conn net.Conn) {
  115. s.conn = conn
  116. s.liveState = true
  117. }
  118. //// 给屏发送数据
  119. //func (s *Screen) Send(data []byte) {
  120. // if !s.getLiveState() {
  121. // return
  122. // }
  123. // _, err := s.conn.Write(data)
  124. // if err != nil {
  125. // logrus.WithFields(map[string]interface{}{"设备名": s.Name}).Error("tcp write error:", err)
  126. // s.liveState = false
  127. // }
  128. //}
  129. //以下对协议进行封装
  130. //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
  131. type Color byte
  132. const (
  133. Default Color = iota
  134. Red
  135. Green
  136. Yellow
  137. Blue
  138. LightBlue
  139. LightPurple
  140. White
  141. )
  142. const (
  143. Normal = 0
  144. Warn = 1
  145. )
  146. // 发送动态区节目 0正常页面 1红色提醒页面
  147. func (s *Screen) SendRam(color Color, runMode RunMode, displayMode DisplayMode, isProgram, speed int, text string) {
  148. if isProgram == 0 {
  149. file1 := FlashFile{}
  150. file1.SetMsg(FormatSpeed(speed), color)
  151. file1.SetMode(runMode, displayMode)
  152. file1.SetOrigin(8, true, 0)
  153. file1.SetArea(16, true, 32)
  154. s.TextRam(file1, false)
  155. } else if isProgram == 1 {
  156. file1 := FlashFile{}
  157. file1.SetMsg(text, color)
  158. file1.SetMode(runMode, displayMode)
  159. file1.SetOrigin(32, true, 4)
  160. file1.SetArea(96, true, 32)
  161. s.TextFlash([]FlashFile{file1}, "P000", false, false)
  162. } else if isProgram == 2 {
  163. file1 := FlashFile{}
  164. file1.SetMsg(text, color)
  165. file1.SetMode(runMode, displayMode)
  166. file1.SetOrigin(32, true, 4)
  167. file1.SetArea(96, true, 32)
  168. s.TextRam(file1, true)
  169. }
  170. }
  171. func FormatSpeed(speed int) string {
  172. // 校验输入范围(仅处理0-99的整数)
  173. if speed < 0 || speed > 99 {
  174. return "0\\n0"
  175. }
  176. // 处理个位数(0-9)
  177. if speed < 10 {
  178. speedStr := strconv.Itoa(speed)
  179. return fmt.Sprintf("%s\\n0", speedStr)
  180. }
  181. // 处理十位数(10-99)
  182. tens := speed / 10 // 提取十位数字(如21→2)
  183. units := speed % 10 // 提取个位数字(如21→1)
  184. return fmt.Sprintf("%d\\n%d", units, tens)
  185. }
  186. // TextRam 发送动态区节目实现
  187. func (s *Screen) TextRam(ff FlashFile, isNumber bool) {
  188. if !s.getLiveState() {
  189. return
  190. }
  191. var areas []bx.BxArea
  192. encoder := simplifiedchinese.GB18030.NewEncoder()
  193. var bytes []byte
  194. var id = 0
  195. if ff.color == Default && isNumber == false {
  196. bytes, _ = encoder.Bytes([]byte(ff.msg))
  197. } else if isNumber == true {
  198. province, number := ParsePlateUTF8(ff.msg)
  199. logrus.Info("车牌号", ff.msg)
  200. logrus.Info("车牌号", "号码", number, "省份", province)
  201. bytes, _ = encoder.Bytes([]byte("\\FO000\\C" + strconv.Itoa(int(ff.color)) + province + "\\FE001\\C" + strconv.Itoa(int(Green)) + number))
  202. id = 1
  203. } else if isNumber == false {
  204. bytes, _ = encoder.Bytes([]byte("\\FE000\\C" + strconv.Itoa(int(ff.color)) + ff.msg))
  205. }
  206. area := bx.NewBxAreaProgram(byte(id), 1, byte(ff.dispMode), ff.originX, ff.originY, ff.width,
  207. ff.height, bytes, false)
  208. areas = append(areas, area)
  209. //
  210. cmd := bx.NewBxCmdSendDynamicArea(areas)
  211. pack := bx.NewBxDataPackCmd(cmd)
  212. pack.SetDisplayType(0) //动态显示模式
  213. d := pack.Pack()
  214. if isNumber {
  215. logrus.Info(hex.EncodeToString(d))
  216. }
  217. s.Send(d)
  218. resp := s.ReadResp()
  219. if !resp.IsAck() {
  220. logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
  221. return
  222. }
  223. s.StateInfo.DynaAreaNum++
  224. }
  225. // DelRamText 删除动态区,不传删除所有
  226. func (s *Screen) DelRamText(numbers ...byte) {
  227. if !s.getLiveState() {
  228. return
  229. }
  230. cmd := bx.NewCmdDelDynamicArea(numbers)
  231. pack := bx.NewBxDataPackCmd(cmd)
  232. s.Send(pack.Pack())
  233. s.ReadResp()
  234. if len(numbers) == 0 {
  235. s.StateInfo.DynaAreaNum = 0
  236. } else {
  237. s.StateInfo.DynaAreaNum--
  238. }
  239. }
  240. type RunMode byte
  241. const (
  242. Loop RunMode = iota //循环
  243. LoopAndStayAtEnd //循环直到最后,停留在最后一个动态区
  244. LoopAndTimeoutOff //循环直到超时,超时后未更新不在显示
  245. LoopAndStayAtLogo //循环完后,停留显示LOGO
  246. LoopAndOff //循环完后不在显示
  247. LoopAndCountOff //循环设定次数后不在显示
  248. DefaultRunMode RunMode = Loop //默认
  249. )
  250. type DisplayMode byte
  251. const (
  252. _ DisplayMode = iota
  253. Static //0x01——静止显示
  254. QuickPunch //0x02——快速打出
  255. MoveLeft //0x03——向左移动
  256. MoveRight //0x04——向右移动
  257. MoveUp //0x05——向上移动
  258. MoveDown //0x06——向下移动
  259. Flicker //0x07——闪烁
  260. DefaultDisplayMode DisplayMode = Static
  261. )
  262. type FlashFile struct {
  263. msg string
  264. soundData string
  265. color Color
  266. runMode RunMode
  267. dispMode DisplayMode
  268. originX uint16
  269. originY uint16
  270. width uint16
  271. height uint16
  272. }
  273. func (ft *FlashFile) SetMsg(msg string, color Color) {
  274. ft.msg = msg
  275. ft.color = color
  276. }
  277. func (ft *FlashFile) SetMode(runMode RunMode, displayMode DisplayMode) {
  278. ft.runMode = runMode
  279. ft.dispMode = displayMode
  280. }
  281. func (ft *FlashFile) SetSoundData(soundData string) {
  282. ft.soundData = soundData
  283. }
  284. // SetOrigin xIsPixel=true表示x坐标为像素单位, =false表示以字节(8像素)为单位;y只有像素单位
  285. func (ft *FlashFile) SetOrigin(x uint16, xIsPixel bool, y uint16) {
  286. ft.originY = y
  287. if xIsPixel {
  288. ft.originX = 0x8000 | x
  289. return
  290. }
  291. ft.originX = x
  292. }
  293. // SetArea yIsPixel=true表示像素单位, =false表示以字节(8像素)为单位
  294. func (ft *FlashFile) SetArea(w uint16, yIsPixel bool, h uint16) {
  295. ft.height = h
  296. if yIsPixel {
  297. ft.width = 0x8000 | w
  298. return
  299. }
  300. ft.width = w
  301. }
  302. // TextFlash 发送静态文件节目,掉电保存,文件名格式"P000","P001"
  303. func (s *Screen) TextFlash(ft []FlashFile, name string, isNumber, isLogo bool) {
  304. if !s.getLiveState() {
  305. return
  306. }
  307. encoder := simplifiedchinese.GB18030.NewEncoder()
  308. if isLogo {
  309. name = "LOGO"
  310. }
  311. var areas []bx.BxArea
  312. for _, i := range ft {
  313. var bytes []byte
  314. if i.color == Default && isNumber == false {
  315. bytes, _ = encoder.Bytes([]byte(i.msg))
  316. } else if isNumber == true {
  317. province, number := ParsePlateUTF8(i.msg)
  318. logrus.Info("车牌号", i.msg)
  319. logrus.Info("车牌号", "号码", number, "省份", province)
  320. bytes, _ = encoder.Bytes([]byte("\\FO000\\C" + strconv.Itoa(int(i.color)) + province + "\\FE001\\C" + strconv.Itoa(int(Green)) + number))
  321. } else if isNumber == false {
  322. bytes, _ = encoder.Bytes([]byte("\\FO000\\C" + strconv.Itoa(int(i.color)) + i.msg))
  323. }
  324. area := bx.NewBxAreaProgram(0xff, byte(i.runMode), byte(i.dispMode), i.originX, i.originY, i.width, i.height, bytes, false)
  325. areas = append(areas, area)
  326. }
  327. file := bx.NewBxFile(name, "", areas)
  328. cmd := file.NewCmdWriteFile()
  329. pack := bx.NewBxDataPackCmd(cmd)
  330. data := pack.Pack()
  331. if isNumber {
  332. logrus.Info(hex.EncodeToString(data))
  333. }
  334. s.Send(data)
  335. resp := s.ReadResp()
  336. if !resp.IsAck() {
  337. logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
  338. return
  339. }
  340. //pack1 := bx.NewBxDataPackCmd(cmd)
  341. //data1 := pack1.Pack()
  342. //s.Send(data1)
  343. //resp1 := s.ReadResp()
  344. //if resp1.NoError() {
  345. // s.StateInfo.ProgramNum++
  346. //}
  347. }
  348. func ParsePlateUTF8(rawPlate string) (province, number string) {
  349. firstRune, firstLen := utf8.DecodeRuneInString(rawPlate)
  350. province = string(firstRune)
  351. number = rawPlate[firstLen:]
  352. return
  353. }
  354. // Bitmap 发送自定义位图节目
  355. func (s *Screen) Bitmap(name string, bitmap []byte) {
  356. file := bx.NewBitmapFile(name, bitmap)
  357. cmd := file.NewCmd()
  358. pack := bx.NewBxDataPackCmd(cmd)
  359. data := pack.Pack()
  360. s.Send(data)
  361. resp := s.ReadResp()
  362. if !resp.IsAck() {
  363. logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
  364. return
  365. }
  366. pack1 := bx.NewBxDataPackCmd(cmd)
  367. data1 := pack1.Pack()
  368. s.Send(data1)
  369. fmt.Printf("写图文件数据:% 02x\n", data1)
  370. s.ReadResp()
  371. }
  372. // Lock 锁定状态:0x00——解锁状态,0x01——锁定状态
  373. func (s *Screen) Lock(flag byte, name string) {
  374. cmd := bx.NewCmdLock(flag, name)
  375. pack := bx.NewBxDataPackCmd(&cmd)
  376. fmt.Println(hex.EncodeToString(pack.Pack()))
  377. s.Send(pack.Pack())
  378. resp := s.ReadResp()
  379. if !resp.IsAck() {
  380. logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
  381. return
  382. }
  383. }
  384. // DelFile 删除静态文件节目
  385. func (s *Screen) DelFile(delFiles ...string) {
  386. cmd := bx.NewCmdDeleteFile(delFiles)
  387. pack := bx.NewBxDataPackCmd(cmd)
  388. s.Send(pack.Pack())
  389. s.ReadResp()
  390. if len(delFiles) == 0 {
  391. s.StateInfo.ProgramNum = 0
  392. } else {
  393. s.StateInfo.ProgramNum--
  394. }
  395. }
  396. // DelText 删除动态区域节目
  397. func (s *Screen) DelText(delIds []byte) {
  398. cmd := bx.NewBxCmdSendDynamicArea(nil)
  399. cmd.SetDelAreaIds(delIds)
  400. pack := bx.NewBxDataPackCmd(cmd)
  401. s.Send(pack.Pack())
  402. }
  403. func (s *Screen) TurnOnOff(onOff bool) {
  404. if !s.getLiveState() {
  405. return
  406. }
  407. cmd := bx.NewBxCmdTurnOnOff(onOff)
  408. pack := bx.NewBxDataPackCmd(cmd)
  409. s.Send(pack.Pack())
  410. }
  411. // TimingSwitch 定时开关屏
  412. // 13:49 开, 13:55 关,最多设置3组
  413. //
  414. // onOffSet := [][2]uint64{
  415. // {1349, 1355},
  416. // }
  417. func (s *Screen) TimingSwitch(onOffSet [][2]uint64) {
  418. if !s.getLiveState() {
  419. return
  420. }
  421. cmd := bx.NewCmdTimingSwitch(onOffSet)
  422. pack := bx.NewBxDataPackCmd(cmd)
  423. s.Send(pack.Pack())
  424. }
  425. func (s *Screen) CancelTimingSwitch() {
  426. if !s.getLiveState() {
  427. return
  428. }
  429. cmd := bx.NewCmdCancelTimingSwitch()
  430. pack := bx.NewBxDataPackCmd(cmd)
  431. s.Send(pack.Pack())
  432. }
  433. func (s *Screen) State() *bx.BxResp {
  434. if !s.getLiveState() {
  435. return nil
  436. }
  437. cmd := bx.NewCmdState()
  438. pack := bx.NewBxDataPackCmd(cmd)
  439. s.Send(pack.Pack())
  440. r := s.ReadResp()
  441. return &r
  442. }
  443. func (s *Screen) Param() *bx.BxResp {
  444. if !s.getLiveState() {
  445. return nil
  446. }
  447. cmd := bx.NewCmdReadParams()
  448. pack := bx.NewBxDataPackCmd(cmd)
  449. s.Send(pack.Pack())
  450. r := s.ReadResp()
  451. return &r
  452. }
  453. func (s *Screen) Info() {
  454. s.StateInfo.Print(s.Name)
  455. }
  456. // ReadResp 读取响应
  457. func (s *Screen) ReadResp() bx.BxResp {
  458. var resp = make([]byte, 1024)
  459. read, err := s.conn.Read(resp)
  460. if err != nil {
  461. logrus.Error("读数据错误:", err)
  462. s.liveState = false
  463. return bx.BxResp{}
  464. }
  465. var bxResp = bx.BxResp{}
  466. parse := bxResp.Parse(resp, read)
  467. //if parse.IsAck() {
  468. // fmt.Println("response ACK")
  469. // fmt.Printf("原始响应数据:% 0x\n", resp[:read])
  470. // fmt.Println("解析响应数据:", parse)
  471. //} else if parse.IsInfo() {
  472. // fmt.Println("state ACK")
  473. // fmt.Printf("原始响应数据:% 0x\n", resp[:read])
  474. // fmt.Println("解析响应数据:", parse)
  475. //} else {
  476. // fmt.Println("response")
  477. // fmt.Printf("原始响应数据:% 0x\n", resp[:read])
  478. // fmt.Println("解析响应数据:", parse)
  479. //}
  480. return *parse
  481. }