You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
4.4 KiB

package maincontrollerClient
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/towgo/towgo/errors/terror"
"io"
"reflect"
"sync"
"time"
"log"
"go.bug.st/serial"
)
var serialPortMode bool
var serialPort *SerialPort
// SerialPort 串口对象
type SerialPort struct {
config *serial.Mode
address string
port serial.Port
isOpen bool
ctx context.Context
cancel context.CancelFunc
dataChan chan byte
onData []func([]byte)
ticket *time.Ticker
isBIO bool
bioChan chan []byte
bioWriteLock sync.Mutex
bioLocker sync.Mutex
}
// NewSerialPort 创建串口实例
func NewSerialPort(portName string, baudRate int) *SerialPort {
return &SerialPort{
address: portName,
config: &serial.Mode{
BaudRate: baudRate,
},
}
}
func UseSerialPort(portName string, baudRate int) error {
serialPortMode = true
serialPort = &SerialPort{
address: portName,
config: &serial.Mode{
BaudRate: baudRate,
},
}
err := serialPort.Open()
if err != nil {
return gerror.Wrap(err, "串口连接打开失败")
}
serialPort.OnDataReceived(func(data []byte) {
t := time.NewTimer(time.Millisecond * 100)
if serialPort.IsBIO() {
<-t.C
for {
select {
case <-serialPort.bioChan:
continue
default:
serialPort.bioChan <- data
}
break
}
} else {
log.Printf("无效上传的数据(仅支持BIO模式),丢弃:% X", (data))
}
})
return nil
}
// Open 打开串口连接
func (sp *SerialPort) Open() error {
if sp.isOpen {
return terror.New("port is already open")
}
port, err := serial.Open(sp.address, sp.config)
if err != nil {
return gerror.Wrap(err, "串口打开失败")
}
sp.dataChan = make(chan byte, 1)
sp.bioChan = make(chan []byte, 1)
sp.port = port
sp.ctx, sp.cancel = context.WithCancel(context.Background())
sp.isOpen = true
go sp.readLoop()
go sp.readChanLoop()
return nil
}
func (sp *SerialPort) BIO(data []byte) ([]byte, error) {
sp.bioWriteLock.Lock()
defer sp.bioWriteLock.Unlock()
sp.SetisBIO(true)
sp.port.Write(data)
timer := time.NewTimer(time.Second * 3)
select {
case <-timer.C:
sp.SetisBIO(false)
return nil, terror.New("数据读取超时")
case message := <-sp.bioChan:
sp.SetisBIO(false)
return message, nil
}
}
// Close 关闭串口连接
func (sp *SerialPort) Close() error {
if !sp.isOpen {
return terror.New("port is already closed")
}
// 执行关闭操作
err := sp.port.Close()
sp.isOpen = false
return err
}
// OnDataReceived 注册数据接收事件回调
func (sp *SerialPort) OnDataReceived(callback func([]byte)) {
sp.onData = append(sp.onData, callback)
}
func (sp *SerialPort) readChanLoop() {
sp.ticket = time.NewTicker(time.Millisecond * 100)
var buf []byte
for {
if !sp.isOpen {
return
}
select {
case <-sp.ctx.Done():
return
case <-sp.ticket.C:
if len(buf) > 0 {
callback := sp.onData
for _, f := range callback {
go f(buf)
}
buf = []byte{}
}
case b := <-sp.dataChan:
buf = append(buf, b)
}
}
}
// 读取循环
func (sp *SerialPort) readLoop() {
buf := make([]byte, 1)
for {
select {
case <-sp.ctx.Done():
return
default:
_, err := sp.port.Read(buf)
sp.ticket.Reset(time.Millisecond * 100)
if err != nil {
if err != io.EOF {
if sp.isOpen {
sp.port.Close()
sp.isOpen = false
}
}
return
}
sp.dataChan <- buf[0]
}
}
}
func ToBytes(data interface{}) ([]byte, error) {
if data == nil {
return nil, nil
}
// 处理 []byte 和字符串
switch v := data.(type) {
case []byte:
return v, nil
case string:
return []byte(v), nil
}
// 处理基本类型
val := reflect.ValueOf(data)
for {
if val.Kind() == reflect.Interface {
val = val.Elem()
continue
}
break
}
switch val.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.BigEndian, data); err != nil {
return nil, err
}
return buf.Bytes(), nil
default:
// 复杂类型使用JSON序列化
return json.Marshal(data)
}
}
func (sp *SerialPort) SetisBIO(isBio bool) {
sp.bioLocker.Lock()
sp.isBIO = isBio
sp.bioLocker.Unlock()
}
func (sp *SerialPort) IsBIO() bool {
sp.bioLocker.Lock()
defer sp.bioLocker.Unlock()
return sp.isBIO
}