فهرست منبع

初始化版本

longan 11 ماه پیش
کامیت
f6f238c7ff

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 13 - 0
.idea/lc-smartX.iml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true">
+    <buildTags>
+      <option name="os" value="windows" />
+    </buildTags>
+  </component>
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/bx6k.iml" filepath="$PROJECT_DIR$/.idea/bx6k.iml" />
+    </modules>
+  </component>
+</project>

+ 57 - 0
bx/BxArea.go

@@ -0,0 +1,57 @@
+package bx
+
+type BxArea interface {
+	Build() []byte
+	Length() int16
+}
+
+type bxArea struct {
+	BxArea
+	typ byte
+	x   int16
+	y   int16
+	w   int16
+	h   int16
+}
+
+func NewBxArea(typ byte, x int16, y int16, w int16, h int16) bxArea {
+	return bxArea{
+		typ: typ,
+		x:   x,
+		y:   y,
+		w:   w,
+		h:   h,
+	}
+}
+
+func (a *bxArea) GetX() int16 {
+	return a.x
+}
+
+func (a *bxArea) SetX(x int16) {
+	a.x = x
+}
+
+func (a *bxArea) GetY() int16 {
+	return a.y
+}
+
+func (a *bxArea) SetY(y int16) {
+	a.y = y
+}
+
+func (a *bxArea) GetW() int16 {
+	return a.w
+}
+
+func (a *bxArea) SetW(w int16) {
+	a.w = w
+}
+
+func (a *bxArea) GetH() int16 {
+	return a.h
+}
+
+func (a *bxArea) SetH(h int16) {
+	a.h = h
+}

+ 184 - 0
bx/BxAreaDynamic.go

@@ -0,0 +1,184 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type BxAreaDynamic struct {
+	bxArea
+	is5K bool
+	//
+	// id
+	// 动态区域编号
+	// 注意:该参数只对动态区有效,其他区域为默认
+	// 值,动态区必须统一编号,编号从 0 开始递增。
+	id byte
+	// 行间距
+	lineSpace byte
+	// 动态区运行模式
+	//0—动态区数据循环显示。
+	//1—动态区数据显示完成后静止显示最后一页数
+	//据。
+	//2—动态区数据循环显示,超过设定时间后数据仍
+	//未更新时不再显示
+	//3—动态区数据循环显示,超过设定时间后数据仍
+	//未更新时显示 Logo 信息,Logo 信息即为动态区域
+	//的最后一页信息
+	//4—动态区数据顺序显示,显示完最后一页后就不
+	//再显示
+	//5—动态区数据顺序显示,超过设定次数后数据仍
+	//未更新时不再显示
+	runMode byte
+	// 动 态 区 数 据 超 时 时 间 , 单 位 为 秒 / 次 数 ( 若
+	// RunMode=5,则表示更新次数)
+	timeout int16
+	// 是否使能语音播放
+	//0 表示不使能语音
+	//1 表示播放下文中 Data 部分内容
+	//2 表示播放下文中 SoundData 部分内容
+	soundMode   byte
+	soundPerson byte
+	soundRepeat byte
+	soundVolume byte //todo 默认值0x05
+	soundSpeed  byte
+	SoundData   []byte
+	// extend para len
+	extendParaLen byte
+	// type setting
+	// 属于 extend para
+	//typeSetting byte
+	// text alignment
+	alignment byte
+	// single line
+	singleLine byte
+	// 是否自动换行
+	// 是否自动换行
+	// 0x01——不自动换行,显示数据在换行时必须插入
+	// 换行符
+	// 0x02——自动换行,显示内容不需要换行符,但是
+	// 只能使用统一的中文字体和英文字体
+	autoNewLine byte
+	// 显示方式
+	//0x01——静止显示
+	//0x02——快速打出
+	//0x03——向左移动
+	//0x04——向右移动
+	//0x05——向上移动
+	//0x06——向下移动
+	dispMode byte
+	exitMode byte
+	speed    byte
+	holdTime byte
+	data     []byte
+}
+
+func NewBxAreaDynamic(id, dispMode byte, x int16, y int16, w int16, h int16, data []byte, is5K bool) *BxAreaDynamic {
+	return &BxAreaDynamic{
+		bxArea:      NewBxArea(0, x, y, w, h),
+		id:          id,
+		data:        data,
+		is5K:        is5K,
+		timeout:     5,
+		soundVolume: 0x05,
+		soundSpeed:  0x05,
+		singleLine:  0x02,
+		autoNewLine: 0x01,
+		dispMode:    dispMode,
+		speed:       0x0a,
+		holdTime:    0x08,
+	}
+}
+
+func (b *BxAreaDynamic) Length() int16 {
+	return 27 + int16(len(b.data))
+}
+
+func (b *BxAreaDynamic) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	binary.Write(w, binary.LittleEndian, b.typ)
+	x8 := b.GetX()
+	w8 := b.GetW()
+	if b.is5K {
+		x8 = b.x / 8
+		w8 = b.w / 8
+	}
+	binary.Write(w, binary.LittleEndian, x8)
+	binary.Write(w, binary.LittleEndian, b.GetY())
+	binary.Write(w, binary.LittleEndian, w8)
+	binary.Write(w, binary.LittleEndian, b.GetH())
+	// 动态区编号
+	binary.Write(w, binary.LittleEndian, b.id)
+	// 行间距
+	binary.Write(w, binary.LittleEndian, b.lineSpace)
+	// 运行模式
+	binary.Write(w, binary.LittleEndian, b.runMode)
+	binary.Write(w, binary.LittleEndian, b.timeout)
+	binary.Write(w, binary.LittleEndian, b.soundMode)
+	if b.soundMode == 0x01 || b.soundMode == 0x02 {
+		pr := ((b.soundRepeat << 4) & 0xf0) | (b.soundPerson & 0x0f)
+		binary.Write(w, binary.LittleEndian, pr)
+		binary.Write(w, binary.LittleEndian, b.soundVolume)
+		binary.Write(w, binary.LittleEndian, b.soundSpeed)
+	}
+	if b.soundMode == 0x02 {
+		soundDataLen := len(b.SoundData)
+		binary.Write(w, binary.LittleEndian, int32(soundDataLen))
+		binary.Write(w, binary.LittleEndian, b.SoundData)
+	}
+	// extendParaLen
+	binary.Write(w, binary.LittleEndian, b.extendParaLen)
+	binary.Write(w, binary.LittleEndian, b.alignment)
+	binary.Write(w, binary.LittleEndian, b.singleLine)
+	binary.Write(w, binary.LittleEndian, b.autoNewLine)
+	binary.Write(w, binary.LittleEndian, b.dispMode)
+	binary.Write(w, binary.LittleEndian, b.exitMode)
+	binary.Write(w, binary.LittleEndian, b.speed)
+	binary.Write(w, binary.LittleEndian, b.holdTime)
+	binary.Write(w, binary.LittleEndian, int32(len(b.data)))
+	binary.Write(w, binary.LittleEndian, b.data)
+
+	return w.Bytes()
+}
+
+func (b *BxAreaDynamic) SetSoundMode(soundMode byte) {
+	if soundMode > 2 {
+		b.soundMode = 0x02
+	} else {
+		b.soundMode = soundMode
+	}
+}
+
+func (b *BxAreaDynamic) SetSoundPerson(soundPerson byte) {
+	if soundPerson > 5 {
+		b.soundPerson = 0
+	} else {
+		b.soundPerson = soundPerson
+	}
+}
+
+func (b *BxAreaDynamic) SetSoundRepeat(soundRepeat byte) {
+	if soundRepeat > 15 {
+		b.soundRepeat = 15
+	} else {
+		b.soundRepeat = soundRepeat
+	}
+}
+
+func (b *BxAreaDynamic) SetSoundVolume(soundVolume byte) {
+	if soundVolume > 10 {
+		b.soundVolume = 10
+	} else {
+		b.soundVolume = soundVolume
+	}
+}
+
+func (b *BxAreaDynamic) SetSoundSpeed(soundSpeed byte) {
+	if soundSpeed < 1 {
+		b.soundSpeed = 1
+	} else if soundSpeed > 10 {
+		b.soundSpeed = 10
+	} else {
+		b.soundSpeed = soundSpeed
+	}
+}

+ 105 - 0
bx/BxByteArray.go

@@ -0,0 +1,105 @@
+package bx
+
+import (
+	"encoding/binary"
+)
+
+const (
+	DefaultCapacity = 128
+)
+
+type BxByteArray struct {
+	list []byte
+	next int
+}
+
+func NewBxByteArray(capacity int) *BxByteArray {
+	return &BxByteArray{
+		list: make([]byte, capacity),
+		next: 0,
+	}
+}
+
+func NewDefaultBxByteArray() *BxByteArray {
+	return NewBxByteArray(DefaultCapacity)
+}
+
+func (b *BxByteArray) add(data byte) {
+	if b.next == len(b.list) {
+		b.list = append(b.list, make([]byte, len(b.list)*2)...)
+	}
+	b.list[b.next] = data
+	b.next++
+}
+
+func (b *BxByteArray) addInt16(data int16, endian int) {
+	if b.next+1 >= len(b.list) {
+		b.list = append(b.list, make([]byte, len(b.list)*2)...)
+	}
+
+	if endian == 0 { // LITTLE
+		binary.LittleEndian.PutUint16(b.list[b.next:], uint16(data))
+	} else { // BIG
+		binary.BigEndian.PutUint16(b.list[b.next:], uint16(data))
+	}
+
+	b.next += 2
+}
+
+func (b *BxByteArray) addInt(data int32, endian int) {
+	if b.next+3 >= len(b.list) {
+		b.list = append(b.list, make([]byte, len(b.list)*2)...)
+	}
+
+	if endian == 0 { // LITTLE
+		binary.LittleEndian.PutUint32(b.list[b.next:], uint32(data))
+	} else { // BIG
+		binary.BigEndian.PutUint32(b.list[b.next:], uint32(data))
+	}
+
+	b.next += 4
+}
+
+func (b *BxByteArray) addBytes(src []byte) {
+	if src != nil {
+		if b.next+len(src)-1 >= len(b.list) {
+			b.list = append(b.list, make([]byte, len(b.list)+len(src))...)
+		}
+
+		copy(b.list[b.next:], src)
+		b.next += len(src)
+	}
+}
+
+func (b *BxByteArray) addBytesOffsetLength(src []byte, offset int, length int) {
+	if src != nil {
+		if b.next+length-1 >= len(b.list) {
+			b.list = append(b.list, make([]byte, len(b.list)+length)...)
+		}
+
+		copy(b.list[b.next:], src[offset:offset+length])
+		b.next += length
+	}
+}
+
+func (b *BxByteArray) set(index int, data byte) {
+	if index < len(b.list) {
+		b.list[index] = data
+	}
+}
+
+func (b *BxByteArray) get(index int) byte {
+	return b.list[index]
+}
+
+func (b *BxByteArray) Build() []byte {
+	return b.list[:b.next]
+}
+
+func (b *BxByteArray) size() int {
+	return b.next
+}
+
+func (b *BxByteArray) clear() {
+	b.next = 0
+}

+ 61 - 0
bx/BxCmd.go

@@ -0,0 +1,61 @@
+package bx
+
+type BxCmd interface {
+	Build() []byte
+}
+
+type baseBxCmd struct {
+	BxCmd
+	group   byte
+	cmd     byte
+	reqResp byte
+	r0      byte
+	r1      byte
+}
+
+func newBaseCmd(group, cmd byte) baseBxCmd {
+	return baseBxCmd{
+		group:   group,
+		cmd:     cmd,
+		reqResp: 0x01,
+	}
+}
+func (b *baseBxCmd) Group() byte {
+	return b.group
+}
+
+func (b *baseBxCmd) SetGroup(group byte) {
+	b.group = group
+}
+
+func (b *baseBxCmd) Cmd() byte {
+	return b.cmd
+}
+
+func (b *baseBxCmd) SetCmd(cmd byte) {
+	b.cmd = cmd
+}
+
+func (b *baseBxCmd) ReqResp() byte {
+	return b.reqResp
+}
+
+func (b *baseBxCmd) SetReqResp(reqResp byte) {
+	b.reqResp = reqResp
+}
+
+func (b *baseBxCmd) R0() byte {
+	return b.r0
+}
+
+func (b *baseBxCmd) SetR0(r0 byte) {
+	b.r0 = r0
+}
+
+func (b *baseBxCmd) R1() byte {
+	return b.r1
+}
+
+func (b *baseBxCmd) SetR1(r1 byte) {
+	b.r1 = r1
+}

+ 27 - 0
bx/BxCmdCancelTimingSwitch.go

