|
@@ -1,6 +1,7 @@
|
|
|
package lc
|
|
package lc
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "encoding/hex"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/sirupsen/logrus"
|
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
|
@@ -8,11 +9,12 @@ import (
|
|
|
"net"
|
|
"net"
|
|
|
"strconv"
|
|
"strconv"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
+ "unicode/utf8"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// Screener 屏接口
|
|
// Screener 屏接口
|
|
|
type Screener interface {
|
|
type Screener interface {
|
|
|
- Display(int)
|
|
|
|
|
|
|
+ Display(Color, RunMode, DisplayMode, int, int, string)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type Screen struct {
|
|
type Screen struct {
|
|
@@ -22,24 +24,66 @@ type Screen struct {
|
|
|
liveState bool
|
|
liveState bool
|
|
|
StateInfo *bx.StateInfo //状态信息
|
|
StateInfo *bx.StateInfo //状态信息
|
|
|
Params *bx.Params //屏参
|
|
Params *bx.Params //屏参
|
|
|
|
|
+ sendQueue *CommandQueue // 发送队列,用于串行处理Send操作
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 初始化屏幕时创建并启动发送队列
|
|
|
func NewScreen(name string, ip, port string) *Screen {
|
|
func NewScreen(name string, ip, port string) *Screen {
|
|
|
s := &Screen{
|
|
s := &Screen{
|
|
|
Name: name,
|
|
Name: name,
|
|
|
Addr: fmt.Sprintf("%s:%s", ip, port),
|
|
Addr: fmt.Sprintf("%s:%s", ip, port),
|
|
|
StateInfo: &bx.StateInfo{},
|
|
StateInfo: &bx.StateInfo{},
|
|
|
Params: &bx.Params{},
|
|
Params: &bx.Params{},
|
|
|
|
|
+ sendQueue: NewCommandQueue(100), // 缓冲区大小100,可调整
|
|
|
}
|
|
}
|
|
|
|
|
+ // 启动队列,传入实际发送处理函数
|
|
|
|
|
+ s.sendQueue.Start(s.handleSend)
|
|
|
s.Reconnect()
|
|
s.Reconnect()
|
|
|
return s
|
|
return s
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (s *Screen) Display(id int) {
|
|
|
|
|
|
|
+// 实际执行发送的处理函数(由队列goroutine调用)
|
|
|
|
|
+func (s *Screen) handleSend(data []byte) {
|
|
|
|
|
+ if !s.getLiveState() || s.conn == nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ _, err := s.conn.Write(data)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ logrus.WithFields(map[string]interface{}{"设备名": s.Name}).Error("tcp write error:", err)
|
|
|
|
|
+ s.liveState = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //resp := s.ReadResp()
|
|
|
|
|
+ //if !resp.IsAck() {
|
|
|
|
|
+ // logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
|
|
|
|
|
+ // return
|
|
|
|
|
+ //}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 修改Send方法,将数据添加到队列而非直接发送
|
|
|
|
|
+func (s *Screen) Send(data []byte) {
|
|
|
if !s.getLiveState() {
|
|
if !s.getLiveState() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- s.SendRam(id)
|
|
|
|
|
|
|
+ // 将发送任务加入队列,由队列串行处理
|
|
|
|
|
+ s.sendQueue.AddTask(data)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加Close方法,用于释放队列资源
|
|
|
|
|
+func (s *Screen) Close() {
|
|
|
|
|
+ if s.sendQueue != nil {
|
|
|
|
|
+ s.sendQueue.Stop()
|
|
|
|
|
+ }
|
|
|
|
|
+ if s.conn != nil {
|
|
|
|
|
+ s.conn.Close()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (s *Screen) Display(color Color, runMode RunMode, displayMode DisplayMode, isProgram, speed int, text string) {
|
|
|
|
|
+ if !s.getLiveState() {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ s.SendRam(color, runMode, displayMode, isProgram, speed, text)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Correct 校正时间
|
|
// Correct 校正时间
|
|
@@ -53,11 +97,15 @@ func (s *Screen) Correct() {
|
|
|
s.Send(data.Pack())
|
|
s.Send(data.Pack())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Reconnect 重连
|
|
|
|
|
|
|
+// Reconnect方法中添加连接关闭时的队列处理(可选增强)
|
|
|
func (s *Screen) Reconnect() {
|
|
func (s *Screen) Reconnect() {
|
|
|
if s.getLiveState() {
|
|
if s.getLiveState() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ // 关闭旧连接(如果存在)
|
|
|
|
|
+ if s.conn != nil {
|
|
|
|
|
+ s.conn.Close()
|
|
|
|
|
+ }
|
|
|
conn, err := net.DialTimeout("tcp", s.Addr, 5*time.Second)
|
|
conn, err := net.DialTimeout("tcp", s.Addr, 5*time.Second)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
logrus.Error(s.Name, "-", s.Addr, "[屏]重连接失败! error:", err)
|
|
logrus.Error(s.Name, "-", s.Addr, "[屏]重连接失败! error:", err)
|
|
@@ -81,17 +129,17 @@ func (s *Screen) setConn(conn net.Conn) {
|
|
|
s.liveState = true
|
|
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
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+//// 给屏发送数据
|
|
|
|
|
+//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
|
|
|
|
|
+// }
|
|
|
|
|
+//}
|
|
|
|
|
|
|
|
//以下对协议进行封装
|
|
//以下对协议进行封装
|
|
|
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
|
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
|
@@ -115,60 +163,92 @@ const (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// 发送动态区节目 0正常页面 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)
|
|
|
|
|
|
|
+func (s *Screen) SendRam(color Color, runMode RunMode, displayMode DisplayMode, isProgram, speed int, text string) {
|
|
|
|
|
+
|
|
|
|
|
+ if isProgram == 0 {
|
|
|
|
|
+ file1 := FlashFile{}
|
|
|
|
|
+ file1.SetMsg(FormatSpeed(speed), color)
|
|
|
|
|
+ file1.SetMode(runMode, displayMode)
|
|
|
|
|
+ file1.SetOrigin(8, true, 0)
|
|
|
|
|
+ file1.SetArea(16, true, 32)
|
|
|
|
|
+ s.TextRam(file1, false)
|
|
|
|
|
+ } else if isProgram == 1 {
|
|
|
|
|
+ file1 := FlashFile{}
|
|
|
|
|
+ file1.SetMsg(text, color)
|
|
|
|
|
+ file1.SetMode(runMode, displayMode)
|
|
|
|
|
+ file1.SetOrigin(32, true, 4)
|
|
|
|
|
+ file1.SetArea(96, true, 32)
|
|
|
|
|
+ s.TextFlash([]FlashFile{file1}, "P000", false, false)
|
|
|
|
|
+ } else if isProgram == 2 {
|
|
|
|
|
+ file1 := FlashFile{}
|
|
|
|
|
+ file1.SetMsg(text, color)
|
|
|
|
|
+ file1.SetMode(runMode, displayMode)
|
|
|
|
|
+ file1.SetOrigin(32, true, 4)
|
|
|
|
|
+ file1.SetArea(96, true, 32)
|
|
|
|
|
+ s.TextRam(file1, true)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func FormatSpeed(speed int) string {
|
|
|
|
|
+ // 校验输入范围(仅处理0-99的整数)
|
|
|
|
|
+ if speed < 0 || speed > 99 {
|
|
|
|
|
+ return "0\\n0"
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理个位数(0-9)
|
|
|
|
|
+ if speed < 10 {
|
|
|
|
|
+ speedStr := strconv.Itoa(speed)
|
|
|
|
|
+ return fmt.Sprintf("%s\\n0", speedStr)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 处理十位数(10-99)
|
|
|
|
|
+ tens := speed / 10 // 提取十位数字(如21→2)
|
|
|
|
|
+ units := speed % 10 // 提取个位数字(如21→1)
|
|
|
|
|
+ return fmt.Sprintf("%d\\n%d", units, tens)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TextRam 发送动态区节目实现
|
|
// TextRam 发送动态区节目实现
|
|
|
-func (s *Screen) TextRam(ff FlashFile, needSpeak bool) {
|
|
|
|
|
|
|
+func (s *Screen) TextRam(ff FlashFile, isNumber bool) {
|
|
|
if !s.getLiveState() {
|
|
if !s.getLiveState() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
var areas []bx.BxArea
|
|
var areas []bx.BxArea
|
|
|
encoder := simplifiedchinese.GB18030.NewEncoder()
|
|
encoder := simplifiedchinese.GB18030.NewEncoder()
|
|
|
var bytes []byte
|
|
var bytes []byte
|
|
|
- if ff.color == Default {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ var id = 0
|
|
|
|
|
+
|
|
|
|
|
+ if ff.color == Default && isNumber == false {
|
|
|
bytes, _ = encoder.Bytes([]byte(ff.msg))
|
|
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)
|
|
|
|
|
|
|
+ } else if isNumber == true {
|
|
|
|
|
+ province, number := ParsePlateUTF8(ff.msg)
|
|
|
|
|
+ logrus.Info("车牌号", ff.msg)
|
|
|
|
|
+ logrus.Info("车牌号", "号码", number, "省份", province)
|
|
|
|
|
+ bytes, _ = encoder.Bytes([]byte("\\FO000\\C" + strconv.Itoa(int(ff.color)) + province + "\\FE001\\C" + strconv.Itoa(int(Green)) + number))
|
|
|
|
|
+ id = 1
|
|
|
|
|
+ } else if isNumber == false {
|
|
|
|
|
+ bytes, _ = encoder.Bytes([]byte("\\FE000\\C" + strconv.Itoa(int(ff.color)) + ff.msg))
|
|
|
}
|
|
}
|
|
|
|
|
+ area := bx.NewBxAreaProgram(byte(id), 1, byte(ff.dispMode), ff.originX, ff.originY, ff.width,
|
|
|
|
|
+ ff.height, bytes, false)
|
|
|
|
|
+ areas = append(areas, area)
|
|
|
|
|
|
|
|
//
|
|
//
|
|
|
cmd := bx.NewBxCmdSendDynamicArea(areas)
|
|
cmd := bx.NewBxCmdSendDynamicArea(areas)
|
|
|
pack := bx.NewBxDataPackCmd(cmd)
|
|
pack := bx.NewBxDataPackCmd(cmd)
|
|
|
- pack.SetDisplayType(1) //动态显示模式
|
|
|
|
|
|
|
+ pack.SetDisplayType(0) //动态显示模式
|
|
|
d := pack.Pack()
|
|
d := pack.Pack()
|
|
|
|
|
+ if isNumber {
|
|
|
|
|
+ logrus.Info(hex.EncodeToString(d))
|
|
|
|
|
+ }
|
|
|
s.Send(d)
|
|
s.Send(d)
|
|
|
resp := s.ReadResp()
|
|
resp := s.ReadResp()
|
|
|
if !resp.IsAck() {
|
|
if !resp.IsAck() {
|
|
|
- println("设备拒绝写文件! error:", resp.Error().Description)
|
|
|
|
|
|
|
+ logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- //s.StateInfo.DynaAreaNum++
|
|
|
|
|
|
|
+ s.StateInfo.DynaAreaNum++
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// DelRamText 删除动态区,不传删除所有
|
|
// DelRamText 删除动态区,不传删除所有
|
|
@@ -258,7 +338,7 @@ func (ft *FlashFile) SetArea(w uint16, yIsPixel bool, h uint16) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TextFlash 发送静态文件节目,掉电保存,文件名格式"P000","P001"
|
|
// TextFlash 发送静态文件节目,掉电保存,文件名格式"P000","P001"
|
|
|
-func (s *Screen) TextFlash(ft []FlashFile, name string, isLogo bool) {
|
|
|
|
|
|
|
+func (s *Screen) TextFlash(ft []FlashFile, name string, isNumber, isLogo bool) {
|
|
|
if !s.getLiveState() {
|
|
if !s.getLiveState() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -269,10 +349,15 @@ func (s *Screen) TextFlash(ft []FlashFile, name string, isLogo bool) {
|
|
|
var areas []bx.BxArea
|
|
var areas []bx.BxArea
|
|
|
for _, i := range ft {
|
|
for _, i := range ft {
|
|
|
var bytes []byte
|
|
var bytes []byte
|
|
|
- if i.color == Default {
|
|
|
|
|
|
|
+ if i.color == Default && isNumber == false {
|
|
|
bytes, _ = encoder.Bytes([]byte(i.msg))
|
|
bytes, _ = encoder.Bytes([]byte(i.msg))
|
|
|
- } else {
|
|
|
|
|
- bytes, _ = encoder.Bytes([]byte("\\C" + strconv.Itoa(int(i.color)) + i.msg))
|
|
|
|
|
|
|
+ } else if isNumber == true {
|
|
|
|
|
+ province, number := ParsePlateUTF8(i.msg)
|
|
|
|
|
+ logrus.Info("车牌号", i.msg)
|
|
|
|
|
+ logrus.Info("车牌号", "号码", number, "省份", province)
|
|
|
|
|
+ bytes, _ = encoder.Bytes([]byte("\\FO000\\C" + strconv.Itoa(int(i.color)) + province + "\\FE001\\C" + strconv.Itoa(int(Green)) + number))
|
|
|
|
|
+ } else if isNumber == false {
|
|
|
|
|
+ bytes, _ = encoder.Bytes([]byte("\\FO000\\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)
|
|
area := bx.NewBxAreaProgram(0xff, byte(i.runMode), byte(i.dispMode), i.originX, i.originY, i.width, i.height, bytes, false)
|
|
|
areas = append(areas, area)
|
|
areas = append(areas, area)
|
|
@@ -282,19 +367,29 @@ func (s *Screen) TextFlash(ft []FlashFile, name string, isLogo bool) {
|
|
|
cmd := file.NewCmdWriteFile()
|
|
cmd := file.NewCmdWriteFile()
|
|
|
pack := bx.NewBxDataPackCmd(cmd)
|
|
pack := bx.NewBxDataPackCmd(cmd)
|
|
|
data := pack.Pack()
|
|
data := pack.Pack()
|
|
|
|
|
+ if isNumber {
|
|
|
|
|
+ logrus.Info(hex.EncodeToString(data))
|
|
|
|
|
+ }
|
|
|
s.Send(data)
|
|
s.Send(data)
|
|
|
resp := s.ReadResp()
|
|
resp := s.ReadResp()
|
|
|
if !resp.IsAck() {
|
|
if !resp.IsAck() {
|
|
|
logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
|
|
logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- pack1 := bx.NewBxDataPackCmd(cmd)
|
|
|
|
|
- data1 := pack1.Pack()
|
|
|
|
|
- s.Send(data1)
|
|
|
|
|
- resp1 := s.ReadResp()
|
|
|
|
|
- if resp1.NoError() {
|
|
|
|
|
- s.StateInfo.ProgramNum++
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ //pack1 := bx.NewBxDataPackCmd(cmd)
|
|
|
|
|
+ //data1 := pack1.Pack()
|
|
|
|
|
+ //s.Send(data1)
|
|
|
|
|
+ //resp1 := s.ReadResp()
|
|
|
|
|
+ //if resp1.NoError() {
|
|
|
|
|
+ // s.StateInfo.ProgramNum++
|
|
|
|
|
+ //}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func ParsePlateUTF8(rawPlate string) (province, number string) {
|
|
|
|
|
+ firstRune, firstLen := utf8.DecodeRuneInString(rawPlate)
|
|
|
|
|
+ province = string(firstRune)
|
|
|
|
|
+ number = rawPlate[firstLen:]
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Bitmap 发送自定义位图节目
|
|
// Bitmap 发送自定义位图节目
|
|
@@ -320,7 +415,13 @@ func (s *Screen) Bitmap(name string, bitmap []byte) {
|
|
|
func (s *Screen) Lock(flag byte, name string) {
|
|
func (s *Screen) Lock(flag byte, name string) {
|
|
|
cmd := bx.NewCmdLock(flag, name)
|
|
cmd := bx.NewCmdLock(flag, name)
|
|
|
pack := bx.NewBxDataPackCmd(&cmd)
|
|
pack := bx.NewBxDataPackCmd(&cmd)
|
|
|
|
|
+ fmt.Println(hex.EncodeToString(pack.Pack()))
|
|
|
s.Send(pack.Pack())
|
|
s.Send(pack.Pack())
|
|
|
|
|
+ resp := s.ReadResp()
|
|
|
|
|
+ if !resp.IsAck() {
|
|
|
|
|
+ logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// DelFile 删除静态文件节目
|
|
// DelFile 删除静态文件节目
|