package main import ( "context" "errors" "fmt" zgpf_core "git.swzry.com/zry/zry-go-program-framework/core" "github.com/tarm/serial" "sync" ) var _ zgpf_core.ISubService = (*SerialSubSvc)(nil) type SerialSubSvc struct { serialCfg *serial.Config serialPort *serial.Port subSvcContext context.Context subSvcCancel context.CancelFunc TxChan chan []byte RxChan chan []byte } func NewSerialSubSvc() *SerialSubSvc { s := &SerialSubSvc{ TxChan: make(chan []byte), RxChan: make(chan []byte), } return s } func (s *SerialSubSvc) Prepare(ctx *zgpf_core.SubServiceContext) error { sd, ss, sp, err := GetDnSnP(Config.Serial.DataBits, Config.Serial.StopBits, Config.Serial.Parity) if err != nil { ctx.Error("invalid serial port config: ", err) return errors.New("invalid serial port config") } s.serialCfg = &serial.Config{ Name: Config.Serial.ComPort, Baud: Config.Serial.Baudrate, Size: sd, Parity: sp, StopBits: ss, } return nil } func (s *SerialSubSvc) Run(ctx *zgpf_core.SubServiceContext) error { s.subSvcContext, s.subSvcCancel = context.WithCancel(ctx.GetParentContext()) sport, err := serial.OpenPort(s.serialCfg) if err != nil { ctx.ErrorF("failed open serial port '%s': %v", s.serialCfg.Name, err) return errors.New("failed open serial port") } s.serialPort = sport ctx.InfoF("serial port '%s' opened", s.serialCfg.Name) defer s.serialPort.Close() wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() readbuf := make([]byte, Config.Performance.RxBufferSize) for { n, xerr := s.serialPort.Read(readbuf) if xerr != nil { ctx.Error("error in read serial port: ", xerr) return } s.RxChan <- readbuf[:n] } }() go func() { defer wg.Done() for { select { case <-s.subSvcContext.Done(): return case txdata := <-s.TxChan: _, xerr := s.serialPort.Write(txdata) if xerr != nil { ctx.Error("error in write serial port: ", xerr) return } } } }() wg.Wait() ctx.Info("serial sub service end.") return nil } func (s *SerialSubSvc) Stop(ctx *zgpf_core.SubServiceContext) { if s.subSvcCancel != nil { s.subSvcCancel() } if s.serialPort != nil { err := s.serialPort.Close() if err != nil { ctx.Warn("error in closing serial port: ", err) } } } func GetDnSnP(d int, s float32, p string) (byte, serial.StopBits, serial.Parity, error) { if d < 0 { return 0, 0, 0, fmt.Errorf("invalid databits: value %d is less than 0", d) } if d > 255 { return 0, 0, 0, fmt.Errorf("invalid databits: value %d is more than 255", d) } sd := byte(d) var ss serial.StopBits switch s { case 1: ss = serial.Stop1 break case 1.5: ss = serial.Stop1Half case 2: ss = serial.Stop2 default: return 0, 0, 0, fmt.Errorf("invalid stopbits: %f (should be 1/1.5/2)", s) } var sp serial.Parity switch p { case "N": sp = serial.ParityNone break case "O": sp = serial.ParityOdd break case "E": sp = serial.ParityEven break case "M": sp = serial.ParityMark break case "S": sp = serial.ParitySpace break default: return 0, 0, 0, fmt.Errorf("invalid parity: '%s' (should be N/O/E/M/S)", p) } return sd, ss, sp, nil }