@@ -0,0 +1,27 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type CmdCancelTimingSwitch struct {
+	baseBxCmd
+}
+
+func NewCmdCancelTimingSwitch() CmdCancelTimingSwitch {
+	return CmdCancelTimingSwitch{
+		baseBxCmd: newBaseCmd(CMD_CANCEL_TIMING_SWITCH.group, CMD_CANCEL_TIMING_SWITCH.code),
+	}
+}
+
+func (cmd CmdCancelTimingSwitch) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 8))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, cmd.ReqResp())
+	//r0 r1
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	return nil
+}

+ 25 - 0
bx/BxCmdClearScreen.go

@@ -0,0 +1,25 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type BxCmdClearScreen struct {
+	baseBxCmd
+}
+
+func NewBxCmdClearScreen(group, cmd byte) BxCmd {
+	return BxCmdClearScreen{
+		newBaseCmd(CMD_CLEAR_SCREEN.group, CMD_CLEAR_SCREEN.code)}
+}
+
+func (cs BxCmdClearScreen) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 1024))
+	binary.Write(w, binary.LittleEndian, cs.group)
+	binary.Write(w, binary.LittleEndian, cs.cmd)
+	binary.Write(w, binary.LittleEndian, cs.ReqResp())
+	binary.Write(w, binary.LittleEndian, cs.r0)
+	binary.Write(w, binary.LittleEndian, cs.r1)
+	return w.Bytes()
+}

+ 30 - 0
bx/BxCmdCode.go

@@ -0,0 +1,30 @@
+package bx
+
+type CmdCode struct {
+	name  string
+	group byte
+	code  byte
+}
+
+var (
+	CMD_ACK                  = CmdCode{"ack", 0xa0, 0x00}
+	CMD_NACK                 = CmdCode{"nack", 0xa0, 0x01}
+	CMD_DEL_FILE             = CmdCode{"delete file", 0xa1, 0x01}
+	CMD_SYSTEM_STATE         = CmdCode{"system state", 0xa1, 0x02}
+	CMD_START_WRITE_FILE     = CmdCode{"start write file", 0xa1, 0x05}
+	CMD_WRITE_FILE           = CmdCode{"write file", 0xa1, 0x06}
+	CMD_WRITE_TRANS_START    = CmdCode{"start write trans", 0xa1, 0x07}
+	CMD_WRITE_TRANS_STOP     = CmdCode{"stop the write trans", 0xa1, 0x08}
+	CMD_WRITE_CUSTOMER_INFO  = CmdCode{"write customer information", 0xa1, 0x09}
+	CMD_GET_FILE_INTO        = CmdCode{"get file information", 0xa1, 0x0a}
+	CMD_GET_FILE_CONTENT     = CmdCode{"get file content", 0xa1, 0x0b}
+	CMD_SYSTEM_CLOCK_CORRECT = CmdCode{"system clock correct", 0xa2, 0x03}
+	CMD_READ_PARAMS          = CmdCode{"read params", 0xa2, 0x0a}
+	CMD_TURN_ON_OFF          = CmdCode{"turn on/off screen", 0xa3, 0x00}
+	CMD_TIMING_SWITCH        = CmdCode{"auto turn on/off screen", 0xa3, 0x01}
+	CMD_LOCK_UNLOCK          = CmdCode{"lock or unlock program", 0xa3, 0x04}
+	CMD_SEND_DYNAMIC_AREA    = CmdCode{"send dynamic area", 0xa3, 0x06}
+	CMD_DEL_DYNAMIC_AREA     = CmdCode{"delete dynamic area", 0xa3, 0x07}
+	CMD_CANCEL_TIMING_SWITCH = CmdCode{"cancel auto turn on/off screen", 0xa3, 0x08}
+	CMD_CLEAR_SCREEN         = CmdCode{"clear the screen", 0xa3, 0x10}
+)

+ 36 - 0
bx/BxCmdDelDynamicArea.go

@@ -0,0 +1,36 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type CmdDelDynamicArea struct {
+	baseBxCmd
+	numbers []byte //编号
+}
+
+func NewCmdDelDynamicArea(numbers []byte) CmdDelDynamicArea {
+	return CmdDelDynamicArea{
+		baseBxCmd: newBaseCmd(CMD_DEL_DYNAMIC_AREA.group, CMD_DEL_DYNAMIC_AREA.code),
+		numbers:   numbers,
+	}
+}
+
+func (cmd CmdDelDynamicArea) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 16))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, byte(0x01))
+	binary.Write(w, binary.LittleEndian, []byte{0x00, 0x00})
+	l := byte(len(cmd.numbers))
+	if l == 0 {
+		binary.Write(w, binary.LittleEndian, 0xff)
+	} else {
+		binary.Write(w, binary.LittleEndian, l)
+	}
+	for _, n := range cmd.numbers {
+		binary.Write(w, binary.LittleEndian, n)
+	}
+	return w.Bytes()
+}

+ 87 - 0
bx/BxCmdFileBitmap.go

@@ -0,0 +1,87 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+)
+
+type bitmapFile struct {
+	FileType byte   //0x04
+	FileName string //4byte Txxx
+	FileLen  uint32
+	LibData  []byte
+	CHK      uint16
+}
+
+func NewBitmapFile(filename string, libdata []byte) bitmapFile {
+	chk := CRC16(libdata, 0, len(libdata))
+	fmt.Printf("位图文件校验:% 02x\n", chk)
+	return bitmapFile{
+		FileType: 0x04,
+		FileName: filename,
+		FileLen:  uint32(len(libdata)),
+		LibData:  libdata,
+		CHK:      chk,
+	}
+}
+
+func (bf *bitmapFile) NewCmd() *CmdWriteBitmapFile {
+	return &CmdWriteBitmapFile{
+		baseBxCmd:     newBaseCmd(CMD_WRITE_FILE.group, CMD_WRITE_FILE.code),
+		file:          bf,
+		LastBlockFlag: 1,
+	}
+}
+
+type CmdWriteBitmapFile struct {
+	baseBxCmd
+	state         byte
+	file          *bitmapFile
+	LastBlockFlag byte
+	BlockNum      uint16 //包号,如果是单包发送,则默认为 0x00。
+	BlockLen      uint16 //包长,若是单包发送,此处为文件长度。
+	BlockAddr     uint32 //本包数据在文件中的起始位置,如果是单包发送,此处默认为 0。
+	temp          []byte
+}
+
+func (cmd *CmdWriteBitmapFile) Build() []byte {
+	if cmd.state == 0 {
+		//Write File
+		w1 := bytes.NewBuffer(make([]byte, 0, 1024))
+		//文件描述数据
+		binary.Write(w1, binary.LittleEndian, cmd.Group())
+		binary.Write(w1, binary.LittleEndian, cmd.Cmd())
+		binary.Write(w1, binary.LittleEndian, byte(0x01))
+		binary.Write(w1, binary.LittleEndian, []byte{0x00, 0x00})
+		binary.Write(w1, binary.BigEndian, []byte(cmd.file.FileName))
+		binary.Write(w1, binary.LittleEndian, cmd.LastBlockFlag) //是否是最后一包,0x00——不是最后一包 0x01——最后一包。
+		binary.Write(w1, binary.LittleEndian, cmd.BlockNum)      //包号,单包为0x00
+		binary.Write(w1, binary.LittleEndian, cmd.file.FileLen)  //包长,若是单包发送,此处为文件长度。
+		binary.Write(w1, binary.LittleEndian, cmd.BlockAddr)     //本包数据在文件中的偏移量,单包为0x00
+		//文件内容数据
+		w2 := bytes.NewBuffer(make([]byte, 0, 1024))
+		binary.Write(w2, binary.LittleEndian, cmd.file.FileType)
+		binary.Write(w2, binary.BigEndian, []byte(cmd.file.FileName))
+		binary.Write(w2, binary.LittleEndian, cmd.file.FileLen)
+		binary.Write(w2, binary.BigEndian, cmd.file.LibData)
+		data := w2.Bytes()
+		crc := CRC16(data, 0, w2.Len())
+		binary.Write(w1, binary.BigEndian, data)
+		binary.Write(w1, binary.LittleEndian, crc)
+		cmd.temp = w1.Bytes()
+		//Start Write File "开始写文件",写文件前先检查内存是否够用
+		w3 := bytes.NewBuffer(make([]byte, 0, 64))
+		binary.Write(w3, binary.LittleEndian, CMD_START_WRITE_FILE.group)
+		binary.Write(w3, binary.LittleEndian, CMD_START_WRITE_FILE.code)
+		binary.Write(w3, binary.LittleEndian, byte(0x01))
+		binary.Write(w3, binary.LittleEndian, []byte{0x00, 0x00})
+		binary.Write(w3, binary.LittleEndian, byte(0x01)) //同名是否覆盖,0不覆盖,1覆盖
+		binary.Write(w3, binary.BigEndian, []byte(cmd.file.FileName))
+		binary.Write(w3, binary.LittleEndian, cmd.file.FileLen)
+		cmd.state = 1
+		return w3.Bytes()
+	} else {
+		return cmd.temp
+	}
+}

+ 36 - 0
bx/BxCmdFileDelete.go

@@ -0,0 +1,36 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type CmdDeleteFile struct {
+	baseBxCmd
+	files []string
+}
+
+func NewCmdDeleteFile(files []string) CmdDeleteFile {
+	return CmdDeleteFile{
+		baseBxCmd: newBaseCmd(CMD_DEL_FILE.group, CMD_DEL_FILE.code),
+		files:     files,
+	}
+}
+
+func (cmd CmdDeleteFile) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, cmd.ReqResp())
+	binary.Write(w, binary.LittleEndian, []byte{0x00, 0x00})
+	l := uint16(len(cmd.files))
+	if l != 0 {
+		binary.Write(w, binary.LittleEndian, l)
+		for _, v := range cmd.files {
+			binary.Write(w, binary.BigEndian, []byte(v))
+		}
+		return w.Bytes()
+	}
+	binary.Write(w, binary.LittleEndian, []byte{0x00})
+	return w.Bytes()
+}

+ 25 - 0
bx/BxCmdFileRead.go

@@ -0,0 +1,25 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type CmdReadFileInfo struct {
+	baseBxCmd
+}
+
+func NewCmdReadFileInfo() CmdReadFileInfo {
+	return CmdReadFileInfo{
+		baseBxCmd: newBaseCmd(CMD_GET_FILE_INTO.group, CMD_GET_FILE_INTO.code),
+	}
+}
+
+func (cmd CmdReadFileInfo) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 8))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, cmd.ReqResp())
+	binary.Write(w, binary.LittleEndian, []byte{0x00, 0x00})
+	return nil
+}

+ 171 - 0
bx/BxCmdFileWrite.go

