sungrow/vendor/github.com/goburrow/serial/serial_posix.go
2021-01-10 15:20:11 +11:00

265 lines
6 KiB
Go

// +build darwin linux freebsd openbsd netbsd
package serial
import (
"errors"
"fmt"
"log"
"os"
"syscall"
"time"
"unsafe"
)
// port implements Port interface.
type port struct {
fd int
oldTermios *syscall.Termios
timeout time.Duration
}
const (
rs485Enabled = 1 << 0
rs485RTSOnSend = 1 << 1
rs485RTSAfterSend = 1 << 2
rs485RXDuringTX = 1 << 4
rs485Tiocs = 0x542f
)
// rs485_ioctl_opts is used to configure RS485 options in the driver
type rs485_ioctl_opts struct {
flags uint32
delay_rts_before_send uint32
delay_rts_after_send uint32
padding [5]uint32
}
// New allocates and returns a new serial port controller.
func New() Port {
return &port{fd: -1}
}
// Open connects to the given serial port.
func (p *port) Open(c *Config) (err error) {
termios, err := newTermios(c)
if err != nil {
return
}
// See man termios(3).
// O_NOCTTY: no controlling terminal.
// O_NDELAY: no data carrier detect.
p.fd, err = syscall.Open(c.Address, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NDELAY|syscall.O_CLOEXEC, 0666)
if err != nil {
return
}
// Backup current termios to restore on closing.
p.backupTermios()
if err = p.setTermios(termios); err != nil {
// No need to restore termios
syscall.Close(p.fd)
p.fd = -1
p.oldTermios = nil
return err
}
if err = enableRS485(p.fd, &c.RS485); err != nil {
p.Close()
return err
}
p.timeout = c.Timeout
return
}
func (p *port) Close() (err error) {
if p.fd == -1 {
return
}
p.restoreTermios()
err = syscall.Close(p.fd)
p.fd = -1
p.oldTermios = nil
return
}
// Read reads from serial port. Port must be opened before calling this method.
// It is blocked until all data received or timeout after p.timeout.
func (p *port) Read(b []byte) (n int, err error) {
var rfds syscall.FdSet
fd := p.fd
fdset(fd, &rfds)
var tv *syscall.Timeval
if p.timeout > 0 {
timeout := syscall.NsecToTimeval(p.timeout.Nanoseconds())
tv = &timeout
}
for {
// If syscall.Select() returns EINTR (Interrupted system call), retry it
if err = syscallSelect(fd+1, &rfds, nil, nil, tv); err == nil {
break
}
if err != syscall.EINTR {
err = fmt.Errorf("serial: could not select: %v", err)
return
}
}
if !fdisset(fd, &rfds) {
// Timeout
err = ErrTimeout
return
}
n, err = syscall.Read(fd, b)
return
}
// Write writes data to the serial port.
func (p *port) Write(b []byte) (n int, err error) {
n, err = syscall.Write(p.fd, b)
return
}
func (p *port) setTermios(termios *syscall.Termios) (err error) {
if err = tcsetattr(p.fd, termios); err != nil {
err = fmt.Errorf("serial: could not set setting: %v", err)
}
return
}
// backupTermios saves current termios setting.
// Make sure that device file has been opened before calling this function.
func (p *port) backupTermios() {
oldTermios := &syscall.Termios{}
if err := tcgetattr(p.fd, oldTermios); err != nil {
// Warning only.
log.Printf("serial: could not get setting: %v\n", err)
return
}
// Will be reloaded when closing.
p.oldTermios = oldTermios
}
// restoreTermios restores backed up termios setting.
// Make sure that device file has been opened before calling this function.
func (p *port) restoreTermios() {
if p.oldTermios == nil {
return
}
if err := tcsetattr(p.fd, p.oldTermios); err != nil {
// Warning only.
log.Printf("serial: could not restore setting: %v\n", err)
return
}
p.oldTermios = nil
}
// Helpers for termios
func newTermios(c *Config) (termios *syscall.Termios, err error) {
termios = &syscall.Termios{}
flag := termios.Cflag
// Baud rate
if c.BaudRate == 0 {
// 19200 is the required default.
flag = syscall.B19200
} else {
var ok bool
flag, ok = baudRates[c.BaudRate]
if !ok {
err = fmt.Errorf("serial: unsupported baud rate %v", c.BaudRate)
return
}
}
termios.Cflag |= flag
// Input baud.
cfSetIspeed(termios, flag)
// Output baud.
cfSetOspeed(termios, flag)
// Character size.
if c.DataBits == 0 {
flag = syscall.CS8
} else {
var ok bool
flag, ok = charSizes[c.DataBits]
if !ok {
err = fmt.Errorf("serial: unsupported character size %v", c.DataBits)
return
}
}
termios.Cflag |= flag
// Stop bits
switch c.StopBits {
case 0, 1:
// Default is one stop bit.
// noop
case 2:
// CSTOPB: Set two stop bits.
termios.Cflag |= syscall.CSTOPB
default:
err = fmt.Errorf("serial: unsupported stop bits %v", c.StopBits)
return
}
switch c.Parity {
case "N":
// noop
case "O":
// PARODD: Parity is odd.
termios.Cflag |= syscall.PARODD
fallthrough
case "", "E":
// As mentioned in the modbus spec, the default parity mode must be Even parity
// PARENB: Enable parity generation on output.
termios.Cflag |= syscall.PARENB
// INPCK: Enable input parity checking.
termios.Iflag |= syscall.INPCK
default:
err = fmt.Errorf("serial: unsupported parity %v", c.Parity)
return
}
// Control modes.
// CREAD: Enable receiver.
// CLOCAL: Ignore control lines.
termios.Cflag |= syscall.CREAD | syscall.CLOCAL
// Special characters.
// VMIN: Minimum number of characters for noncanonical read.
// VTIME: Time in deciseconds for noncanonical read.
// Both are unused as NDELAY is we utilized when opening device.
return
}
// enableRS485 enables RS485 functionality of driver via an ioctl if the config says so
func enableRS485(fd int, config *RS485Config) error {
if !config.Enabled {
return nil
}
rs485 := rs485_ioctl_opts{
rs485Enabled,
uint32(config.DelayRtsBeforeSend / time.Millisecond),
uint32(config.DelayRtsAfterSend / time.Millisecond),
[5]uint32{0, 0, 0, 0, 0},
}
if config.RtsHighDuringSend {
rs485.flags |= rs485RTSOnSend
}
if config.RtsHighAfterSend {
rs485.flags |= rs485RTSAfterSend
}
if config.RxDuringTx {
rs485.flags |= rs485RXDuringTX
}
r, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fd),
uintptr(rs485Tiocs),
uintptr(unsafe.Pointer(&rs485)))
if errno != 0 {
return os.NewSyscallError("SYS_IOCTL (RS485)", errno)
}
if r != 0 {
return errors.New("serial: unknown error from SYS_IOCTL (RS485)")
}
return nil
}