Browse Source

发送内码文字、远程喊话功能、连接待优化

chengqian 5 tháng trước cách đây
mục cha
commit
0f6ad3b998

+ 49 - 1
server/api/v1/devices/dev_screens.go

@@ -5,6 +5,7 @@ import (
 	"go.uber.org/zap"
 	"server/dao/devices"
 	"server/global"
+	promodel "server/model/common/devices"
 	"server/model/common/request"
 	"server/model/common/response"
 	model "server/model/devices"
@@ -142,7 +143,7 @@ func (s *ScreensApi) UpdateScreens(c *gin.Context) {
 
 // 熄屏-亮屏幕
 func (s *ScreensApi) SwitchScreens(c *gin.Context) {
-	var swch model.SwitchScreens
+	var swch promodel.SwitchScreens
 	err := c.ShouldBindJSON(&swch)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
@@ -156,3 +157,50 @@ func (s *ScreensApi) SwitchScreens(c *gin.Context) {
 	}
 	response.OkWithMessage("操作成功", c)
 }
+
+// 发送内码文字
+func (s *ScreensApi) SendInternalCode(c *gin.Context) {
+	var ic model.ReqInCode
+	err := c.ShouldBindJSON(&ic)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	var inCodeArr []promodel.InternalCodeContent
+	for _, content := range ic.Content {
+		inCode := promodel.InternalCodeContent{Text: content.Text, Color: 0x01, Size: 0x12} //默认红色,大小24号
+		if content.Color == "绿色" {
+			inCode.Color = 0x02
+		}
+		if content.Color == "黄色" {
+			inCode.Color = 0x03
+		}
+		inCodeArr = append(inCodeArr, inCode)
+	}
+
+	err = ScreensService.SendInternalCode(ic.Sn, inCodeArr)
+	if err != nil {
+		global.GVA_LOG.Error("操作失败!", zap.Error(err))
+		response.FailWithMessage("操作失败", c)
+		return
+	}
+	response.OkWithMessage("操作成功", c)
+}
+
+// 远程喊话
+func (s *ScreensApi) VoiceBroad(c *gin.Context) {
+	var vb promodel.VoiceBroad
+	err := c.ShouldBindJSON(&vb)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = ScreensService.VoiceBroad(vb.Sn, vb.BroadContent)
+	if err != nil {
+		global.GVA_LOG.Error("操作失败!", zap.Error(err))
+		response.FailWithMessage("操作失败", c)
+		return
+	}
+	response.OkWithMessage("操作成功", c)
+}

+ 6 - 12
server/dao/devices/dev_screens.go

@@ -110,18 +110,12 @@ func UpdateScreensStatusByGatewayId(id, status int) error {
 
 func UpdateScreensStatus(code string, sta request.DeviceStatus) error {
 	var screens Screens
-	//err := global.GVA_DB.Model(&screens).
-	//	Select("status").
-	//	Where("screens_code = ?", code).
-	//	Updates(map[string]interface{}{
-	//		"status": status,
-	//	}).Error
-	//return err
-	return global.GVA_DB.Model(&screens).Debug().Where("screens_code = ?", code).Updates(Screens{
-		Status:        sta.Status,
-		PlayStatus:    sta.PlayStatus,
-		DisplayStatus: sta.DisplayStatus,
-		SourceStatus:  sta.SourceStatus,
+	return global.GVA_DB.Model(&screens).Debug().Select("status", "play_status", "display_status", "source_status").
+		Where("screens_code = ?", code).Updates(map[string]interface{}{
+		"status":         sta.Status,
+		"play_status":    sta.PlayStatus,
+		"display_status": sta.DisplayStatus,
+		"source_status":  sta.SourceStatus,
 	}).Error
 }
 

+ 4 - 4
server/go.mod

@@ -23,6 +23,7 @@ require (
 	github.com/google/uuid v1.3.0
 	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible
 	github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84
+	github.com/json-iterator/go v1.1.12
 	github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
 	github.com/mojocn/base64Captcha v1.3.6
 	github.com/pkg/errors v0.9.1
@@ -41,12 +42,13 @@ require (
 	github.com/swaggo/swag v1.16.2
 	github.com/tencentyun/cos-go-sdk-v5 v0.7.42
 	github.com/unrolled/secure v1.13.0
+	github.com/valyala/bytebufferpool v1.0.0
 	go.mongodb.org/mongo-driver v1.12.1
 	go.uber.org/automaxprocs v1.5.3
 	go.uber.org/zap v1.24.0
 	golang.org/x/crypto v0.24.0
-	golang.org/x/sync v0.7.0
-	golang.org/x/text v0.16.0
+	golang.org/x/sync v0.8.0
+	golang.org/x/text v0.19.0
 	gopkg.in/yaml.v2 v2.4.0
 	gorm.io/driver/mysql v1.5.6
 	gorm.io/driver/postgres v1.5.7
@@ -96,7 +98,6 @@ require (
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jonboulle/clockwork v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
-	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.15.9 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
@@ -126,7 +127,6 @@ require (
 	github.com/tklauser/numcpus v0.6.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.11 // indirect
-	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	go.uber.org/atomic v1.9.0 // indirect

+ 4 - 4
server/go.sum

@@ -621,8 +621,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -700,8 +700,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

+ 22 - 0
server/model/common/devices/pro_screens.go

@@ -0,0 +1,22 @@
+package devices
+
+/**
+这里的结构体用于请求和封装协议(公用结构体)
+*/
+
+type SwitchScreens struct {
+	Sn      string `json:"sn"`
+	Network int    `json:"network"`
+	Flag    int    `json:"flag"`
+}
+
+type InternalCodeContent struct {
+	Text  string `json:"text"`  //文字内容
+	Color byte   `json:"color"` //文字颜色
+	Size  byte   `json:"size"`  //字体字号
+}
+
+type VoiceBroad struct {
+	Sn           string `json:"sn"`
+	BroadContent string `json:"broadContent"`
+}

+ 9 - 4
server/model/devices/dev_screens.go

@@ -28,8 +28,13 @@ type ReqScreens struct {
 	InstallTime   time.Time `json:"installTime"`   //安装时间
 }
 
-type SwitchScreens struct {
-	Sn      string `json:"sn"`
-	Network int    `json:"network"`
-	Flag    int    `json:"flag"`
+type ReqInCode struct {
+	Sn      string    `json:"sn"`      //设备sn
+	Content []Content `json:"content"` //发送的内容
+}
+
+type Content struct {
+	Text  string `json:"text"`  //文字内容
+	Color string `json:"color"` //文字颜色
+	Size  string `json:"size"`  //文字尺寸
 }

+ 0 - 34
server/protocol/radar_test.go

@@ -1,34 +0,0 @@
-package protocol
-
-import (
-	"testing"
-)
-
-//buf.Write([]byte{0xFE, 0x5C, 0x4B, 0x89, 0x2A, 0x00, 0x00, 0x00})
-//fmt.Println("buf", buf.Bytes())
-//
-//fmt.Println("转换", hex.EncodeToString(buf.Bytes()))
-//
-//reply, _ := hex.DecodeString(
-//"fe5c4b892a000000")
-//fmt.Println("reply", reply)
-
-func Test_verify(t *testing.T) {
-	//login := AuthDataPack{}.AuthLogin()
-	////fe5c4b89 2a000000 62 00000000 17000000 31 23 32303234 30393237 3035 3137 3230 3031 23 303630 23ffff
-	////fe5c4b89 2a000000 62 00000000 17000000 31 23 32303234 30393239 3037 3137 3237 3535 23 303630 23ffff
-	//
-	//fmt.Println("loginBuf", login.Bytes())
-	//toString := hex.EncodeToString(login.Bytes())
-	//fmt.Println("toString", toString)
-	//reply, _ := hex.DecodeString(toString)
-	//fmt.Println("reply", reply)
-
-	//reply, _ := hex.DecodeString("fe5c4b8913000000520000000000000000ffff")
-	//conn := tcp.DeviceConn["0800075608016688"]
-	//if conn == nil {
-	//	fmt.Println("Connection is nil")
-	//	return
-	//}
-	//conn.Write(reply)
-}

+ 67 - 12
server/protocol/screens_model.go

@@ -1,26 +1,69 @@
 package protocol
 
-// 请求包结构体
-type ReqDataPack struct {
+// 远程喊话结构体
+type VoiceBroadDataPack struct {
+	Head        []byte //包头 固定值0xFE 0x5C 0x4B 0x89
+	Len         []byte //报文总长度
+	Type        byte   //消息类型
+	ForwardPort byte   //转发端口
+	Id          []byte //发送ID
+	DataLen     []byte //控制指令长度
+	Data        []byte //控制指令内容
+	End         []byte //包尾固定值 0xFF 0xFF
+}
+
+// 远程喊话控制指令内容
+type VoiceBroadData struct {
+	FrameHeader       byte   //帧头 固定值0xFD
+	BroadContentLen   []byte //语音内容长度
+	BroadWord         byte   //语音播放命令字
+	BroadEncodeFormat []byte //语音文本编码格式 和 语音标记信息
+	BroadContent      []byte //语音文本内容
+	ReservedWord      []byte //保留字
+}
+
+// 发送内码文字结构体
+type InternalCodeDataPack struct {
 	Head    []byte //包头 固定值0xFE 0x5C 0x4B 0x89
 	Len     []byte //报文总长度
 	Type    byte   //消息类型
 	Id      []byte //发送ID
 	DataLen []byte //控制指令长度
 	Data    []byte //控制指令内容
+	EndSign []byte //素材发送结束标志 固定值0x2D 0x31 0x2C
 	End     []byte //包尾固定值 0xFF 0xFF
 }
 
-// 答复包结构体
-type RspDataPack struct {
-	Head      []byte //包头
-	Len       []byte //报文总长度
-	Type      byte   //消息类型
-	Id        []byte //消息ID
-	DataLen   []byte //消息内容长度
-	Confirm   byte   //确认内容
-	Separator byte   //分隔符
-	End       []byte //包尾
+// 内码文字控制指令内容
+type InternalCodeData struct {
+	MaterialId          []byte //素材ID
+	Separator           byte   //分隔符
+	DisplayMode         byte   //显示方式
+	DisplaySpeed        byte   //显示速度
+	StopTime            byte   //停留时间
+	PlayingPeriod       []byte //播放时间段
+	MaterialAttribLen   []byte //素材属性长度
+	MaterialStartFlag   []byte //素材起始标志
+	ReservedWord1       byte   //保留字1
+	TextRotation        byte   //文字旋转
+	MaterialAttrib      byte   //素材属性
+	MaterialStorageMode byte   //素材存储方式
+	MaterialUpdateMode  byte   //素材更新方式
+	TextStartSign       byte   //文本起始传送标志
+	ScreenColor         byte   //显示屏颜色
+	PictureCodeMode     byte   //图片编码方式
+	ReservedWord2       []byte //保留字2
+	Width               []byte //区域宽度
+	Height              []byte //区域高度
+	CharacterColor      byte   //字符颜色
+	TextSize            byte   //字体字号
+	ReservedWord3       byte   //保留字3
+	MaterialContentLen  []byte //素材内容长度
+	MaterialContent     []byte //素材内容
+	ControlCode1        []byte //传送过程控制码1
+	ControlCode2        []byte //传送过程控制码2
+	ControlCode3        []byte //传送过程控制码3
+	ReservedWord4       []byte //保留字4
 }
 
 // 认证登录结构体
@@ -47,3 +90,15 @@ type SwitchDataPack struct {
 	Reserve []byte //保留值  固定值00 00 00 00
 	End     []byte //包尾固定值 0xFF 0xFF
 }
+
+// 答复包结构体
+type RspDataPack struct {
+	Head      []byte //包头
+	Len       []byte //报文总长度
+	Type      byte   //消息类型
+	Id        []byte //消息ID
+	DataLen   []byte //消息内容长度
+	Confirm   byte   //确认内容
+	Separator byte   //分隔符
+	End       []byte //包尾
+}

+ 231 - 0
server/protocol/screens_service.go

@@ -1,8 +1,14 @@
 package protocol
 
 import (
+	"bytes"
+	"encoding/binary"
 	"fmt"
 	"github.com/valyala/bytebufferpool"
+	"golang.org/x/text/encoding/simplifiedchinese"
+	"golang.org/x/text/transform"
+	"io/ioutil"
+	promodel "server/model/common/devices"
 	"strconv"
 	"time"
 )
@@ -62,6 +68,165 @@ func (x SwitchDataPack) SwitchScreens() *bytebufferpool.ByteBuffer {
 	return buf
 }
 
+// 发送内码文字
+func (x InternalCodeDataPack) SendInternalCode(content []promodel.InternalCodeContent) *bytebufferpool.ByteBuffer {
+	buf := bytebufferpool.Get()
+	x.Head = []byte{0xFE, 0x5C, 0x4B, 0x89}
+
+	x.Type = 0x31
+	x.Id = []byte{0x00, 0x00, 0x00, 0x00}
+
+	//封装控制指令内容
+	for i, codeContent := range content {
+		//拼接素材ID 000000001
+		UidStr := fmt.Sprintf("%09d", i+1)
+		//给每一项加0x30
+		uidByte := EachItemAdd(UidStr)
+
+		//将内容转GB2312编码格式
+		gbk, contentLen, _ := Utf8ToGbk([]byte(codeContent.Text))
+
+		//获取素材内容长度
+		contentLenBytes := GetBytesArrYes(contentLen+10, 4)
+
+		ICD := InternalCodeData{
+			MaterialId:         uidByte,                   //素材ID
+			CharacterColor:     codeContent.Color,         //字符颜色
+			TextSize:           codeContent.Size,          //字体大小
+			ControlCode2:       []byte{byte(i + 1), 0x00}, //控制码2
+			MaterialContentLen: contentLenBytes,           //素材内容长度
+			MaterialContent:    gbk,                       //素材内容
+		}
+		ContentBuf := ICD.AppendInternalCodeContent()
+		x.Data = append(x.Data, ContentBuf.Bytes()...)
+	}
+	x.Len = GetBytesArrYes(uint8(len(x.Data))+22, 4)
+	x.DataLen = GetBytesArrYes(uint8(len(x.Data))+3, 4)
+
+	x.EndSign = []byte{0x2D, 0x31, 0x2C}
+	x.End = []byte{0xFF, 0xFF}
+
+	buf.Write(x.Head)
+	buf.Write(x.Len)
+	buf.WriteByte(x.Type)
+	buf.Write(x.Id)
+	buf.Write(x.DataLen)
+	buf.Write(x.Data)
+	buf.Write(x.EndSign)
+	buf.Write(x.End)
+	return buf
+}
+
+func (x VoiceBroadDataPack) VoiceBroad(broad string) *bytebufferpool.ByteBuffer {
+	buf := bytebufferpool.Get()
+	x.Head = []byte{0xFE, 0x5C, 0x4B, 0x89}
+	x.Type = 0x68
+	x.ForwardPort = 0x02 //转发端口
+	x.Id = []byte{0x00, 0x00, 0x00}
+	x.End = []byte{0xFF, 0xFF}
+
+	//将语音内容字符串转字节数组,并获取其长度供给内容长度使用
+	gbk, contentLen, _ := Utf8ToGbk([]byte(broad))
+	contentLenBytes := GetBytesArrNo(contentLen+16, 2)
+
+	data := VoiceBroadData{
+		BroadContentLen: contentLenBytes,
+		BroadContent:    gbk,
+	}
+	contentByte := data.AppendVoiceBroadContent()
+
+	x.Data = contentByte.Bytes()
+
+	//帧头到保留字刚好是控制指令长度
+	x.DataLen = GetBytesArrYes(uint8(len(x.Data)), 4)
+	x.Len = GetBytesArrYes(uint8(len(x.Data)+19), 4)
+
+	buf.Write(x.Head)
+	buf.Write(x.Len)
+	buf.WriteByte(x.Type)
+	buf.WriteByte(x.ForwardPort)
+	buf.Write(x.Id)
+	buf.Write(x.DataLen)
+	buf.Write(x.Data)
+	buf.Write(x.End)
+	return buf
+}
+
+func (x VoiceBroadData) AppendVoiceBroadContent() *bytebufferpool.ByteBuffer {
+	buf := bytebufferpool.Get()
+	x.FrameHeader = 0xFD
+	x.BroadWord = 0x01
+	x.BroadEncodeFormat = []byte{0x00, //编码格式
+		0x5B, 0x6D, 0x35, 0x32, 0x5D, //男2声
+		0x5B, 0x73, 0x35, 0x5D, //中语速
+		0x5B, 0x76, 0x31, 0x30, 0x5D} //高音量
+	x.ReservedWord = []byte{0x00, 0x00}
+
+	buf.WriteByte(x.FrameHeader)
+	buf.Write(x.BroadContentLen)
+	buf.WriteByte(x.BroadWord)
+	buf.Write(x.BroadEncodeFormat)
+	buf.Write(x.BroadContent)
+	buf.Write(x.ReservedWord)
+	return buf
+}
+
+func (x InternalCodeData) AppendInternalCodeContent() *bytebufferpool.ByteBuffer {
+	buf := bytebufferpool.Get()
+	x.Separator = 0x2C
+	x.DisplayMode = 0x09
+	x.DisplaySpeed = 0x01
+	x.StopTime = 0xFF // 静止显示
+	x.PlayingPeriod = []byte{0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31}
+	x.MaterialAttribLen = []byte{0x13, 0x00, 0x00, 0x00}
+	x.MaterialStartFlag = []byte{0x55, 0xAA}
+	x.ReservedWord1 = 0x00
+	x.TextRotation = 0x01
+	x.MaterialAttrib = 0x37
+	x.MaterialStorageMode = 0x32
+	x.MaterialUpdateMode = 0x31
+	x.TextStartSign = 0x31
+	x.ScreenColor = 0x32
+	x.PictureCodeMode = 0x31
+	x.ReservedWord2 = []byte{0x00, 0x00}
+	x.Width = []byte{0x0C, 0x00}  //宽:96/8=12 也就是0x0C
+	x.Height = []byte{0x18, 0x00} //高:24 也就是0x18
+	x.ReservedWord3 = 0x00
+	x.ControlCode1 = []byte{0xFF, 0x00}
+	x.ControlCode3 = []byte{0x01, 0x00, 0x01, 0x00}
+	x.ReservedWord4 = []byte{0x00, 0x00}
+
+	buf.Write(x.MaterialId)
+	buf.WriteByte(x.Separator)
+	buf.WriteByte(x.DisplayMode)
+	buf.WriteByte(x.DisplaySpeed)
+	buf.WriteByte(x.StopTime)
+	buf.Write(x.PlayingPeriod)
+	buf.Write(x.MaterialAttribLen)
+	buf.Write(x.MaterialStartFlag)
+	buf.WriteByte(x.ReservedWord1)
+	buf.WriteByte(x.TextRotation)
+	buf.WriteByte(x.MaterialAttrib)
+	buf.WriteByte(x.MaterialStorageMode)
+	buf.WriteByte(x.MaterialUpdateMode)
+	buf.WriteByte(x.TextStartSign)
+	buf.WriteByte(x.ScreenColor)
+	buf.WriteByte(x.PictureCodeMode)
+	buf.Write(x.ReservedWord2)
+	buf.Write(x.Width)
+	buf.Write(x.Height)
+	buf.WriteByte(x.CharacterColor)
+	buf.WriteByte(x.TextSize)
+	buf.WriteByte(x.ReservedWord3)
+	buf.Write(x.MaterialContentLen)
+	buf.Write(x.MaterialContent)
+	buf.Write(x.ControlCode1)
+	buf.Write(x.ControlCode2)
+	buf.Write(x.ControlCode3)
+	buf.Write(x.ReservedWord4)
+	return buf
+}
+
 func GetNowDate() (dateSlice [16]int) {
 	now := time.Now()
 	year, month, day := now.Date()
@@ -86,3 +251,69 @@ func GetNowDate() (dateSlice [16]int) {
 	}
 	return dateSlice
 }
+
+func Utf8ToGbk(s []byte) ([]byte, uint8, error) {
+	reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder())
+	d, e := ioutil.ReadAll(reader)
+	u := uint8(len(d))
+	if e != nil {
+		return nil, u, e
+	}
+	return d, u, nil
+}
+
+// 传入数字字符串给每一项加0x30
+func EachItemAdd(strArr string) []byte {
+	var byteArr = make([]byte, len(strArr))
+	for i, _ := range strArr {
+		num, _ := strconv.Atoi(string(strArr[i]))
+		byteArr[i] = byte(num) + 0x30
+	}
+	return byteArr
+}
+
+// 获取字节数组(低字节在前,高字节在后,要倒转)
+func GetBytesArrYes(param uint8, ByteLen int) []byte {
+	byteArray := make([]byte, ByteLen)
+	// 将输入的整数转换为字节并存储在字节数组中
+	if ByteLen >= 4 {
+		binary.BigEndian.PutUint32(byteArray[ByteLen-4:], uint32(param))
+	} else {
+		for i := 0; i < ByteLen; i++ {
+			byteArray[ByteLen-1-i] = byte(param >> (8 * i)) // 右移并放置到正确位置
+		}
+	}
+	return Rotate(byteArray)
+}
+
+// 获取字节数组(低字节在后,高字节在前,不需要倒转)
+func GetBytesArrNo(param uint8, ByteLen int) []byte {
+	byteArray := make([]byte, ByteLen)
+	// 将输入的整数转换为字节并存储在字节数组中
+	if ByteLen >= 4 {
+		binary.BigEndian.PutUint32(byteArray[ByteLen-4:], uint32(param))
+	} else {
+		for i := 0; i < ByteLen; i++ {
+			byteArray[ByteLen-1-i] = byte(param >> (8 * i)) // 右移并放置到正确位置
+		}
+	}
+	return byteArray
+}
+
+// 低位字节在前,高位字节在后
+func Rotate(ss []byte) []byte {
+	ret := make([]byte, len(ss))
+	copy(ret, ss)
+	left, rigth := 0, 0
+	if len(ret)%2 == 0 {
+		left = len(ret)/2 - 1
+		rigth = len(ret) / 2
+	} else {
+		left = len(ret)/2 - 1
+		rigth = len(ret)/2 + 1
+	}
+	for ; left >= 0 || rigth <= len(ret)-1; left, rigth = left-1, rigth+1 {
+		ret[left], ret[rigth] = ret[rigth], ret[left]
+	}
+	return ret
+}

+ 50 - 0
server/radar_test.go

@@ -0,0 +1,50 @@
+package main
+
+import (
+	"encoding/hex"
+	"fmt"
+	"server/protocol"
+	"testing"
+)
+
+// buf.Write([]byte{0xFE, 0x5C, 0x4B, 0x89, 0x2A, 0x00, 0x00, 0x00})
+// fmt.Println("buf", buf.Bytes())
+//
+// fmt.Println("转换", hex.EncodeToString(buf.Bytes()))
+//
+// reply, _ := hex.DecodeString(
+// "fe5c4b892a000000")
+// fmt.Println("reply", reply)
+// encodeToGBK 将输入字符串编码为 GBK 字节切片
+func Test_verify(t *testing.T) {
+	//pack := protocol.InternalCodeDataPack{}
+	//
+	//contents := []promodel.InternalCodeContent{}
+	//
+	//c1 := promodel.InternalCodeContent{
+	//	Text:  "车牌:粤AB31Y9",
+	//	Color: 0x01,
+	//	Size:  0x11,
+	//}
+	//c2 := promodel.InternalCodeContent{
+	//	Text:  "金额:176.8元",
+	//	Color: 0x01,
+	//	Size:  0x11,
+	//}
+	//contents = append(contents, c1, c2)
+	//code := pack.SendInternalCode(contents)
+	//toString := hex.EncodeToString(code.Bytes())
+	//fmt.Println("======", toString)
+
+	//rotate := protocol.Rotate([]byte{0x00, 0x06, 0x00, 0x17})
+	//fmt.Println("rotate", rotate)
+
+	//远程喊话
+
+	no := protocol.GetBytesArrNo(30, 2)
+	fmt.Println("no", no)
+	pack := protocol.VoiceBroadDataPack{}
+	broad := pack.VoiceBroad("语音测试abc123")
+	fmt.Println("======", hex.EncodeToString(broad.Bytes()))
+
+}

+ 7 - 4
server/router/devices/dev_screens.go

@@ -15,10 +15,13 @@ func (l *ScreensRouter) InitScreensRouter(Router *gin.RouterGroup, RouterPub *gi
 	apiPublicRouterWithoutRecord := RouterPub.Group("screens")
 	baseApi := v1.ApiGroupApp.DevicesApiGroup.ScreensApi
 	{
-		screensRouter.PUT("updateScreens", baseApi.UpdateScreens)  //编辑显示屏
-		screensRouter.POST("addScreens", baseApi.AddScreens)       //添加显示屏
-		screensRouter.DELETE("delScreens", baseApi.DelScreens)     // 删除显示屏
-		screensRouter.POST("switchScreens", baseApi.SwitchScreens) // 熄屏-亮屏
+		screensRouter.PUT("updateScreens", baseApi.UpdateScreens) //编辑显示屏
+		screensRouter.POST("addScreens", baseApi.AddScreens)      //添加显示屏
+		screensRouter.DELETE("delScreens", baseApi.DelScreens)    // 删除显示屏
+
+		screensRouter.POST("switchScreens", baseApi.SwitchScreens)       // 熄屏-亮屏
+		screensRouter.POST("sendInternalCode", baseApi.SendInternalCode) // 发送内码文字
+		screensRouter.POST("voiceBroad", baseApi.VoiceBroad)             // 远程喊话
 	}
 	{
 		screensRouterWithoutRecord.POST("getScreensList", baseApi.ScreensList) // 分页获取网关列表

+ 27 - 1
server/service/devices/dev_screens.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"gorm.io/gorm"
 	"server/dao/devices"
+	promodel "server/model/common/devices"
 	"server/model/common/request"
 	model "server/model/devices"
 	"server/service/tcp"
@@ -59,7 +60,12 @@ func (screensService *ScreensService) UpdateScreens(screens devices.Screens) err
 	return err
 }
 
-func (screensService *ScreensService) SwitchScreens(swatch model.SwitchScreens) error {
+/**
+以下是关于设备操作的一些业务方法
+*/
+
+// 开关屏
+func (screensService *ScreensService) SwitchScreens(swatch promodel.SwitchScreens) error {
 	//判断组网方式 0代表网关 1代表4G模块
 	if swatch.Network == 1 {
 		err := tcp.SwitchScreen(swatch.Sn, swatch.Flag)
@@ -73,3 +79,23 @@ func (screensService *ScreensService) SwitchScreens(swatch model.SwitchScreens)
 	}
 	return nil
 }
+
+// 发送内码文字
+func (screensService *ScreensService) SendInternalCode(sn string, content []promodel.InternalCodeContent) error {
+	err := tcp.SendInternalCode(sn, content)
+	if err != nil {
+		logger.Logger.Errorf("SendInternalCode faild \n")
+		return errors.New("发送内码文字失败!")
+	}
+	return nil
+}
+
+// 远程喊话
+func (screensService *ScreensService) VoiceBroad(sn string, content string) error {
+	err := tcp.VoiceBroad(sn, content)
+	if err != nil {
+		logger.Logger.Errorf("SendInternalCode faild \n")
+		return errors.New("远程喊话失败!")
+	}
+	return nil
+}

+ 64 - 6
server/service/tcp/deviceMgr.go

@@ -9,6 +9,7 @@ import (
 	"net"
 	Dev "server/dao/devices"
 	deviceDao "server/dao/devices"
+	promodel "server/model/common/devices"
 	"server/model/common/request"
 	"server/protocol"
 	"server/utils/logger"
@@ -24,10 +25,12 @@ type Device struct {
 	info     deviceDao.Screens
 	conn     net.Conn
 	lastTime time.Time
+	stopChan chan struct{} // 添加停止通道
 }
 
 func (s *Device) Start(conn net.Conn) {
 	s.conn = conn
+	s.stopChan = make(chan struct{})
 	go s.Process()
 	go s.Handle()
 }
@@ -68,12 +71,17 @@ func (s *Device) Process() {
 				continue
 			}
 			s.UpdateInfo(data[82:84], data[86:88], data[90:92])
+		default:
+			fmt.Println("读取:", data)
 		}
 	}
 }
 
 func (s *Device) Handle() {
+	defer s.conn.Close()
 	t2 := time.NewTicker(3 * time.Minute) //在线监测
+	defer t2.Stop()
+
 	for {
 		select {
 		case <-t2.C:
@@ -83,21 +91,27 @@ func (s *Device) Handle() {
 				DisplayStatus: 1,
 				SourceStatus:  1,
 			}
-			if time.Now().Add(-2 * time.Minute).After(s.lastTime) {
+			if time.Now().Add(-2*time.Minute).After(s.lastTime) && !s.lastTime.IsZero() {
 				//离线
 				state.Status = 0
 				state.PlayStatus = 0
 				state.DisplayStatus = 0
 				state.SourceStatus = 0
-				fmt.Println("离线了...")
-				fmt.Println("在线状态:", state)
+
+				err := Dev.UpdateScreensStatus(s.info.ScreensCode, state)
+				if err != nil {
+					logger.Logger.Error("UpdateScreensStatus err", zap.Error(err))
+					continue
+				}
+
+				s.conn.Close()
+				close(s.stopChan) // 通知协程停止
+				return            // 结束 Handle 协程
 			} else {
 				logger.Logger.Debugf("%s在线", s.info.ScreensCode)
 			}
-			fmt.Println("编号:" + s.info.ScreensCode)
 			//修改数据库状态
 			err := Dev.UpdateScreensStatus(s.info.ScreensCode, state)
-			fmt.Println("改数据库了。。。。。")
 			if err != nil {
 				logger.Logger.Error("UpdateScreensStatus err", zap.Error(err))
 				continue
@@ -125,7 +139,6 @@ func (s *Device) UpdateInfo(playStatus, displayStatus, sourceStatus string) {
 	s.info.DisplayStatus = display
 	s.info.SourceStatus = source
 	s.lastTime = time.Now()
-	fmt.Println("上次在线时间,", s.lastTime.String())
 }
 
 func (s *Device) SwitchScreen(onOff int) error {
@@ -142,6 +155,31 @@ func (s *Device) SwitchScreen(onOff int) error {
 	return nil
 }
 
+func (s *Device) SendInternalCode(content []promodel.InternalCodeContent) error {
+	pack := protocol.InternalCodeDataPack{}
+	//获取要写入连接的字节数组
+	buf := pack.SendInternalCode(content)
+	_, err := s.conn.Write(buf.Bytes())
+	if err != nil {
+		logger.Logger.Errorf("SendInternalCode write failed, err:%v", err)
+		return err
+	}
+
+	return nil
+}
+
+func (s *Device) VoiceBroad(broad string) error {
+	pack := protocol.VoiceBroadDataPack{}
+	buf := pack.VoiceBroad(broad)
+	_, err := s.conn.Write(buf.Bytes())
+	if err != nil {
+		logger.Logger.Errorf("VoiceBroad write failed, err:%v", err)
+		return err
+	}
+	return nil
+}
+
+// 向外暴露的开关操作
 func SwitchScreen(sn string, onOff int) error {
 	device := devices[sn]
 	err := device.SwitchScreen(onOff)
@@ -150,3 +188,23 @@ func SwitchScreen(sn string, onOff int) error {
 	}
 	return nil
 }
+
+// 向外暴露的发送内码文字操作
+func SendInternalCode(sn string, content []promodel.InternalCodeContent) error {
+	device := devices[sn]
+	err := device.SendInternalCode(content)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 向外暴露的远程喊话操作
+func VoiceBroad(sn string, broad string) error {
+	device := devices[sn]
+	err := device.VoiceBroad(broad)
+	if err != nil {
+		return err
+	}
+	return nil
+}

+ 16 - 0
web/src/api/screens.js

@@ -46,3 +46,19 @@ export const switchScreens = (data) => {
         data: data
     })
 }
+
+export const sendInternalCode = (data) => {
+    return service({
+        url: '/screens/sendInternalCode',
+        method: 'post',
+        data: data
+    })
+}
+
+export const voiceBroad = (data) => {
+    return service({
+        url: '/screens/voiceBroad',
+        method: 'post',
+        data: data
+    })
+}

+ 206 - 11
web/src/view/devicesAdmin/screens/screens.vue

@@ -89,12 +89,6 @@
                 icon="edit"
                 @click="editScreens(scope.row)"
             >编辑</el-button>
-            <el-button
-                type="primary"
-                link
-                icon="delete"
-                @click="deleteScreens(scope.row)"
-            >删除</el-button>
             <el-button
                 type="primary"
                 link
@@ -105,6 +99,22 @@
                 link
                 @click="switchOpenCloseScreens(scope.row,0)"
             >熄屏</el-button>
+            <el-button
+                type="primary"
+                link
+                @click="openInCodeDialog(scope.row)"
+            >发送文字</el-button>
+            <el-button
+                type="primary"
+                link
+                @click="openVoiceBroadDialog(scope.row)"
+            >远程喊话</el-button>
+            <el-button
+                type="primary"
+                link
+                icon="delete"
+                @click="deleteScreens(scope.row)"
+            >删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -350,11 +360,96 @@
           </el-row>
         </el-form>
       </el-dialog>
+
+<!--      发送内码文字时打开的弹窗-->
+      <el-dialog v-model="inCodeDialog" title="发送文字" width="900">
+<!--          <div class="dialog-footer" style="position: relative;left: 780px;height: 35px">-->
+<!--            <el-button type="danger" circle @click="addData()">添加</el-button>-->
+<!--            <el-button type="danger" circle @click="minData()">移除</el-button>-->
+<!--          </div>-->
+        <el-scrollbar max-height="400px">
+          <el-form v-for="(item,i) in dataForms" ref="dataForm" :rules="rules" :model="dataForms" label-width="120px" style="padding: 15px">
+<!--            <el-divider>文字数据{{i+1}}</el-divider>-->
+            <el-row>
+              <el-col :span="24">
+                <el-form-item label="文字内容:" :inline="false" prop="text">
+                  <template #label>
+                    <span>文字内容</span>
+                    <span style="color: red">(必填)</span>:
+                  </template>
+                  <el-input v-model="item.text" type="textarea" :maxlength="4" show-word-limit/>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="12">
+                <el-form-item label="文字颜色:" :inline="false">
+                  <el-select v-model="item.color" placeholder="请选择文字颜色">
+                    <el-option label="红色" value="红色" />
+                    <el-option label="黄色" value="黄色" />
+                    <el-option label="绿色" value="绿色" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+<!--              <el-col :span="12">-->
+<!--                <el-form-item label="文字尺寸:" :inline="false">-->
+<!--                  <el-select v-model="item.size" placeholder="请选择文字尺寸">-->
+<!--                    <el-option label="16号" value="16号" />-->
+<!--                    <el-option label="24号" value="24号" />-->
+<!--                    <el-option label="32号" value="32号" />-->
+<!--                    <el-option label="48号" value="48号" />-->
+<!--                    <el-option label="64号" value="64号" />-->
+<!--                  </el-select>-->
+<!--                </el-form-item>-->
+<!--              </el-col>-->
+            </el-row>
+          </el-form>
+        </el-scrollbar>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="closeInCodeDialog">取消</el-button>
+            <el-button type="primary" @click="enterInCodeDialog">
+              确定
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
+
+<!--远程喊话弹窗-->
+      <el-dialog v-model="voiceBroadDialog" title="远程喊话" width="900">
+        <el-scrollbar max-height="400px">
+          <el-form ref="dataForm" :rules="rules" :model="voiceBroadForm" label-width="120px" style="padding: 15px">
+            <el-row>
+              <el-col :span="24">
+                <el-form-item label="播报内容:" :inline="false" prop="broadContent">
+                  <el-input v-model="voiceBroadForm.broadContent" type="textarea" :maxlength="4" show-word-limit/>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-scrollbar>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="closeVoiceBroadDialog">取消</el-button>
+            <el-button type="primary" @click="enterVoiceBroadDialog">
+              发送
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
     </div>
   </div>
 </template>
 <script setup>
-import {deleteBaseScreens, getScreensList, queryscreensParms,setScreensInfo,addScreens,switchScreens} from "@/api/screens";
+import {
+  deleteBaseScreens,
+  getScreensList,
+  queryscreensParms,
+  setScreensInfo,
+  addScreens,
+  switchScreens,
+  sendInternalCode, voiceBroad
+} from "@/api/screens";
 import { ref,reactive} from 'vue'
 import {ElMessage, ElMessageBox} from "element-plus";
 const page = ref(1)
@@ -363,16 +458,28 @@ const pageSize = ref(10)
 const tableData = ref([])
 const searchInfo = ref({})
 const screensInfo = ref({})
+const nowSn = ref("")
+const dataForms = ref([
+  {
+    text:'',
+    color:'红色',
+    size:'24号'
+  }
+])
+const voiceBroadForm = ref({})
 const screensParams = reactive({
   resolution:[],
   screensSize:[],
   gatewayList:[]
 })
 const screensForm = ref(null)
+const dataForm = ref(null)
 const dialogFlag = ref('add')
 const isFlag = ref('')
 const addScreensDialog = ref(false)
 const viewScreensDialog = ref(false)
+const inCodeDialog = ref(false)
+const voiceBroadDialog = ref(false)
 
 const rules = ref({
   screensName: [
@@ -385,10 +492,6 @@ const rules = ref({
   screensModel: [
     { required: true, message: '请填写型号',trigger: 'blur'},
   ],
-  // ipAddress: [
-  //   { required: true,  message:'请输入ip地址',trigger: 'blur'},
-  //   { pattern:/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,message: '请输入正确的ip地址',trigger: 'blur'}
-  // ],
   sn: [
     { required: true,  message:'请输入设备SN',trigger: 'blur'},
   ],
@@ -397,9 +500,13 @@ const rules = ref({
   ],
   gatewayId: [
     { required: true,  message:'请选择网关',trigger: 'blur'},
+  ],
+  broadContent: [
+    { required: true,  message:'请填写播报内容',trigger: 'blur'},
   ]
 })
 
+
 const getNowDay = ()=>{
   let timestamp = Date.now(); // 获取当前时间戳,单位是毫秒
   screensInfo.value.installTime = new Date(timestamp)
@@ -557,6 +664,94 @@ const switchOpenCloseScreens = async (obj,opt) => {
   }
 }
 
+const openInCodeDialog = async (obj)=>{
+  if (obj.status ===0 ){
+    ElMessage({
+      type: 'info',
+      message: '操作失败,设备离线',
+    })
+    return
+  }
+  inCodeDialog.value = true
+  nowSn.value = obj.sn
+}
+const openVoiceBroadDialog = async (obj)=>{
+  if (obj.status ===0 ){
+    ElMessage({
+      type: 'info',
+      message: '操作失败,设备离线',
+    })
+    return
+  }
+  voiceBroadDialog.value = true
+  nowSn.value = obj.sn
+}
+
+// const addData = ()=>{
+//   if(dataForms.value.length === 5){
+//     ElMessage({
+//       type: 'info',
+//       message: '最多添加五个数据',
+//     })
+//     return
+//   }
+//   var arr = {text:'',color:'',size:''}
+//   dataForms.value.push(arr)
+// }
+// const minData = ()=>{
+//   if(dataForms.value.length === 1){
+//     ElMessage({
+//       type: 'info',
+//       message: '至少选择一个数据',
+//     })
+//     return
+//   }
+//   dataForms.value.length = dataForms.value.length-1
+// }
+
+const closeInCodeDialog = ()=>{
+  nowSn.value = ""
+  inCodeDialog.value = false
+  dataForms.value = [{text:'', color:'红色',size:'24号'}]
+}
+
+const enterInCodeDialog = async ()=>{
+  for (const x of dataForms.value) { // 使用 for...of 循环
+    if (x.text === '') {
+      ElMessage({
+        type: 'error',
+        message: '请填写文字内容',
+      })
+      return;
+    }
+  }
+  var res = await sendInternalCode({sn:nowSn.value,content:dataForms.value});
+  if (res.code === 0) {
+    ElMessage({
+      type: 'success',
+      message: '操作成功!',
+    })
+  }
+}
+
+const closeVoiceBroadDialog = ()=>{
+  nowSn.value = ""
+  voiceBroadDialog.value = false
+  voiceBroadForm.value = {}
+}
+
+const enterVoiceBroadDialog = async () => {
+  console.log("----",{sn: nowSn.value, broadContent: voiceBroadForm.value.broadContent})
+  var res = await voiceBroad({sn: nowSn.value, broadContent: voiceBroadForm.brodContent});
+  if (res.code === 0) {
+    ElMessage({
+      type: 'success',
+      message: '操作成功!',
+    })
+  }
+}
+
+
 
 </script>
 <style>