@@ -0,0 +1,171 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"time"
+)
+
+// 普通文本文件
+type bxFile struct {
+	type_       byte   //默认0x00
+	name        string //4字节ASCII
+	len         uint32
+	content     string
+	Priority    byte
+	DisplayType uint16 //播放方式节目播放方式,0——顺序播放,其他——定长播放的 时间,单位为秒
+	PlayTimes   byte
+	//节目生命周期,发送顺序为:起始年(2)+起始月(1)+起
+	//始日(1)+结束年(2)+结束月(1)+结束日(1)注:1. 时间均
+	//采用 BCD 码的方式2. 年范围为 0x1900—0x2099,
+	//0xffff 为永久有效,先发送 LSB,后发送 MSB
+	ProgramLife string
+	//节目的星期属性
+	//1. Bit0 为 1 表示一周中的每一天都播放。
+	//2. Bit0 为 0 时,需判断 bit1-bit7 的来决定每天播放,
+	//bit1-bit7依次表示周一到周日。
+	//3.比特为0表示禁止播放,为 1 表示播放。
+	ProgramWeek byte
+	//定时节目位 0 非定时,注:为 0 时则播放时段组数设 置为 0
+	ProgramTime byte
+	//节目播放时段组数,最多支持一组,当为 0 时 PlayPeriodSetting
+	PlayPeriodGrpNum byte
+	//6Byte 播放组0,发送顺序为:起始小时(1)+起始分钟(1)+起始秒(1)+结束小时(1)+结束分钟(1)+结束秒(1)
+	//PlayPeriodSetting0 time.Time
+	Areas []BxArea
+}
+
+// NewBxFile diedLine 节目过期时间 "2006-01-02", 传"" 永不过期
+func NewBxFile(fileName string, diedLine string, areas []BxArea) bxFile {
+	return bxFile{
+		name:        fileName,
+		Areas:       areas,
+		ProgramLife: diedLine,
+		Priority:    0xff,
+		PlayTimes:   1,
+		ProgramWeek: 1,
+	}
+}
+func (f bxFile) NewCmdWriteFile() *CmdWriteFile {
+	return &CmdWriteFile{
+		baseBxCmd:     newBaseCmd(CMD_WRITE_FILE.group, CMD_WRITE_FILE.code),
+		file:          &f,
+		LastBlockFlag: 1,
+	}
+}
+
+func (f bxFile) encodeProgramLife() []byte {
+	if f.ProgramLife == "" {
+		return []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+	}
+	w := bufPoll.Get().(*bytes.Buffer)
+	now := time.Now()
+	y := Uint2BCD(uint64(now.Year()), false)
+	m := Uint2BCD(uint64(now.Month()), false)
+	d := Uint2BCD(uint64(now.Day()), false)
+	binary.Write(w, binary.BigEndian, y)
+	binary.Write(w, binary.BigEndian, m)
+	binary.Write(w, binary.BigEndian, d)
+	end, err := time.Parse("2006-01-02", f.ProgramLife)
+	if err != nil {
+		logrus.Error("时间解析错误:", err)
+		return nil
+	}
+	endY := Uint2BCD(uint64(end.Year()), false)
+	endM := Uint2BCD(uint64(end.Month()), false)
+	endD := Uint2BCD(uint64(end.Day()), false)
+	binary.Write(w, binary.BigEndian, endY)
+	binary.Write(w, binary.BigEndian, endM)
+	binary.Write(w, binary.BigEndian, endD)
+	return w.Bytes()
+}
+
+//func (f bxFile) Build() []byte {
+//	w := bytes.NewBuffer(make([]byte, 0, 1024))
+//	return w.Bytes()
+//}
+
+type CmdWriteFile struct {
+	baseBxCmd
+	state         byte
+	file          *bxFile
+	LastBlockFlag byte
+	BlockNum      uint16 //包号,如果是单包发送,则默认为 0x00。
+	BlockLen      uint16 //包长,若是单包发送,此处为文件长度。
+	BlockAddr     uint32 //本包数据在文件中的起始位置,如果是单包发送,此处默认为 0。
+	temp          []byte
+}
+
+func (cmd *CmdWriteFile) Build() []byte {
+	if cmd.state == 0 {
+		//先计算区域数据及长度
+		w0 := bytes.NewBuffer(make([]byte, 0, 1024))
+		for _, v := range cmd.file.Areas {
+			b := v.Build()
+			binary.Write(w0, binary.LittleEndian, uint32(len(b))+4)
+			binary.Write(w0, binary.LittleEndian, b)
+		}
+		l := w0.Len() + 27
+		cmd.BlockLen = uint16(l)
+		cmd.file.len = uint32(l)
+		fmt.Println("传输的长度:", l)
+		//Write File
+		w1 := bytes.NewBuffer(make([]byte, 0, 1024))
+		//文件描述数据
+		binary.Write(w1, binary.LittleEndian, cmd.Group())
+		binary.Write(w1, binary.LittleEndian, cmd.Cmd())
+		binary.Write(w1, binary.LittleEndian, byte(0x01))
+		binary.Write(w1, binary.LittleEndian, []byte{0x00, 0x00})
+		binary.Write(w1, binary.BigEndian, []byte(cmd.file.name))
+		binary.Write(w1, binary.LittleEndian, cmd.LastBlockFlag) //是否是最后一包,0x00——不是最后一包 0x01——最后一包。
+		binary.Write(w1, binary.LittleEndian, cmd.BlockNum)      //包号,单包为0x00
+		binary.Write(w1, binary.LittleEndian, cmd.BlockLen)      //包长,若是单包发送,此处为文件长度。
+		binary.Write(w1, binary.LittleEndian, cmd.BlockAddr)     //本包数据在文件中的偏移量,单包为0x00
+		//文件内容数据
+		w2 := bytes.NewBuffer(make([]byte, 0, 1024))
+		binary.Write(w2, binary.LittleEndian, cmd.file.type_)
+		binary.Write(w2, binary.BigEndian, []byte(cmd.file.name))
+		binary.Write(w2, binary.LittleEndian, cmd.file.len)
+		binary.Write(w2, binary.LittleEndian, cmd.file.Priority)
+		binary.Write(w2, binary.LittleEndian, cmd.file.DisplayType)
+		binary.Write(w2, binary.LittleEndian, cmd.file.PlayTimes)
+		binary.Write(w2, binary.BigEndian, cmd.file.encodeProgramLife())
+		binary.Write(w2, binary.LittleEndian, cmd.file.ProgramWeek)
+		binary.Write(w2, binary.LittleEndian, cmd.file.ProgramTime)
+		binary.Write(w2, binary.LittleEndian, cmd.file.PlayPeriodGrpNum)
+		//binary.Write(w, binary.LittleEndian, cmd.file.PlayPeriodSetting0)
+		binary.Write(w2, binary.LittleEndian, byte(len(cmd.file.Areas)))
+		binary.Write(w2, binary.LittleEndian, w0.Bytes())
+		b2 := w2.Bytes()
+		binary.Write(w1, binary.BigEndian, b2)
+		crc16 := CRC16(b2, 0, w2.Len())
+		fmt.Printf("文件校验:% 02x\n", crc16)
+		binary.Write(w1, binary.LittleEndian, crc16)
+		cmd.temp = w1.Bytes()
+		//Start Write File "开始写文件",写文件前先检查内存是否够用
+		w3 := bytes.NewBuffer(make([]byte, 0, 64))
+		binary.Write(w3, binary.LittleEndian, CMD_START_WRITE_FILE.group)
+		binary.Write(w3, binary.LittleEndian, CMD_START_WRITE_FILE.code)
+		binary.Write(w3, binary.LittleEndian, byte(0x01))
+		binary.Write(w3, binary.LittleEndian, []byte{0x00, 0x00})
+		binary.Write(w3, binary.LittleEndian, byte(0x01)) //同名是否覆盖,0不覆盖,1覆盖
+		binary.Write(w3, binary.BigEndian, []byte(cmd.file.name))
+		binary.Write(w3, binary.LittleEndian, cmd.file.len)
+		cmd.state = 1
+		return w3.Bytes()
+	} else {
+		return cmd.temp
+	}
+}
+
+type cmdFileBeginWrite struct {
+	baseBxCmd
+}
+
+func NewCmdFileBeginWrite() cmdFileBeginWrite {
+	return cmdFileBeginWrite{
+		baseBxCmd: newBaseCmd(CMD_START_WRITE_FILE.group, CMD_START_WRITE_FILE.code),
+	}
+}

+ 39 - 0
bx/BxCmdLock.go

@@ -0,0 +1,39 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type CmdLock struct {
+	baseBxCmd
+	StoreMode       byte
+	LockFlag        byte
+	ProgramFileName string
+}
+
+// NewCmdLock 锁定状态:0x00——解锁状态,0x01——锁定状态
+func NewCmdLock(flag byte, name string) CmdLock {
+	return CmdLock{
+		baseBxCmd:       newBaseCmd(CMD_LOCK_UNLOCK.group, CMD_LOCK_UNLOCK.code),
+		LockFlag:        flag,
+		ProgramFileName: name,
+	}
+}
+
+// SetStoreMode 锁定状态保存方式:0x00——掉电不保存,0x01——掉电保存
+func (cmd *CmdLock) SetStoreMode(storeMode byte) {
+	cmd.StoreMode = storeMode
+}
+
+func (cmd *CmdLock) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 32))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, byte(0x01))
+	binary.Write(w, binary.LittleEndian, []byte{0x00, 0x00})
+	binary.Write(w, binary.LittleEndian, cmd.StoreMode)
+	binary.Write(w, binary.LittleEndian, cmd.LockFlag)
+	binary.Write(w, binary.BigEndian, []byte(cmd.ProgramFileName))
+	return w.Bytes()
+}

+ 15 - 0
bx/BxCmdReadParams.go

@@ -0,0 +1,15 @@
+package bx
+
+type CmdReadParams struct {
+	baseBxCmd
+}
+
+func NewCmdReadParams() CmdReadParams {
+	return CmdReadParams{
+		baseBxCmd: newBaseCmd(CMD_READ_PARAMS.group, CMD_READ_PARAMS.code),
+	}
+}
+
+func (cmd CmdReadParams) Build() []byte {
+	return []byte{0xa2, 0x0a, 0x01, 0x00, 0x00}
+}

+ 82 - 0
bx/BxCmdSendDynamicArea.go

@@ -0,0 +1,82 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+var GROUP byte = 0xa3
+var CMD byte = 0x06
+
+type CmdSendDynamicArea struct {
+	baseBxCmd
+	processMode byte
+	r2          byte
+	delAreaIds  []byte
+	areas       []BxArea
+}
+
+func NewBxCmdSendDynamicArea(areas []BxArea) CmdSendDynamicArea {
+	return CmdSendDynamicArea{
+		baseBxCmd: newBaseCmd(CMD_SEND_DYNAMIC_AREA.group, CMD_SEND_DYNAMIC_AREA.code),
+		areas:     areas,
+	}
+}
+
+func (sd CmdSendDynamicArea) ProcessMode() byte {
+	return sd.processMode
+}
+
+func (sd CmdSendDynamicArea) SetProcessMode(processMode byte) {
+	sd.processMode = processMode
+}
+
+func (sd CmdSendDynamicArea) R2() byte {
+	return sd.r2
+}
+
+func (sd CmdSendDynamicArea) SetR2(r2 byte) {
+	sd.r2 = r2
+}
+
+func (sd CmdSendDynamicArea) DelAreaIds() []byte {
+	return sd.delAreaIds
+}
+
+func (sd CmdSendDynamicArea) SetDelAreaIds(delAreaIds []byte) {
+	sd.delAreaIds = delAreaIds
+}
+
+func (sd CmdSendDynamicArea) Areas() []BxArea {
+	return sd.areas
+}
+
+func (sd CmdSendDynamicArea) SetAreas(areas []BxArea) {
+	sd.areas = areas
+}
+
+func (sd CmdSendDynamicArea) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	binary.Write(w, binary.LittleEndian, sd.Group())
+	binary.Write(w, binary.LittleEndian, sd.Cmd())
+	binary.Write(w, binary.LittleEndian, sd.ReqResp())
+	binary.Write(w, binary.LittleEndian, sd.processMode)
+	binary.Write(w, binary.LittleEndian, sd.r2)
+	if sd.delAreaIds == nil {
+		binary.Write(w, binary.LittleEndian, byte(0x00))
+	} else {
+		binary.Write(w, binary.LittleEndian, byte(len(sd.delAreaIds)))
+		binary.Write(w, binary.LittleEndian, sd.delAreaIds)
+	}
+	if sd.areas == nil || len(sd.areas) == 0 {
+		binary.Write(w, binary.LittleEndian, byte(0x00))
+	} else {
+		binary.Write(w, binary.LittleEndian, byte(len(sd.areas)))
+		for _, v := range sd.areas {
+			b := v.Build()
+			binary.Write(w, binary.LittleEndian, int16(len(b)))
+			binary.Write(w, binary.LittleEndian, b)
+		}
+	}
+	return w.Bytes()
+}

+ 27 - 0
bx/BxCmdState.go

@@ -0,0 +1,27 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type CmdState struct {
+	baseBxCmd
+}
+
+func NewCmdState() CmdState {
+	return CmdState{
+		baseBxCmd: newBaseCmd(CMD_SYSTEM_STATE.group, CMD_SYSTEM_STATE.code),
+	}
+}
+
+func (cmd CmdState) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, cmd.ReqResp())
+	//r0 r1
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	return w.Bytes()
+}

+ 159 - 0
bx/BxCmdSystemClockCorrect.go

