package lc import ( "fmt" "github.com/sirupsen/logrus" "golang.org/x/text/encoding/simplifiedchinese" "lc-smartX/bx" "net" "strconv" "time" ) // Screener 屏接口 type Screener interface { Display(int) } type Screen struct { Name string Addr string conn net.Conn liveState bool StateInfo *bx.StateInfo //状态信息 Params *bx.Params //屏参 } func NewScreen(name string, ip, port string) *Screen { s := &Screen{ Name: name, Addr: fmt.Sprintf("%s:%s", ip, port), StateInfo: &bx.StateInfo{}, Params: &bx.Params{}, } s.Reconnect() return s } func (s *Screen) Display(id int) { if !s.getLiveState() { return } s.SendRam(id) } // Correct 校正时间 func (s *Screen) Correct() { if !s.getLiveState() { return } now := time.Now() cmd := bx.NewBxCmdSystemClockCorrect(now) data := bx.NewBxDataPackCmd(cmd) s.Send(data.Pack()) } // Reconnect 重连 func (s *Screen) Reconnect() { if s.getLiveState() { return } conn, err := net.DialTimeout("tcp", s.Addr, 5*time.Second) if err != nil { logrus.Error(s.Name, "-", s.Addr, "[屏]重连接失败! error:", err) return } logrus.Info(s.Name, "-", s.Addr, "[屏]连接成功!") s.setConn(conn) //读取屏信息 state := s.State() s.StateInfo.Parse(state.Data) params := s.Param() s.Params.Parse(params.Data) } func (s *Screen) getLiveState() bool { return s.liveState } func (s *Screen) setConn(conn net.Conn) { s.conn = conn s.liveState = true } // 给屏发送数据 func (s *Screen) Send(data []byte) { if !s.getLiveState() { return } _, err := s.conn.Write(data) if err != nil { logrus.WithFields(map[string]interface{}{"设备名": s.Name}).Error("tcp write error:", err) s.liveState = false } } //以下对协议进行封装 //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ type Color byte const ( Default Color = iota Red Green Yellow Blue LightBlue LightPurple White ) const ( Normal = 0 Warn = 1 ) // 发送动态区节目 0正常页面 1红色提醒页面 func (s *Screen) SendRam(id int) { file := FlashFile{} if id == 0 { file.SetMsg("减速慢行", Yellow) file.SetMode(DefaultRunMode, DefaultDisplayMode) file.SetOrigin(0, true, 0) file.SetArea(64, true, 16) s.TextRam(file, false) } else { file.SetMsg("支路来车", Red) file.SetSoundData("支路来车,请减速") file.SetMode(DefaultRunMode, DefaultDisplayMode) file.SetOrigin(0, true, 0) file.SetArea(64, true, 16) s.TextRam(file, true) } } // TextRam 发送动态区节目实现 func (s *Screen) TextRam(ff FlashFile, needSpeak bool) { if !s.getLiveState() { return } var areas []bx.BxArea encoder := simplifiedchinese.GB18030.NewEncoder() var bytes []byte if ff.color == Default { bytes, _ = encoder.Bytes([]byte(ff.msg)) } else { bytes, _ = encoder.Bytes([]byte("\\C" + strconv.Itoa(int(ff.color)) + ff.msg)) } if needSpeak { soundData, _ := encoder.Bytes([]byte(ff.soundData)) area := bx.NewBxAreaDynamic(0, 1, byte(ff.dispMode), ff.originX, ff.originY, ff.width, ff.height, bytes, soundData, false) areas = append(areas, area) } else { area := bx.NewBxAreaProgram(0, 1, byte(ff.dispMode), ff.originX, ff.originY, ff.width, ff.height, bytes, false) areas = append(areas, area) } // cmd := bx.NewBxCmdSendDynamicArea(areas) pack := bx.NewBxDataPackCmd(cmd) pack.SetDisplayType(1) //动态显示模式 d := pack.Pack() s.Send(d) resp := s.ReadResp() if !resp.IsAck() { println("设备拒绝写文件! error:", resp.Error().Description) return } //s.StateInfo.DynaAreaNum++ } // DelRamText 删除动态区,不传删除所有 func (s *Screen) DelRamText(numbers ...byte) { if !s.getLiveState() { return } cmd := bx.NewCmdDelDynamicArea(numbers) pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) s.ReadResp() if len(numbers) == 0 { s.StateInfo.DynaAreaNum = 0 } else { s.StateInfo.DynaAreaNum-- } } type RunMode byte const ( Loop RunMode = iota //循环 LoopAndStayAtEnd //循环直到最后,停留在最后一个动态区 LoopAndTimeoutOff //循环直到超时,超时后未更新不在显示 LoopAndStayAtLogo //循环完后,停留显示LOGO LoopAndOff //循环完后不在显示 LoopAndCountOff //循环设定次数后不在显示 DefaultRunMode RunMode = Loop //默认 ) type DisplayMode byte const ( _ DisplayMode = iota Static //0x01——静止显示 QuickPunch //0x02——快速打出 MoveLeft //0x03——向左移动 MoveRight //0x04——向右移动 MoveUp //0x05——向上移动 MoveDown //0x06——向下移动 Flicker //0x07——闪烁 DefaultDisplayMode DisplayMode = Static ) type FlashFile struct { msg string soundData string color Color runMode RunMode dispMode DisplayMode originX uint16 originY uint16 width uint16 height uint16 } func (ft *FlashFile) SetMsg(msg string, color Color) { ft.msg = msg ft.color = color } func (ft *FlashFile) SetMode(runMode RunMode, displayMode DisplayMode) { ft.runMode = runMode ft.dispMode = displayMode } func (ft *FlashFile) SetSoundData(soundData string) { ft.soundData = soundData } // SetOrigin xIsPixel=true表示x坐标为像素单位, =false表示以字节(8像素)为单位;y只有像素单位 func (ft *FlashFile) SetOrigin(x uint16, xIsPixel bool, y uint16) { ft.originY = y if xIsPixel { ft.originX = 0x8000 | x return } ft.originX = x } // SetArea yIsPixel=true表示像素单位, =false表示以字节(8像素)为单位 func (ft *FlashFile) SetArea(w uint16, yIsPixel bool, h uint16) { ft.height = h if yIsPixel { ft.width = 0x8000 | w return } ft.width = w } // TextFlash 发送静态文件节目,掉电保存,文件名格式"P000","P001" func (s *Screen) TextFlash(ft []FlashFile, name string, isLogo bool) { if !s.getLiveState() { return } encoder := simplifiedchinese.GB18030.NewEncoder() if isLogo { name = "LOGO" } var areas []bx.BxArea for _, i := range ft { var bytes []byte if i.color == Default { bytes, _ = encoder.Bytes([]byte(i.msg)) } else { bytes, _ = encoder.Bytes([]byte("\\C" + strconv.Itoa(int(i.color)) + i.msg)) } area := bx.NewBxAreaProgram(0xff, byte(i.runMode), byte(i.dispMode), i.originX, i.originY, i.width, i.height, bytes, false) areas = append(areas, area) } file := bx.NewBxFile(name, "", areas) cmd := file.NewCmdWriteFile() pack := bx.NewBxDataPackCmd(cmd) data := pack.Pack() s.Send(data) resp := s.ReadResp() if !resp.IsAck() { logrus.Error("设备拒绝写文件! error:", resp.Error().Description) return } pack1 := bx.NewBxDataPackCmd(cmd) data1 := pack1.Pack() s.Send(data1) resp1 := s.ReadResp() if resp1.NoError() { s.StateInfo.ProgramNum++ } } // Bitmap 发送自定义位图节目 func (s *Screen) Bitmap(name string, bitmap []byte) { file := bx.NewBitmapFile(name, bitmap) cmd := file.NewCmd() pack := bx.NewBxDataPackCmd(cmd) data := pack.Pack() s.Send(data) resp := s.ReadResp() if !resp.IsAck() { logrus.Error("设备拒绝写文件! error:", resp.Error().Description) return } pack1 := bx.NewBxDataPackCmd(cmd) data1 := pack1.Pack() s.Send(data1) fmt.Printf("写图文件数据:% 02x\n", data1) s.ReadResp() } // Lock 锁定状态:0x00——解锁状态,0x01——锁定状态 func (s *Screen) Lock(flag byte, name string) { cmd := bx.NewCmdLock(flag, name) pack := bx.NewBxDataPackCmd(&cmd) s.Send(pack.Pack()) } // DelFile 删除静态文件节目 func (s *Screen) DelFile(delFiles ...string) { cmd := bx.NewCmdDeleteFile(delFiles) pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) s.ReadResp() if len(delFiles) == 0 { s.StateInfo.ProgramNum = 0 } else { s.StateInfo.ProgramNum-- } } // DelText 删除动态区域节目 func (s *Screen) DelText(delIds []byte) { cmd := bx.NewBxCmdSendDynamicArea(nil) cmd.SetDelAreaIds(delIds) pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) } func (s *Screen) TurnOnOff(onOff bool) { if !s.getLiveState() { return } cmd := bx.NewBxCmdTurnOnOff(onOff) pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) } // TimingSwitch 定时开关屏 // 13:49 开, 13:55 关,最多设置3组 // // onOffSet := [][2]uint64{ // {1349, 1355}, // } func (s *Screen) TimingSwitch(onOffSet [][2]uint64) { if !s.getLiveState() { return } cmd := bx.NewCmdTimingSwitch(onOffSet) pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) } func (s *Screen) CancelTimingSwitch() { if !s.getLiveState() { return } cmd := bx.NewCmdCancelTimingSwitch() pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) } func (s *Screen) State() *bx.BxResp { if !s.getLiveState() { return nil } cmd := bx.NewCmdState() pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) r := s.ReadResp() return &r } func (s *Screen) Param() *bx.BxResp { if !s.getLiveState() { return nil } cmd := bx.NewCmdReadParams() pack := bx.NewBxDataPackCmd(cmd) s.Send(pack.Pack()) r := s.ReadResp() return &r } func (s *Screen) Info() { s.StateInfo.Print(s.Name) } // ReadResp 读取响应 func (s *Screen) ReadResp() bx.BxResp { var resp = make([]byte, 1024) read, err := s.conn.Read(resp) if err != nil { logrus.Error("读数据错误:", err) s.liveState = false return bx.BxResp{} } var bxResp = bx.BxResp{} parse := bxResp.Parse(resp, read) //if parse.IsAck() { // fmt.Println("response ACK") // fmt.Printf("原始响应数据:% 0x\n", resp[:read]) // fmt.Println("解析响应数据:", parse) //} else if parse.IsInfo() { // fmt.Println("state ACK") // fmt.Printf("原始响应数据:% 0x\n", resp[:read]) // fmt.Println("解析响应数据:", parse) //} else { // fmt.Println("response") // fmt.Printf("原始响应数据:% 0x\n", resp[:read]) // fmt.Println("解析响应数据:", parse) //} return *parse }