diff --git a/serial.go b/serial.go index d50e1f7..08f227a 100644 --- a/serial.go +++ b/serial.go @@ -6,6 +6,8 @@ package serial +import "time" + //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go // Port is the interface for a serial Port @@ -40,10 +42,17 @@ type Port interface { // modem status bits for the serial port (CTS, DSR, etc...) GetModemStatusBits() (*ModemStatusBits, error) + // SetReadTimeout sets the timeout for the Read operation or use serial.NoTimeout + // to disable read timeout. + SetReadTimeout(t time.Duration) error + // Close the serial port Close() error } +// NoTimeout should be used as a parameter to SetReadTimeout to disable timeout. +var NoTimeout time.Duration = -1 + // ModemStatusBits contains all the modem status bits for a serial port (CTS, DSR, etc...). // It can be retrieved with the Port.GetModemStatusBits() method. type ModemStatusBits struct { @@ -125,6 +134,8 @@ const ( InvalidParity // InvalidStopBits the selected number of stop bits is not valid or not supported InvalidStopBits + // InvalidTimeoutValue the timeout value is not valid or not supported + InvalidTimeoutValue // ErrorEnumeratingPorts an error occurred while listing serial port ErrorEnumeratingPorts // PortClosed the port has been closed while the operation is in progress @@ -152,6 +163,8 @@ func (e PortError) EncodedErrorString() string { return "Port parity invalid or not supported" case InvalidStopBits: return "Port stop bits invalid or not supported" + case InvalidTimeoutValue: + return "Timeout value invalid or not supported" case ErrorEnumeratingPorts: return "Could not enumerate serial ports" case PortClosed: diff --git a/serial_unix.go b/serial_unix.go index c921389..0143aa2 100644 --- a/serial_unix.go +++ b/serial_unix.go @@ -14,6 +14,7 @@ import ( "strings" "sync" "sync/atomic" + "time" "go.bug.st/serial/unixutils" "golang.org/x/sys/unix" @@ -22,6 +23,7 @@ import ( type unixPort struct { handle int + readTimeout time.Duration closeLock sync.RWMutex closeSignal *unixutils.Pipe opened uint32 @@ -61,9 +63,18 @@ func (port *unixPort) Read(p []byte) (int, error) { return 0, &PortError{code: PortClosed} } + var deadline time.Time + if port.readTimeout != NoTimeout { + deadline = time.Now().Add(port.readTimeout) + } + fds := unixutils.NewFDSet(port.handle, port.closeSignal.ReadFD()) for { - res, err := unixutils.Select(fds, nil, fds, -1) + timeout := time.Duration(-1) + if port.readTimeout != NoTimeout { + timeout = deadline.Sub(time.Now()) + } + res, err := unixutils.Select(fds, nil, fds, timeout) if err == unix.EINTR { continue } @@ -73,6 +84,10 @@ func (port *unixPort) Read(p []byte) (int, error) { if res.IsReadable(port.closeSignal.ReadFD()) { return 0, &PortError{code: PortClosed} } + if !res.IsReadable(port.handle) { + // Timeout happened + return 0, nil + } n, err := unix.Read(port.handle, p) if err == unix.EINTR { continue @@ -152,6 +167,14 @@ func (port *unixPort) SetRTS(rts bool) error { return port.setModemBitsStatus(status) } +func (port *unixPort) SetReadTimeout(timeout time.Duration) error { + if timeout < 0 && timeout != NoTimeout { + return &PortError{code: InvalidTimeoutValue} + } + port.readTimeout = timeout + return nil +} + func (port *unixPort) GetModemStatusBits() (*ModemStatusBits, error) { status, err := port.getModemBitsStatus() if err != nil { @@ -177,8 +200,9 @@ func nativeOpen(portName string, mode *Mode) (*unixPort, error) { return nil, err } port := &unixPort{ - handle: h, - opened: 1, + handle: h, + opened: 1, + readTimeout: NoTimeout, } // Setup serial port