@@ -0,0 +1,159 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"time"
+)
+
+type CmdSystemClockCorrect struct {
+	baseBxCmd
+	sysTime                                      time.Time
+	year, month, day, hour, minute, second, week int
+}
+
+func NewBxCmdSystemClockCorrect(t time.Time) CmdSystemClockCorrect {
+	fmt.Println(t.Year())
+	fmt.Println(t.Month())
+	fmt.Println(t.Day())
+	fmt.Println(t.Hour())
+	fmt.Println(t.Minute())
+	fmt.Println(t.Second())
+	fmt.Println(t.Weekday())
+	year, _ := BIN2Uint64(Uint2BCD(uint64(t.Year()), false), binary.LittleEndian)
+	month, _ := BIN2Uint64(Uint2BCD(uint64(t.Month()), false), binary.LittleEndian)
+	day, _ := BIN2Uint64(Uint2BCD(uint64(t.Day()), false), binary.LittleEndian)
+	hour, _ := BIN2Uint64(Uint2BCD(uint64(t.Hour()), false), binary.LittleEndian)
+	minute, _ := BIN2Uint64(Uint2BCD(uint64(t.Minute()), false), binary.LittleEndian)
+	second, _ := BIN2Uint64(Uint2BCD(uint64(t.Second()), false), binary.LittleEndian)
+	week, _ := BIN2Uint64(Uint2BCD(uint64(t.Weekday()), false), binary.LittleEndian)
+	if week == 0 {
+		week = 7
+	}
+	return CmdSystemClockCorrect{
+		baseBxCmd: newBaseCmd(CMD_SYSTEM_CLOCK_CORRECT.group, CMD_SYSTEM_CLOCK_CORRECT.code),
+		sysTime:   t,
+		year:      int(year),
+		month:     int(month),
+		day:       int(day),
+		hour:      int(hour),
+		minute:    int(minute),
+		second:    int(second),
+		week:      int(week),
+	}
+}
+
+// uint转BCD
+func Uint2BCD(n uint64, isBigEndian bool) []byte {
+	var b []byte
+	//if n < 256 {
+	//	b = []byte{0}
+	//}
+	for i := 0; ; i++ {
+		h := (n / 10) % 10
+		l := n % 10
+		b = append(b, byte(h<<4|l))
+		n = n / 100
+		if n == 0 {
+			break
+		}
+	}
+	if !isBigEndian {
+		return b
+	}
+	l := len(b)
+	var r = make([]byte, l)
+	for i, v := range b {
+		r[l-1-i] = v
+	}
+	return r
+}
+func BIN2Uint64(bin []byte, order binary.ByteOrder) (uint64, error) {
+	len := len(bin)
+	switch len {
+	case 1:
+		return uint64(bin[0]), nil
+	case 2:
+		return uint64(order.Uint16(bin)), nil
+	case 3, 4:
+		bin4 := make([]byte, 8)
+		copy(bin4[4-len:], bin) //前面字节填充0
+		return uint64(order.Uint32(bin4)), nil
+	case 5, 6, 7, 8:
+		bin8 := make([]byte, 8)
+		copy(bin8[8-len:], bin)
+		return order.Uint64(bin8), nil
+	default:
+		return 0, errors.New("不符合字节长度范围1-8")
+	}
+}
+
+func (this CmdSystemClockCorrect) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	binary.Write(w, binary.LittleEndian, this.Group())
+	binary.Write(w, binary.LittleEndian, this.Cmd())
+	binary.Write(w, binary.LittleEndian, this.ReqResp())
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	//BCD码:年(2)+月(1)+日(1)+时(1)+分(1)+秒(1)+星期(1);先地位再高位
+	//年:低端发送,低位在前
+	y := []byte{
+		byte(this.year),
+		byte(this.year >> 8),
+		byte(this.year >> 16),
+		byte(this.year >> 24),
+	}
+	if y[0] == 0x00 && y[1] == 0x00 {
+		binary.Write(w, binary.LittleEndian, y[2])
+		binary.Write(w, binary.LittleEndian, y[3])
+	} else {
+		binary.Write(w, binary.LittleEndian, y[0])
+		binary.Write(w, binary.LittleEndian, y[1])
+	}
+	//月
+	m := []byte{
+		byte(this.month),
+		byte(this.month >> 8),
+		byte(this.month >> 16),
+		byte(this.month >> 24),
+	}
+	binary.Write(w, binary.LittleEndian, m[0])
+	d := []byte{
+		byte(this.day),
+		byte(this.day >> 8),
+		byte(this.day >> 16),
+		byte(this.day >> 24),
+	}
+	binary.Write(w, binary.LittleEndian, d[0])
+	h := []byte{
+		byte(this.hour),
+		byte(this.hour >> 8),
+		byte(this.hour >> 16),
+		byte(this.hour >> 24),
+	}
+	binary.Write(w, binary.LittleEndian, h[0])
+	min := []byte{
+		byte(this.minute),
+		byte(this.minute >> 8),
+		byte(this.minute >> 16),
+		byte(this.minute >> 24),
+	}
+	binary.Write(w, binary.LittleEndian, min[0])
+	s := []byte{
+		byte(this.second),
+		byte(this.second >> 8),
+		byte(this.second >> 16),
+		byte(this.second >> 24),
+	}
+	binary.Write(w, binary.LittleEndian, s[0])
+	week := []byte{
+		byte(this.week),
+		byte(this.week >> 8),
+		byte(this.week >> 16),
+		byte(this.week >> 24),
+	}
+	binary.Write(w, binary.LittleEndian, week[0])
+	return w.Bytes()
+}

+ 42 - 0
bx/BxCmdTimingSwitch.go

@@ -0,0 +1,42 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+//定时开关机
+
+type CmdTimingSwitch struct {
+	baseBxCmd
+	onOffSet [][2]uint64
+}
+
+func NewCmdTimingSwitch(onOffSet [][2]uint64) CmdTimingSwitch {
+	return CmdTimingSwitch{
+		baseBxCmd: newBaseCmd(CMD_TIMING_SWITCH.group, CMD_TIMING_SWITCH.code),
+		onOffSet:  onOffSet,
+	}
+}
+
+func (cmd CmdTimingSwitch) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, cmd.ReqResp())
+	//r0 r1
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	if len(cmd.onOffSet) == 0 || len(cmd.onOffSet) > 256 {
+		return nil
+	}
+	binary.Write(w, binary.LittleEndian, byte(len(cmd.onOffSet)))
+	for i, v := range cmd.onOffSet {
+		if i > 2 {
+			break
+		}
+		binary.Write(w, binary.BigEndian, Uint2BCD(uint64(v[0]), true)) //开机
+		binary.Write(w, binary.BigEndian, Uint2BCD(uint64(v[1]), true)) //关机
+	}
+	return w.Bytes()
+}

+ 34 - 0
bx/BxCmdTurnOnOff.go

@@ -0,0 +1,34 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+)
+
+type BxCmdTurnOnOff struct {
+	baseBxCmd
+	on bool
+}
+
+// NewBxCmdTurnOnOff ture=on false=off
+func NewBxCmdTurnOnOff(on bool) BxCmdTurnOnOff {
+	return BxCmdTurnOnOff{
+		baseBxCmd: newBaseCmd(CMD_TURN_ON_OFF.group, CMD_TURN_ON_OFF.code),
+		on:        on,
+	}
+}
+func (cmd BxCmdTurnOnOff) Build() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 8))
+	binary.Write(w, binary.LittleEndian, cmd.Group())
+	binary.Write(w, binary.LittleEndian, cmd.Cmd())
+	binary.Write(w, binary.LittleEndian, cmd.ReqResp())
+	//r0 r1
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	binary.Write(w, binary.LittleEndian, byte(0x00))
+	if cmd.on {
+		binary.Write(w, binary.LittleEndian, byte(0x01))
+	} else {
+		binary.Write(w, binary.LittleEndian, byte(0x02))
+	}
+	return w.Bytes()
+}

+ 231 - 0
bx/BxDataPack.go

@@ -0,0 +1,231 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+)
+
+type BxDataPack struct {
+	WRAP_A5_NUM int
+	WRAP_5A_NUM int
+	dstAddr     uint16
+	srcAddr     uint16
+	r0          byte
+	r1          byte
+	r2          byte
+	option      byte
+	crcMode     byte
+	dispMode    byte
+	deviceType  byte
+	version     byte
+	dataLen     uint16
+	data        []byte
+	crc         uint16
+}
+
+func NewBxDataPackData(data []byte) BxDataPack {
+	return BxDataPack{
+		data:        data,
+		dataLen:     uint16(len(data)),
+		WRAP_A5_NUM: 8,
+		WRAP_5A_NUM: 1,
+		dstAddr:     0xfffe,
+		srcAddr:     0x8000,
+		deviceType:  0xfe,
+		version:     0x02,
+	}
+}
+func NewBxDataPackCmd(cmd BxCmd) BxDataPack {
+	b := cmd.Build()
+	fmt.Printf("build Data:% 02x\n", b)
+	return BxDataPack{
+		data:        b,
+		dataLen:     uint16(len(b)),
+		WRAP_A5_NUM: 8,
+		WRAP_5A_NUM: 1,
+		dstAddr:     0x0002,
+		srcAddr:     0x8000,
+		deviceType:  0xfe,
+		version:     0x02,
+	}
+}
+
+// SetDispType 注:特殊动态区不支持动态模式
+// 0x00:普通模式,动态区与节目可同时显示,但各区域不可重叠。
+// 0x01:动态模式,优先显示动态区,无动态区则显示节目,动态区与节目区可重叠。
+func (dp *BxDataPack) SetDispType(typ byte) {
+	dp.dispMode = typ
+}
+
+func (dp *BxDataPack) wrap(src []byte) []byte {
+	len := len(src)
+	for _, v := range src { //每次转义会使长度+1
+		if v == 0xa5 || v == 0x5a || v == 0xa6 || v == 0x5b {
+			len++
+		}
+	}
+	len += dp.WRAP_5A_NUM
+	len += dp.WRAP_A5_NUM
+	dst := make([]byte, len)
+	offset := 0
+	for i := 0; i < dp.WRAP_A5_NUM; i++ {
+		dst[offset] = 0xa5
+		offset++
+	}
+	//转义
+	for _, v := range src {
+		if v == 0xa5 {
+			dst[offset] = 0xa6
+			offset++
+			dst[offset] = 0xa2
+			offset++
+		} else if v == 0xa6 {
+			dst[offset] = 0xa6
+			offset++
+			dst[offset] = 0xa1
+			offset++
+		} else if v == 0x5a {
+			dst[offset] = 0x5b
+			offset++
+			dst[offset] = 0xa2
+			offset++
+		} else if v == 0x5b {
+			dst[offset] = 0x5b
+			offset++
+			dst[offset] = 0x01
+			offset++
+		} else {
+			dst[offset] = v
+			offset++
+		}
+	}
+
+	for i := 0; i < dp.WRAP_5A_NUM; i++ {
+		dst[offset] = 0x5a
+		offset++
+	}
+	return dst
+}
+
+func (dp *BxDataPack) Pack() []byte {
+	w := bytes.NewBuffer(make([]byte, 0, 1024))
+	//目标地址
+	binary.Write(w, binary.LittleEndian, dp.dstAddr)
+	//源地址
+	binary.Write(w, binary.LittleEndian, dp.srcAddr)
+	binary.Write(w, binary.LittleEndian, dp.r0)
+	binary.Write(w, binary.LittleEndian, dp.r1)
+	binary.Write(w, binary.LittleEndian, dp.r2)
+	binary.Write(w, binary.LittleEndian, dp.option)
+	binary.Write(w, binary.LittleEndian, dp.crcMode)
+	binary.Write(w, binary.LittleEndian, dp.dispMode)
+	binary.Write(w, binary.LittleEndian, dp.deviceType)
+	binary.Write(w, binary.LittleEndian, dp.version)
+	binary.Write(w, binary.LittleEndian, dp.dataLen)
+	binary.Write(w, binary.LittleEndian, dp.data)
+	dp.crc = 0x0
+	binary.Write(w, binary.LittleEndian, dp.crc)
+	data := w.Bytes()
+	len := w.Len()
+	dp.crc = CRC16(data, 0, len-2)
+	data[len-2] = byte(dp.crc & 0xff)
+	data[len-1] = byte(dp.crc >> 8)
+	return dp.wrap(data)
+}
+
+func dpParse(src []byte, length int) *BxDataPack {
+	dst := unwrap(src, length)
+	if dst == nil {
+		return nil
+	}
+	crcCalculated := CRC16(dst, 0, len(dst)-2)
+	crcGot := bytesToUint16(dst, len(dst)-2, binary.LittleEndian)
+
+	if crcCalculated != crcGot {
+		return nil
+	}
+	dp := BxDataPack{}
+	offset := 0
+
+	//目标地址
+	dp.dstAddr = bytesToUint16(dst, offset, binary.LittleEndian)
+	offset += 2
+	//源地址
+	dp.srcAddr = bytesToUint16(dst, offset, binary.LittleEndian)
+	offset += 2
+	//保留字 r0,r1,r2
+	dp.r0 = dst[offset]
+	offset++
+	dp.r1 = dst[offset]
+	offset++
+	dp.r2 = dst[offset]
+	offset++
+
+	dp.option = dst[offset]
+	offset++
+	dp.crcMode = dst[offset]
+	offset++
+	dp.dispMode = dst[offset]
+	offset++
+	dp.deviceType = dst[offset]
+	offset++
+	dp.version = dst[offset]
+	offset++
+	dp.dataLen = bytesToUint16(dst, offset, binary.LittleEndian)
+	offset += 2
+	//数据
+	dp.data = dst[offset : offset+int(dp.dataLen)]
+	offset += int(dp.dataLen)
+	dp.crc = bytesToUint16(dst, offset, binary.LittleEndian)
+	return &dp
+}
+
+func unwrap(src []byte, length int) []byte {
+	len := length
+	for _, v := range src {
+		if v == 0xa5 || v == 0x5a || v == 0xa6 {
+			len--
+		}
+	}
+	//如果计算的帧长度为0, 说明数据不正确
+	if len == 0 {
+		return nil
+	}
+	dst := make([]byte, len)
+	offset := 0
+	for i := 0; i < length; {
+		if src[i] == 0xa5 || src[i] == 0x5a {
+			i++
+		} else if src[i] == 0xa6 {
+			if src[i+1] == 0x01 {
+				dst[offset] = 0xa6
+				offset++
+				i = i + 2
+			} else if src[i+1] == 0x02 {
+				dst[offset] = 0xa5
+				offset++
+				i = i + 2
+			} else {
+				return nil
+			}
+		} else if src[i] == 0x5b {
+			if src[i+1] == 0x01 {
+				dst[offset] = 0x5b
+				offset++
+				i = i + 2
+			} else if src[i+1] == 0x02 {
+				dst[offset] = 0x5a
+				offset++
+				i = i + 2
+			} else {
+				return nil
+			}
+		} else {
+			dst[offset] = src[i]
+			offset++
+			i++
+		}
+	}
+	return dst
+}

+ 213 - 0
bx/BxResp.go

@@ -0,0 +1,213 @@
+package bx
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+)
+
+type BxResp struct {
+	group, cmd, Err, r0, r1 byte
+	Data                    []byte
+	//StateInfo               *StateInfo
+}
+
+func parse_(pack BxDataPack) *BxResp {
+	var offset int
+	resp := &BxResp{}
+	resp.group = pack.data[offset]
+	offset++
+	resp.cmd = pack.data[offset]
+	offset++
+	resp.Err = pack.data[offset]
+	offset++
+	resp.r0 = pack.data[offset]
+	offset++
+	resp.r1 = pack.data[offset]
+	offset++
+	resp.Data = pack.data[offset:(offset + int(pack.dataLen) - 5)]
+	//if !resp.IsInfo() {
+	//	return resp
+	//}
+	//offset = 0
+	//resp.StateInfo = &StateInfo{}
+	//resp.StateInfo.OnOff = resp.Data[offset]
+	//offset++
+	//resp.StateInfo.Brightness = resp.Data[offset]
+	//offset++
+	//t := resp.Data[offset : offset+8]
+	//resp.StateInfo.SystemTime = hex.EncodeToString(t)
+	//offset += 8
+	//resp.StateInfo.ProgramNum = resp.Data[offset]
+	//offset++
+	//resp.StateInfo.CruFileName = string(resp.Data[offset : offset+4])
+	//offset += 4
+	//resp.StateInfo.SpecialDynaArea = resp.Data[offset]
+	//offset++
+	//resp.StateInfo.PageNum = resp.Data[offset]
+	//offset++
+	//resp.StateInfo.DynaAreaNum = resp.Data[offset]
+	//offset++
+	//for i := byte(0); i < resp.StateInfo.ProgramNum; i++ {
+	//	resp.StateInfo.DynaAreaIDs = append(resp.StateInfo.DynaAreaIDs, resp.Data[offset])
+	//	offset++
+	//}
+	//resp.StateInfo.BarCode = string(resp.Data[offset : offset+16])
+	return resp
+}
+
+func (r BxResp) Parse(src []byte, len int) *BxResp {
+	dp := dpParse(src, len)
+	if dp == nil {
+		return nil
+	} else {
+		return parse_(*dp)
+	}
+}
+
+func (r BxResp) IsAck() bool {
+	if r.group == CMD_ACK.group && r.cmd == CMD_ACK.code {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (r BxResp) NoError() bool {
+	return r.Err == 0
+}
+
+func (r BxResp) Error() BxError {
+	return bxErrors[r.Err]
+}
+
+// IsInfo 是否是返回"控制器状态"信息
+func (r BxResp) IsInfo() bool {
+	if r.group == CMD_SYSTEM_STATE.group && r.cmd == CMD_SYSTEM_STATE.code {
+		return true
+	} else {
+		return false
+	}
+}
+
+type StateInfo struct {
+	OnOff           byte
+	Brightness      byte
+	SystemTime      string //年(2)+月(1)+日(1)+星期(1)+时(1)+分(1)+秒(1)
+	ProgramNum      byte
+	CruFileName     string
+	SpecialDynaArea byte
+	PageNum         byte
+	DynaAreaNum     byte
+	DynaAreaIDs     []byte
+	BarCode         string
+	//CustomId        string
+}
+
+func (s *StateInfo) Print(name string) {
+	info :=
+		`==== %s ====
+电源状态: %s
+系统时间: %s
+节目数量: %d
+当前播放: %s
+动态区数: %d
+条码: %s
+===============
+`
+	onoff := ""
+	if s.OnOff == 1 {
+		onoff = "开机"
+	} else {
+		onoff = "关机"
+	}
+	fmt.Printf(info, name, onoff, s.SystemTime, s.ProgramNum, s.CruFileName, s.DynaAreaNum, s.BarCode)
+}
+
+func (s *StateInfo) Parse(data []byte) {
+	offset := 0
+	s.OnOff = data[offset]
+	offset++
+	s.Brightness = data[offset]
+	offset++
+	t := data[offset : offset+8]
+	s.SystemTime = hex.EncodeToString(t)
+	offset += 8
+	s.ProgramNum = data[offset]
+	offset++
+	s.CruFileName = string(data[offset : offset+4])
+	offset += 4
+	s.SpecialDynaArea = data[offset]
+	offset++
+	s.PageNum = data[offset]
+	offset++
+	s.DynaAreaNum = data[offset]
+	offset++
+	for i := byte(0); i < s.DynaAreaNum; i++ {
+		s.DynaAreaIDs = append(s.DynaAreaIDs, data[offset])
+		offset++
+	}
+	s.BarCode = string(data[offset : offset+16])
+}
+
+type Params struct {
+	Address      uint16
+	DeviceType   byte
+	BaudRate     byte
+	ScreenWidth  uint16
+	ScreenHeight uint16
+	Color        byte
+	DA           byte
+	OE           byte
+	FreqPar      byte
+	RowOrder     byte
+	MirrorMode   byte
+	OEAngle      byte
+	ScanMode     byte
+	ScanConfNum  byte
+	LatticeMode  byte
+	//Reserved []byte
+}
+
+func (p *Params) Parse(data []byte) {
+	offset := 0
+	addr, err := BIN2Uint64(data[offset:offset+2], binary.LittleEndian)
+	if err == nil {
+		p.Address = uint16(addr)
+	}
+	offset += 2
+	p.DeviceType = data[offset]
+	offset++
+	p.BaudRate = data[offset]
+	offset++
+	w, err := BIN2Uint64(data[offset:offset+2], binary.LittleEndian)
+	if err == nil {
+		p.ScreenWidth = uint16(w)
+	}
+	offset += 2
+	h, err := BIN2Uint64(data[offset:offset+2], binary.LittleEndian)
+	if err == nil {
+		p.ScreenHeight = uint16(h)
+	}
+	offset += 2
+	p.Color = data[offset]
+	offset++
+	p.DA = data[offset]
+	offset++
+	p.OE = data[offset]
+	offset++
+	p.FreqPar = data[offset]
+	offset++
+	p.RowOrder = data[offset]
+	offset++
+	p.MirrorMode = data[offset]
+	offset++
+	p.OEAngle = data[offset]
+	offset++
+	p.ScanMode = data[offset]
+	offset++
+	p.ScanConfNum = data[offset]
+	offset++
+	//p.LatticeMode = data[offset]
+	//offset++
+}

+ 105 - 0
bx/BxUtils.go

@@ -0,0 +1,105 @@
+package bx
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"math"
+	"sync"
+)
+
+var bufPoll = sync.Pool{
+	New: func() interface{} {
+		var buf = make([]byte, 0, 210)
+		return bytes.NewBuffer(buf)
+	},
+}
+
+var crc16_table = []uint16{
+	0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
+	0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
+	0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
+	0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
+	0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
+	0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
+	0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
+	0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
+	0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
+	0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
+	0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
+	0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
+	0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
+	0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
+	0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
+	0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
+	0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
+	0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
+	0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
+	0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
+	0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
+	0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
+	0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
+	0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
+	0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
+	0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
+	0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
+	0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
+	0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
+	0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
+	0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
+	0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,
+}
+
+func bytesToUint16(src []byte, start int, order binary.ByteOrder) uint16 {
+	return order.Uint16(src[start:])
+}
+func CRC16(data []byte, offset, length int) uint16 {
+	var crc16 uint16
+	for _, v := range data[offset:length] {
+		n := uint8(uint16(v) ^ crc16)
+		crc16 >>= 8
+		crc16 ^= crc16_table[n]
+	}
+	return crc16
+}
+
+func Uint16ToBin(i uint16, order binary.ByteOrder) []byte {
+	buf := make([]byte, 2)
+	order.PutUint16(buf, i)
+	return buf
+}
+
+var lengthErr = errors.New("需要更大的长度存储该数值")
+
+func Uint2BIN(n uint64, len uint8, order binary.ByteOrder) ([]byte, error) {
+	switch len {
+	case 1:
+		if n > math.MaxUint8 {
+			return nil, lengthErr
+		}
+		return []byte{byte(n)}, nil
+	case 2:
+		if n > math.MaxUint16 {
+			return nil, lengthErr
+		}
+		b := make([]byte, 2)
+		order.PutUint16(b, uint16(n))
+		return b, nil
+	case 3, 4:
+		if n > math.MaxUint32 {
+			return nil, lengthErr
+		}
+		b := make([]byte, 4)
+		order.PutUint32(b, uint32(n))
+		return b, nil
+	case 5, 6, 7, 8:
+		if n > math.MaxUint64 {
+			return nil, lengthErr
+		}
+		b := make([]byte, 8)
+		order.PutUint64(b, n)
+		return b, nil
+	default:
+		return nil, errors.New("非法字节长度")
+	}
+}

+ 28 - 0
bx/BxUtils_test.go

@@ -0,0 +1,28 @@
+package bx
+
+import (
+	"encoding/binary"
+	"fmt"
+	"testing"
+)
+
+func TestCRC16(t *testing.T) {
+	data := []byte{
+		0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, //帧头
+		//包头
+		0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x02, 0x0D, 0x00,
+		//数据域
+		0xA2, 0x03, 0x01, 0x08, 0x00, 0x13, 0x20, 0x01, 0x25, 0x11, 0x17, 0x26, 0x05,
+		0xB0, 0x3F, //校验码,报头和数据域
+		0x5A, //帧尾
+	}
+	//
+	//
+	crc16 := CRC16(data[:len(data)-3], 8, len(data))
+	real := binary.LittleEndian.Uint16(data[len(data)-3 : len(data)-1])
+	if crc16 != real {
+		t.Error("不通过!结果:", crc16, " 预期:", real)
+	} else {
+		fmt.Println("结果:", crc16, " 预期:", real)
+	}
+}

+ 31 - 0
bx/bxError.go

@@ -0,0 +1,31 @@
+package bx
+
+type BxError struct {
+	ErrorCode   byte
+	Name        string
+	Description string
+}
+
+var bxErrors = []BxError{
+	BxError{0, "ERR_NO", "No Err"},
+	BxError{1, "ERR_OUTOFGROUP", "Command Group Err"},
+	BxError{2, "ERR_NOCMD", "Not Found"},
+	BxError{3, "ERR_BUSY", "The Controller is busy now"},
+	BxError{4, "ERR_MEMORYVOLUME", "Out of the Memory Volume"},
+	BxError{5, "ERR_CHECKSUM", "CRC16 Checksum Err"},
+	BxError{6, "ERR_FILENOTEXIST", "File Not Exist"},
+	BxError{7, "ERR_FLASH", "Flash Access Err"},
+	BxError{8, "ERR_FILE_DOWNLOAD", "File Download Err"},
+	BxError{9, "ERR_FILE_NAME", "Filename Err"},
+	BxError{10, "ERR_FILE_TYPE", "File type Err"},
+	BxError{11, "ERR_FILE_CRC16", "File CRC16 Err"},
+	BxError{12, "ERR_FONT_NOT_EXIST", "Font Library Not Exist"},
+	BxError{13, "ERR_FIRMWARE_TYPE", "Firmware Type Err (Check the controller type)"},
+	BxError{14, "ERR_DATE_TIME_FORMAT", "Date Time format Err"},
+	BxError{15, "ERR_FILE_EXIST", "File Exist for File overwrite"},
+	BxError{16, "ERR_FILE_BLOCK_NUM", "File block number Err"},
+}
+
+func GetError(code byte) BxError {
+	return bxErrors[code]
+}

+ 87 - 0
cmd.go

@@ -0,0 +1,87 @@
+package main
+
+import (
+	"fmt"
+	"github.com/chzyer/readline"
+	"os"
+	"strings"
+)
+
+func lcCmdServer() {
+	//rl, err := readline.New("> ")
+	rl := readline.NewCancelableStdin(os.Stdin)
+	readline.NewFillableStdin(os.Stdin)
+	//if err != nil {
+	//	fmt.Println("start cmd server Error:", err)
+	//	return
+	//}
+	defer rl.Close()
+
+	for {
+		var line []byte
+		_, err := rl.Read(line)
+		if err != nil {
+			fmt.Println("read line Error:", err)
+			break
+		}
+
+		// 判断输入是否以"lc"作为前缀
+		if strings.HasPrefix(string(line), "lc") {
+			args := strings.Fields(string(line)) // 将输入按空格分割成多个字段
+			if len(args) >= 2 {
+				command := args[1]
+				switch command {
+				case "rm":
+					handleRmCommand(args[2:])
+				case "text":
+					handleTextCommand(args[2:])
+				default:
+					fmt.Println("Unknown command:", command)
+				}
+			} else {
+				fmt.Println("Missing command")
+			}
+		} else {
+			fmt.Println("Invalid command")
+		}
+	}
+
+	//inputReader := bufio.NewReader(os.Stdin)
+	//fmt.Printf("Please enter your name:")
+	//input, err := inputReader.ReadString('\n')
+	//if err != nil {
+	//	fmt.Println("There were errors reading, exiting program.")
+	//	return
+	//}
+	//fmt.Printf("Your name is %s", input)
+	//switch input {
+	//case "yinzhengjie\n":
+	//	fmt.Println("Welcome yinzhengjie!")
+	//case "bingan\n":
+	//	fmt.Println("Welcome bingan!")
+	//case "liufei\n":
+	//	fmt.Println("Welcome liufei")
+	//default:
+	//	fmt.Println("You are not welcome here! Goodbye!")
+	//}
+
+}
+
+func handleRmCommand(args []string) {
+	// 处理rm指令
+	for _, arg := range args {
+		fmt.Println("Deleting:", arg)
+		// 执行删除操作...
+	}
+}
+
+func handleTextCommand(args []string) {
+	// 处理text指令
+	if len(args) > 0 {
+		text := strings.Join(args, " ")
+		fmt.Println("Text:", text)
+		// 执行文本处理操作...
+	} else {
+		fmt.Println("Missing text argument")
+	}
+}

+ 15 - 0
config.yaml

@@ -0,0 +1,15 @@
+hikServer:
+  addr : ":8850"
+  path : "/event"
+cameras:
+  -
+    ip: "192.168.1.64"     #192.168.1.64	 e0:ca:3c:6a:26:8c
+    name: "支路1摄像头"
+    branch: 0
+ledServerAddr: ":5005"
+outputDevices:
+  -
+    name: "主路1输出设备"
+    screen_ip: "192.168.110.200"
+    loudspeaker_ip: "...地址"
+    branch: 1

+ 47 - 0
demo/main.go

@@ -0,0 +1,47 @@
+package main
+
+import (
+	"fmt"
+	"time"
+)
+
+func main() {
+	s := "2023-12-30"
+	//4BC6 [4,11,12,6]
+	t, err := time.Parse("2006-01-02", s)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	y := Uint2BCD(uint64(t.Year()), true)
+	fmt.Println("year:", y)
+	m := Uint2BCD(uint64(t.Month()), true)
+	fmt.Println("mouth:", m)
+	d := Uint2BCD(uint64(t.Day()), true)
+	fmt.Println("day:", d)
+}
+
+func Uint2BCD(n uint64, isBigEndian bool) []byte {
+	var b []byte
+	//if n < 256 {
+	//	b = []byte{0}
+	//}
+	for i := 0; ; i++ {
+		h := (n / 10) % 10
+		l := n % 10
+		b = append(b, byte(h<<4|l))
+		n = n / 100
+		if n == 0 {
+			break
+		}
+	}
+	if !isBigEndian {
+		return b
+	}
+	l := len(b)
+	var r = make([]byte, l)
+	for i, v := range b {
+		r[l-1-i] = v
+	}
+	return r
+}

+ 14 - 0
go.mod

@@ -0,0 +1,14 @@
+module lc-smartX
+
+go 1.20
+
+require (
+	github.com/chzyer/readline v1.5.1 // indirect
+	github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
+	github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 28 - 0
go.sum

@@ -0,0 +1,28 @@
+github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
+github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
+github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
+github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4=
+github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8=
+github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
+github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


+ 37 - 0
lc/IDevice.go

@@ -0,0 +1,37 @@
+package lc
+
+import (
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"lc-smartX/util"
+	"net"
+)
+
+// IDevice 抽象设备,包含屏和喇叭
+type IDevice interface {
+	Call()
+	ReConnect()
+}
+
+type IntersectionDevice struct {
+	Info util.OutputDevice
+	S    Screen
+	L    IpCast
+}
+
+func (id *IntersectionDevice) Call() {
+	id.L.Speak()
+	id.S.Display()
+}
+
+func (id *IntersectionDevice) ReConnect() {
+	conn, err := net.Dial("tcp", id.Info.ScreenIp+":5000")
+	if err != nil {
+		logrus.Error("连接", id.Info.ScreenIp, "失败!error:", err)
+		return
+	}
+	logrus.Info("连接", id.Info.ScreenIp, "成功!")
+	fmt.Println("连接", id.Info.ScreenIp, "成功!")
+	id.S.Conn = conn
+	id.S.IsLive = true
+}

BIN
lc/bitmap/T000.bcm


+ 113 - 0
lc/event.go

@@ -0,0 +1,113 @@
+package lc
+
+import (
+	"encoding/xml"
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"io"
+	"io/ioutil"
+	"lc-smartX/lc/model"
+	"lc-smartX/util"
+	"lc-smartX/util/gopool"
+	"log"
+	"mime/multipart"
+	"net/http"
+	"strings"
+	"time"
+)
+
+type Notifier interface {
+	Notify()
+}
+
+func StartEventServer() {
+	S = &Server{Cameras: util.Config.Cameras, Notifiers: make(map[string]Notifier, 4)}
+	fmt.Println("cameras:", S.Cameras)
+	S.start()
+}
+
+var S *Server
+
+type Server struct {
+	Cameras   []model.CameraInfo
+	Notifiers map[string]Notifier
+}
+
+func (s *Server) start() {
+	http.HandleFunc(util.Config.HikServer.Path, handler)
+	logrus.Fatal("事件监听服务启动失败:", util.Config.HikServer.Addr, ", error:", http.ListenAndServe(util.Config.HikServer.Addr, nil))
+}
+
+func RegisterCallback(branch byte, notifer Notifier) {
+	for _, camera := range S.Cameras {
+		//关联主路led屏和支路摄像头;关联支路led屏和主路摄像头
+		if branch == 0 && camera.Branch == 1 || branch == 1 && camera.Branch == 0 {
+			S.Notifiers[camera.IP] = notifer
+		}
+	}
+}
+
+func (s *Server) Callback(ip string) {
+	notifier, ok := s.Notifiers[ip]
+	if !ok {
+		logrus.Errorf("回调函数注册表没有该ip:%s", ip)
+		return
+	}
+	notifier.Notify()
+	logrus.Debugf("camera %s Callback", ip)
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+	gopool.Go(func() {
+		//监听主机应答固定,直接先应答
+		w.WriteHeader(200)
+		w.Header().Add("Date", time.Now().String())
+		w.Header().Add("Connection", "keep-alive")
+	})
+	//1.
+	contentType := r.Header.Get("Content-Type")
+	if strings.Contains(contentType, "application/xml") {
+		bytes, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			return
+		}
+		var event model.EventNotificationAlert
+		err = xml.Unmarshal(bytes, &event)
+		if err != nil {
+			return
+		}
+		//发送事件通知
+		S.Callback(event.IpAddress)
+	} else if strings.Contains(contentType, "multipart/form-data") {
+		handleMultipart(r)
+	}
+}
+
+// 处理多文件事件
+func handleMultipart(r *http.Request) {
+	multipartReader := multipart.NewReader(r.Body, "boundary")
+	// 循环读取每个 part
+	var event model.EventNotificationAlert
+	for {
+		part, err := multipartReader.NextPart()
+		//defer part.Close()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			log.Println("Failed to read part:", err)
+			return
+		}
+		if part.FormName() != "linedetectionImage" || !strings.Contains(part.FormName(), "Image") {
+			//不含图片的xml部分数据
+			xmlData, err := ioutil.ReadAll(part)
+			if err != nil {
+				return
+			}
+			xml.Unmarshal(xmlData, &event)
+			continue
+		}
+	}
+	//发送事件通知
+	S.Callback(event.IpAddress)
+}

+ 14 - 0
lc/loudspeaker.go

@@ -0,0 +1,14 @@
+package lc
+
+import "fmt"
+
+type Loudspeaker interface {
+	Speak()
+}
+
+type IpCast struct {
+}
+
+func (ip IpCast) Speak() {
+	fmt.Println("语音")
+}

+ 7 - 0
lc/model/camera.go

@@ -0,0 +1,7 @@
+package model
+
+type CameraInfo struct {
+	IP     string `yaml:"ip"`
+	Name   string `yaml:"name"`
+	Branch byte   `yaml:"branch"`
+}

+ 42 - 0
lc/model/event.go

@@ -0,0 +1,42 @@
+package model
+
+type EventNotificationAlert struct {
+	IpAddress           string `xml:"ipAddress"`
+	PortNo              string `xml:"portNo"`
+	Protocol            string `xml:"protocol"`
+	MacAddress          string `xml:"macAddress"`
+	ChannelID           string `xml:"channelID"`
+	DateTime            string `xml:"dateTime"`
+	ActivePostCount     string `xml:"activePostCount"`
+	EventType           string `xml:"eventType"`
+	EventState          string `xml:"eventState"`
+	EventDescription    string `xml:"eventDescription"`
+	DetectionRegionList struct {
+		DetectionRegionEntry []struct {
+			RegionID              string `xml:"regionID"`
+			SensitivityLevel      string `xml:"sensitivityLevel"`
+			RegionCoordinatesList struct {
+				RegionCoordinates []struct {
+					PositionX string `xml:"positionX"`
+					PositionY string `xml:"positionY"`
+				} `xml:"RegionCoordinates"`
+			} `xml:"RegionCoordinatesList"`
+			DetectionTarget string `xml:"detectionTarget"`
+			TargetRect      struct {
+				X      string `xml:"X"`
+				Y      string `xml:"Y"`
+				Width  string `xml:"width"`
+				Height string `xml:"height"`
+			} `xml:"TargetRect"`
+		} `xml:"DetectionRegionEntry"`
+	} `xml:"DetectionRegionList"`
+	ChannelName               string `xml:"channelName"`
+	DetectionPictureTransType string `xml:"detectionPictureTransType"`
+	DetectionPicturesNumber   string `xml:"detectionPicturesNumber"`
+	IsDataRetransmission      string `xml:"isDataRetransmission"`
+	DurationList              struct {
+		Duration []struct {
+			RelationEvent string `xml:"relationEvent"`
+		} `xml:"Duration"`
+	} `xml:"DurationList"`
+}

+ 316 - 0
lc/screen.go

@@ -0,0 +1,316 @@
+package lc
+
+import (
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"golang.org/x/text/encoding/simplifiedchinese"
+	"lc-smartX/bx"
+	"net"
+	"strconv"
+	"time"
+)
+
+type Screener interface {
+	Display()
+}
+
+type Screen struct {
+	Id        byte
+	Sn        string //B006K12311020021
+	Name      string
+	Type      byte
+	FileNum   byte
+	Files     []string
+	Conn      net.Conn
+	IsLive    bool
+	StateInfo *bx.StateInfo //状态信息
+	Params    *bx.Params    //屏参
+}
+
+func NewScreen(name string, conn net.Conn) Screen {
+	s := Screen{
+		Name:      name,
+		Conn:      conn,
+		IsLive:    true,
+		StateInfo: &bx.StateInfo{},
+		Params:    &bx.Params{},
+	}
+	state := s.State()
+	s.StateInfo.Parse(state.Data)
+	params := s.Param()
+	s.Params.Parse(params.Data)
+	return s
+}
+
+func (s *Screen) Display() {
+	s.Lock(1, "P001")
+}
+
+func (s *Screen) send(data []byte) {
+	if !s.IsLive {
+		fmt.Println("连接已断开!")
+		return
+	}
+	_, err := s.Conn.Write(data)
+	if err != nil {
+		logrus.Error("tcp write error:", err)
+		s.IsLive = false
+	}
+}
+
+// CorrectTime 校正时间
+func (s *Screen) CorrectTime() (ok bool) {
+	if !s.IsLive {
+		return false
+	}
+	cmd := bx.NewBxCmdSystemClockCorrect(time.Now())
+	data := bx.NewBxDataPackCmd(cmd)
+	_, err := s.Conn.Write(data.Pack())
+	if err != nil {
+		logrus.WithFields(map[string]interface{}{"Id": s.Id, "Name": s.Name}).Errorf("Conn写数据失败:%v", err)
+		return false
+	}
+	return true
+}
+
+type Color byte
+
+const (
+	Default Color = iota
+	Red
+	Green
+	Yellow
+	Blue
+	LightBlue
+	LightPurple
+	White
+	None
+)
+
+// TextRam
+// 0x01——静止显示
+// 0x02——快速打出
+// 0x03——向左移动
+// 0x04——向右移动
+// 0x05——向上移动
+// 0x06——向下移动
+func (s *Screen) TextRam(str string, color Color, playMode byte) {
+	if !s.IsLive {
+		return
+	}
+	var areas []bx.BxArea
+	//var id byte
+	//var x, y int16
+	//var w, h int16 = 64, 16
+	encoder := simplifiedchinese.GB18030.NewEncoder()
+	bytes, err := encoder.Bytes([]byte("\\C" + strconv.Itoa(int(color)) + str))
+	if err != nil {
+		logrus.Error("编码转换失败:", err)
+		return
+	}
+	x := 0x8000
+	w := 0x8040
+	area := bx.NewBxAreaDynamic(s.StateInfo.DynaAreaNum, playMode, int16(x), 0, int16(w), 16, bytes, false)
+	areas = append(areas, area)
+	//
+	cmd := bx.NewBxCmdSendDynamicArea(areas)
+	pack := bx.NewBxDataPackCmd(cmd)
+	pack.SetDispType(2) //动态显示模式
+	d := pack.Pack()
+	s.send(d)
+	s.ReadResp()
+	s.StateInfo.DynaAreaNum++
+}
+
+// DelRamText 删除动态区,不传删除所有
+func (s *Screen) DelRamText(numbers ...byte) {
+	if !s.IsLive {
+		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--
+	}
+}
+
+func (s *Screen) TextFlash(msg string, color Color) {
+	if !s.IsLive {
+		return
+	}
+	encoder := simplifiedchinese.GB18030.NewEncoder()
+	var bytes []byte
+	//
+	if color == None {
+		bytes = []byte(msg)
+	} else {
+		bytes, _ = encoder.Bytes([]byte("\\C" + strconv.Itoa(int(color)) + msg))
+	}
+	x := 0x8000
+	w := 0x8040
+	area := bx.NewBxAreaDynamic(0xff, 2, int16(x), 0, int16(w), 16, bytes, false)
+	//if s.FileNum
+	name := fmt.Sprintf("P%03d", s.StateInfo.ProgramNum)
+	fmt.Println("文件名:", name)
+	file := bx.NewBxFile(name, "", []bx.BxArea{area})
+	cmd := file.NewCmdWriteFile()
+	pack := bx.NewBxDataPackCmd(cmd)
+	data := pack.Pack()
+	fmt.Println("数据长度:", len(data))
+	fmt.Printf("数据:% 02x\n", data)
+	s.send(data)
+	resp := s.ReadResp()
+	if !resp.IsAck() {
+		logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
+		return
+	}
+	pack1 := bx.NewBxDataPackCmd(cmd)
+	data1 := pack1.Pack()
+	fmt.Println("数据长度:", len(data))
+	s.send(data1)
+	fmt.Printf("数据1:% 02x\n", data1)
+	resp1 := s.ReadResp()
+	if resp1.NoError() {
+		s.StateInfo.ProgramNum++
+	}
+}
+
+func (s *Screen) Bitmap(name string, bitmap []byte) {
+	file := bx.NewBitmapFile(name, bitmap)
+	cmd := file.NewCmd()
+	pack := bx.NewBxDataPackCmd(cmd)
+	data := pack.Pack()
+	fmt.Println("数据长度:", len(data))
+	fmt.Printf("数据:% 02x\n", data)
+	s.send(data)
+	resp := s.ReadResp()
+	if !resp.IsAck() {
+		logrus.Error("设备拒绝写文件! error:", resp.Error().Description)
+		return
+	}
+	pack1 := bx.NewBxDataPackCmd(cmd)
+	data1 := pack1.Pack()
+	fmt.Println("数据长度1:", len(data1))
+	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())
+}
+
+func (s *Screen) QueryFile() {
+
+}
+
+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--
+	}
+}
+
+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.IsLive {
+		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.IsLive {
+		return
+	}
+	cmd := bx.NewCmdTimingSwitch(onOffSet)
+	pack := bx.NewBxDataPackCmd(cmd)
+	s.send(pack.Pack())
+}
+
+func (s *Screen) CancelTimingSwitch() {
+	if !s.IsLive {
+		return
+	}
+	cmd := bx.NewCmdCancelTimingSwitch()
+	pack := bx.NewBxDataPackCmd(cmd)
+	s.send(pack.Pack())
+}
+
+func (s *Screen) State() *bx.BxResp {
+	if !s.IsLive {
+		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.IsLive {
+		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)
+}
+
+func (s *Screen) ReadResp() bx.BxResp {
+	var resp = make([]byte, 1024)
+	read, err := s.Conn.Read(resp)
+	if err != nil {
+		logrus.Error("读数据错误:", err)
+		s.IsLive = 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
+}

+ 171 - 0
lc/server.go

@@ -0,0 +1,171 @@
+package lc
+
+import (
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"lc-smartX/util"
+	"lc-smartX/util/gopool"
+	"net"
+	"strings"
+	"time"
+)
+
+type Controller interface {
+	StartServer()
+}
+
+var Ctl = IntersectionCtl{
+	Main:     time.NewTicker(5 * time.Second),
+	Sub:      time.NewTicker(5 * time.Second),
+	ReTicker: time.NewTicker(18 * time.Second),
+}
+
+type IntersectionCtl struct {
+	MainState   byte
+	SubState    byte
+	MainDevices []IntersectionDevice
+	SubDevices  []IntersectionDevice
+	ReTicker    *time.Ticker
+	Main        *time.Ticker
+	Sub         *time.Ticker
+}
+
+type MainNotifier struct{}
+
+// Notify 主路来车,通知支路设备
+func (m MainNotifier) Notify() {
+	Ctl.Main.Reset(5 * time.Second)
+	if Ctl.MainState != 1 {
+		Ctl.MainState = 1
+		for _, v := range Ctl.SubDevices {
+			if v.S.IsLive {
+				gopool.Go(v.Call)
+			}
+		}
+	}
+}
+
+type SubNotifier struct{}
+
+// Notify 支路来车,通知主路设备
+func (s SubNotifier) Notify() {
+	Ctl.Sub.Reset(5 * time.Second)
+	if Ctl.SubState != 1 {
+		Ctl.SubState = 1
+		for _, v := range Ctl.MainDevices {
+			if v.S.IsLive {
+				gopool.Go(v.Call)
+			}
+		}
+	}
+}
+
+func (ctl *IntersectionCtl) StartServer() {
+	RegisterCallback(1, &SubNotifier{})
+	RegisterCallback(0, &MainNotifier{})
+	//先创建响应设备
+	for _, v := range util.Config.OutputDevices {
+		iDevice := IntersectionDevice{
+			Info: v,
+		}
+		iDevice.ReConnect() //初次连接
+		if iDevice.Info.Branch == 1 {
+			ctl.MainDevices = append(ctl.MainDevices, iDevice)
+		} else {
+			ctl.SubDevices = append(ctl.SubDevices, iDevice)
+		}
+	}
+	logrus.Info("主路输出设备列表:", ctl.MainDevices)
+	logrus.Info("支路输出设备列表:", ctl.SubDevices)
+	fmt.Println("主路输出设备列表:", ctl.MainDevices)
+	fmt.Println("支路输出设备列表:", ctl.SubDevices)
+	for {
+		select {
+		case <-ctl.Main.C: //检查主路状态->支路输出设备作出响应
+			for _, v := range Ctl.SubDevices {
+				if v.S.IsLive && ctl.MainState == 1 {
+					back := func() {
+						v.S.Lock(1, "P000")
+					}
+					gopool.Go(back)
+					//go v.S.Lock(1, "P000")
+				}
+			}
+			ctl.MainState = 0
+		case <-ctl.Sub.C: //检查支路状态->主路输出设备作出响应
+			for _, v := range Ctl.MainDevices {
+				if v.S.IsLive && ctl.SubState == 1 {
+					back := func() {
+						v.S.Lock(1, "P000")
+					}
+					gopool.Go(back)
+					//go v.S.Lock(1, "P000")
+				}
+			}
+			ctl.SubState = 0
+		case <-ctl.ReTicker.C: //每18s尝试重连
+			gopool.Go(func() {
+				for _, v := range ctl.MainDevices {
+					if v.S.IsLive {
+						continue
+					}
+					logrus.Info("reconnect")
+					fmt.Println("reconnect")
+					v.ReConnect()
+				}
+			})
+			gopool.Go(func() {
+				for _, v := range ctl.SubDevices {
+					if v.S.IsLive {
+						continue
+					}
+					logrus.Info("reconnect")
+					fmt.Println("reconnect")
+					v.ReConnect()
+				}
+			})
+		}
+	}
+}
+
+// LServer ###
+// ===
+// ===
+// ===
+// === todo 服务器模式没测通,屏没有连接服务器
+var LServer LedServer
+
+type LedServer struct {
+}
+
+func (ls LedServer) Start() {
+	listener, err := net.Listen("tcp", "192.168.110.69"+util.Config.LedServerAddr)
+	if err != nil {
+		fmt.Println("服务启动失败:", err)
+		return
+	}
+	fmt.Println("led屏服务启动成功!addr:", listener.Addr())
+	for {
+		conn, err := listener.Accept()
+		if err != nil {
+			continue
+		}
+		go func(c net.Conn) {
+			ip := strings.Split(c.RemoteAddr().String(), ":")[0]
+			for i, v := range Ctl.MainDevices {
+				if v.Info.ScreenIp == ip {
+					fmt.Println("主路屏幕注册,ip:", ip)
+					screen := NewScreen(v.Info.Name, c)
+					Ctl.MainDevices[i].S = screen
+				}
+			}
+			for i, v := range Ctl.SubDevices {
+				if v.Info.ScreenIp == ip {
+					fmt.Println("支路屏幕注册,ip:", ip)
+					screen := NewScreen(v.Info.Name, c)
+					Ctl.SubDevices[i].S = screen
+				}
+			}
+		}(conn)
+	}
+}

+ 0 - 0
lc/store.json


+ 62 - 0
main.go

@@ -0,0 +1,62 @@
+package main
+
+import (
+	"lc-smartX/lc"
+	"lc-smartX/util/gopool"
+	"time"
+)
+
+func main() {
+	gopool.SetCap(64)
+	//gopool.Go(lcCmdServer)
+	time.Sleep(10 * time.Second)
+	gopool.Go(lc.StartEventServer)
+	//等事件服务先启动
+	time.Sleep(1 * time.Second)
+	lc.Ctl.StartServer()
+
+	//conn, err := net.Dial("tcp", "192.168.110.200:5000")
+	//if err != nil {
+	//	logrus.Fatalln("建立连接失败:", err)
+	//}
+	//client := lc.NewScreen("支路1屏", conn)
+
+	//开关机
+	//client.TurnOnOff(false)
+
+	//清除所有文件
+	//client.DelFile("")
+
+	//删除指定文件
+	//client.DelFile("P002")
+
+	//发送文件节目
+	//client.TextFlash("减速慢行", lc.Yellow)
+	//client.TextFlash("支路来车", lc.Red)
+
+	//发送位图文件-不报错,不成功
+	//file, err := os.ReadFile("./lc/bitmap/T000.bcm")
+	//if err != nil {
+	//	logrus.Error("读取图库文件失败:", err)
+	//	return
+	//}
+	//client.Bitmap("T000", file)
+
+	//发送动态区
+	//0,1红,2绿,3黄,4蓝,5浅蓝,6浅紫,7白
+	//client.TextRam("支路来车", lc.Red, 1)
+
+	//锁定/解锁节目
+	//client.Lock(1, "P000")
+
+	//删除动态区
+	//client.DelRamText(0)
+
+	//client.Lock(1, "P000")
+	//time.Sleep(time.Second * 1)
+	//查询当前状态
+	//client.State()
+
+	//client.Info()
+	time.Sleep(time.Second * 15)
+}

+ 40 - 0
util/config.go

@@ -0,0 +1,40 @@
+package util
+
+import (
+	"github.com/sirupsen/logrus"
+	"gopkg.in/yaml.v3"
+	"lc-smartX/lc/model"
+	"os"
+)
+
+var Config = func() config {
+	var conf config
+	file, err := os.ReadFile("./config.yaml")
+	if err != nil {
+		logrus.Fatal("配置文件读取失败! error:", err)
+	}
+	err = yaml.Unmarshal(file, &conf)
+	if err != nil {
+		logrus.Fatal("配置文件解析失败! error:", err)
+	}
+	return conf
+}()
+
+type config struct {
+	LedServerAddr string             `yaml:"ledServerAddr"`
+	HikServer     hikServer          `yaml:"hikServer"`
+	Cameras       []model.CameraInfo `yaml:"cameras"`
+	OutputDevices []OutputDevice     `yaml:"outputDevices"`
+}
+
+type hikServer struct {
+	Addr string `yaml:"addr"`
+	Path string `yaml:"path"`
+}
+
+type OutputDevice struct {
+	Name          string `yaml:"name"`
+	ScreenIp      string `yaml:"screen_ip"`
+	LoudspeakerIp string `yaml:"loudspeaker_ip"`
+	Branch        byte   `yaml:"branch"`
+}

+ 1 - 0
util/encoding.go

@@ -0,0 +1 @@
+package util

+ 35 - 0
util/gopool/config.go

@@ -0,0 +1,35 @@
+// Copyright 2021 ByteDance Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gopool
+
+const (
+	defaultScalaThreshold = 1
+)
+
+// Config is used to config pool.
+type Config struct {
+	// threshold for scale.
+	// new goroutine is created if len(task chan) > ScaleThreshold.
+	// defaults to defaultScalaThreshold.
+	ScaleThreshold int32
+}
+
+// NewConfig creates a default Config.
+func NewConfig() *Config {
+	c := &Config{
+		ScaleThreshold: defaultScalaThreshold,
+	}
+	return c
+}

+ 80 - 0
util/gopool/gopoll.go

@@ -0,0 +1,80 @@
+// Copyright 2021 ByteDance Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gopool
+
+import (
+	"context"
+	"fmt"
+	"sync"
+)
+
+// defaultPool is the global default pool.
+var defaultPool Pool
+
+var poolMap sync.Map
+
+func init() {
+	defaultPool = NewPool("gopool.DefaultPool", 10000, NewConfig())
+}
+
+// Go is an alternative to the go keyword, which is able to recover panic.
+//
+//	gopool.Go(func(arg interface{}){
+//	    ...
+//	}(nil))
+func Go(f func()) {
+	CtxGo(context.Background(), f)
+}
+
+// CtxGo is preferred than Go.
+func CtxGo(ctx context.Context, f func()) {
+	defaultPool.CtxGo(ctx, f)
+}
+
+// SetCap is not recommended to be called, this func changes the global pool's capacity which will affect other callers.
+func SetCap(cap int32) {
+	defaultPool.SetCap(cap)
+}
+
+// SetPanicHandler sets the panic handler for the global pool.
+func SetPanicHandler(f func(context.Context, interface{})) {
+	defaultPool.SetPanicHandler(f)
+}
+
+// WorkerCount returns the number of global default pool's running workers
+func WorkerCount() int32 {
+	return defaultPool.WorkerCount()
+}
+
+// RegisterPool registers a new pool to the global map.
+// GetPool can be used to get the registered pool by name.
+// returns error if the same name is registered.
+func RegisterPool(p Pool) error {
+	_, loaded := poolMap.LoadOrStore(p.Name(), p)
+	if loaded {
+		return fmt.Errorf("name: %s already registered", p.Name())
+	}
+	return nil
+}
+
+// GetPool gets the registered pool by name.
+// Returns nil if not registered.
+func GetPool(name string) Pool {
+	p, ok := poolMap.Load(name)
+	if !ok {
+		return nil
+	}
+	return p.(Pool)
+}

+ 156 - 0
util/gopool/poll.go

@@ -0,0 +1,156 @@
+// Copyright 2021 ByteDance Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gopool
+
+import (
+	"context"
+	"sync"
+	"sync/atomic"
+)
+
+type Pool interface {
+	// Name returns the corresponding pool name.
+	Name() string
+	// SetCap sets the goroutine capacity of the pool.
+	SetCap(cap int32)
+	// Go executes f.
+	Go(f func())
+	// CtxGo executes f and accepts the context.
+	CtxGo(ctx context.Context, f func())
+	// SetPanicHandler sets the panic handler.
+	SetPanicHandler(f func(context.Context, interface{}))
+	// WorkerCount returns the number of running workers
+	WorkerCount() int32
+}
+
+var taskPool sync.Pool
+
+func init() {
+	taskPool.New = newTask
+}
+
+type task struct {
+	ctx context.Context
+	f   func()
+
+	next *task
+}
+
+func (t *task) zero() {
+	t.ctx = nil
+	t.f = nil
+	t.next = nil
+}
+
+func (t *task) Recycle() {
+	t.zero()
+	taskPool.Put(t)
+}
+
+func newTask() interface{} {
+	return &task{}
+}
+
+type taskList struct {
+	sync.Mutex
+	taskHead *task
+	taskTail *task
+}
+
+type pool struct {
+	// The name of the pool
+	name string
+
+	// capacity of the pool, the maximum number of goroutines that are actually working
+	cap int32
+	// Configuration information
+	config *Config
+	// linked list of tasks
+	taskHead  *task
+	taskTail  *task
+	taskLock  sync.Mutex
+	taskCount int32
+
+	// Record the number of running workers
+	workerCount int32
+
+	// This method will be called when the worker panic
+	panicHandler func(context.Context, interface{})
+}
+
+// NewPool creates a new pool with the given name, cap and config.
+func NewPool(name string, cap int32, config *Config) Pool {
+	p := &pool{
+		name:   name,
+		cap:    cap,
+		config: config,
+	}
+	return p
+}
+
+func (p *pool) Name() string {
+	return p.name
+}
+
+func (p *pool) SetCap(cap int32) {
+	atomic.StoreInt32(&p.cap, cap)
+}
+
+func (p *pool) Go(f func()) {
+	p.CtxGo(context.Background(), f)
+}
+
+func (p *pool) CtxGo(ctx context.Context, f func()) {
+	t := taskPool.Get().(*task)
+	t.ctx = ctx
+	t.f = f
+	p.taskLock.Lock()
+	if p.taskHead == nil {
+		p.taskHead = t
+		p.taskTail = t
+	} else {
+		p.taskTail.next = t
+		p.taskTail = t
+	}
+	p.taskLock.Unlock()
+	atomic.AddInt32(&p.taskCount, 1)
+	// The following two conditions are met:
+	// 1. the number of tasks is greater than the threshold.
+	// 2. The current number of workers is less than the upper limit p.cap.
+	// or there are currently no workers.
+	if (atomic.LoadInt32(&p.taskCount) >= p.config.ScaleThreshold && p.WorkerCount() < atomic.LoadInt32(&p.cap)) || p.WorkerCount() == 0 {
+		p.incWorkerCount()
+		w := workerPool.Get().(*worker)
+		w.pool = p
+		w.run()
+	}
+}
+
+// SetPanicHandler the func here will be called after the panic has been recovered.
+func (p *pool) SetPanicHandler(f func(context.Context, interface{})) {
+	p.panicHandler = f
+}
+
+func (p *pool) WorkerCount() int32 {
+	return atomic.LoadInt32(&p.workerCount)
+}
+
+func (p *pool) incWorkerCount() {
+	atomic.AddInt32(&p.workerCount, 1)
+}
+
+func (p *pool) decWorkerCount() {
+	atomic.AddInt32(&p.workerCount, -1)
+}

+ 96 - 0
util/gopool/poll_test.go

@@ -0,0 +1,96 @@
+// Copyright 2021 ByteDance Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gopool
+
+import (
+	"runtime"
+	"sync"
+	"sync/atomic"
+	"testing"
+)
+
+const benchmarkTimes = 10000
+
+func DoCopyStack(a, b int) int {
+	if b < 100 {
+		return DoCopyStack(0, b+1)
+	}
+	return 0
+}
+
+func testFunc() {
+	DoCopyStack(0, 0)
+}
+
+func testPanicFunc() {
+	panic("test")
+}
+
+func TestPool(t *testing.T) {
+	p := NewPool("test", 100, NewConfig())
+	var n int32
+	var wg sync.WaitGroup
+	for i := 0; i < 2000; i++ {
+		wg.Add(1)
+		p.Go(func() {
+			defer wg.Done()
+			atomic.AddInt32(&n, 1)
+		})
+	}
+	wg.Wait()
+	if n != 2000 {
+		t.Error(n)
+	}
+}
+
+func TestPoolPanic(t *testing.T) {
+	p := NewPool("test", 100, NewConfig())
+	p.Go(testPanicFunc)
+}
+
+func BenchmarkPool(b *testing.B) {
+	config := NewConfig()
+	config.ScaleThreshold = 1
+	p := NewPool("benchmark", int32(runtime.GOMAXPROCS(0)), config)
+	var wg sync.WaitGroup
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		wg.Add(benchmarkTimes)
+		for j := 0; j < benchmarkTimes; j++ {
+			p.Go(func() {
+				testFunc()
+				wg.Done()
+			})
+		}
+		wg.Wait()
+	}
+}
+
+func BenchmarkGo(b *testing.B) {
+	var wg sync.WaitGroup
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		wg.Add(benchmarkTimes)
+		for j := 0; j < benchmarkTimes; j++ {
+			go func() {
+				testFunc()
+				wg.Done()
+			}()
+		}
+		wg.Wait()
+	}
+}

+ 87 - 0
util/gopool/worker.go

@@ -0,0 +1,87 @@
+// Copyright 2021 ByteDance Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gopool
+
+import (
+	"fmt"
+	//"github.com/flipped-aurora/gin-vue-admin/server/edge/util/logger"
+	"github.com/sirupsen/logrus"
+	"runtime/debug"
+	"sync"
+	"sync/atomic"
+)
+
+var workerPool sync.Pool
+
+func init() {
+	workerPool.New = newWorker
+}
+
+type worker struct {
+	pool *pool
+}
+
+func newWorker() interface{} {
+	return &worker{}
+}
+
+func (w *worker) run() {
+	go func() {
+		for {
+			var t *task
+			w.pool.taskLock.Lock()
+			if w.pool.taskHead != nil {
+				t = w.pool.taskHead
+				w.pool.taskHead = w.pool.taskHead.next
+				atomic.AddInt32(&w.pool.taskCount, -1)
+			}
+			if t == nil {
+				// if there's no task to do, exit
+				w.close()
+				w.pool.taskLock.Unlock()
+				w.Recycle()
+				return
+			}
+			w.pool.taskLock.Unlock()
+			func() {
+				defer func() {
+					if r := recover(); r != nil {
+						if w.pool.panicHandler != nil {
+							w.pool.panicHandler(t.ctx, r)
+						} else {
+							msg := fmt.Sprintf("GOPOOL: panic in pool: %s: %v: %s", w.pool.name, r, debug.Stack())
+							logrus.Error(t.ctx, msg)
+						}
+					}
+				}()
+				t.f()
+			}()
+			t.Recycle()
+		}
+	}()
+}
+
+func (w *worker) close() {
+	w.pool.decWorkerCount()
+}
+
+func (w *worker) zero() {
+	w.pool = nil
+}
+
+func (w *worker) Recycle() {
+	w.zero()
+	workerPool.Put(w)
+}

+ 27 - 0
util/logrus.go

@@ -0,0 +1,27 @@
+package util
+
+import (
+	rotatelogs "github.com/lestrrat/go-file-rotatelogs"
+	"github.com/sirupsen/logrus"
+	"os"
+	"path"
+	"time"
+)
+
+func InitLogrus() {
+	err := os.MkdirAll("./log", os.ModeDir)
+	if err != nil {
+		panic(err)
+	}
+	fileName := path.Join("./log", "info")
+	writer, _ := rotatelogs.New(
+		fileName+".%Y%m%d.log",
+		rotatelogs.WithMaxAge(15*24*time.Hour),    // 文件最大保存时间
+		rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
+	)
+	logrus.SetFormatter(&logrus.JSONFormatter{})
+	logrus.SetLevel(logrus.DebugLevel)
+	logrus.SetOutput(os.Stdout)
+	logrus.SetReportCaller(true)
+	logrus.SetOutput(writer)
+}