Merge pull request #109 from cmaglie/timeouts
Implementation of read timeouts
This commit is contained in:
13
serial.go
13
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:
|
||||
|
||||
@@ -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,10 +84,20 @@ 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
|
||||
}
|
||||
// Linux: when the port is disconnected during a read operation
|
||||
// the port is left in a "readable with zero-length-data" state.
|
||||
// https://stackoverflow.com/a/34945814/1655275
|
||||
if n == 0 && err == nil {
|
||||
return 0, &PortError{code: PortClosed}
|
||||
}
|
||||
if n < 0 { // Do not return -1 unix errors
|
||||
n = 0
|
||||
}
|
||||
@@ -146,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 {
|
||||
@@ -171,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
|
||||
|
||||
@@ -18,13 +18,15 @@ package serial
|
||||
*/
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type windowsPort struct {
|
||||
mu sync.Mutex
|
||||
handle syscall.Handle
|
||||
mu sync.Mutex
|
||||
handle syscall.Handle
|
||||
readTimeoutCycles int64
|
||||
}
|
||||
|
||||
func nativeGetPortsList() ([]string, error) {
|
||||
@@ -80,16 +82,19 @@ func (port *windowsPort) Read(p []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
defer syscall.CloseHandle(ev.HEvent)
|
||||
|
||||
cycles := int64(0)
|
||||
for {
|
||||
err := syscall.ReadFile(port.handle, p, &readed, ev)
|
||||
if err == syscall.ERROR_IO_PENDING {
|
||||
err = getOverlappedResult(port.handle, ev, &readed, true)
|
||||
}
|
||||
switch err {
|
||||
case nil:
|
||||
// operation completed successfully
|
||||
case syscall.ERROR_IO_PENDING:
|
||||
// wait for overlapped I/O to complete
|
||||
if err := getOverlappedResult(port.handle, ev, &readed, true); err != nil {
|
||||
return int(readed), err
|
||||
}
|
||||
case syscall.ERROR_OPERATION_ABORTED:
|
||||
// port may have been closed
|
||||
return int(readed), &PortError{code: PortClosed, causedBy: err}
|
||||
default:
|
||||
// error happened
|
||||
return int(readed), err
|
||||
@@ -102,6 +107,14 @@ func (port *windowsPort) Read(p []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if port.readTimeoutCycles != -1 {
|
||||
cycles++
|
||||
if cycles == port.readTimeoutCycles {
|
||||
// Timeout
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// At the moment it seems that the only reliable way to check if
|
||||
// a serial port is alive in Windows is to check if the SetCommState
|
||||
// function fails.
|
||||
@@ -369,6 +382,31 @@ func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
|
||||
var cycles, cycleDuration int64
|
||||
if timeout == NoTimeout {
|
||||
cycles = -1
|
||||
cycleDuration = 1000
|
||||
} else {
|
||||
cycles = timeout.Milliseconds()/1000 + 1
|
||||
cycleDuration = timeout.Milliseconds() / cycles
|
||||
}
|
||||
|
||||
err := setCommTimeouts(port.handle, &commTimeouts{
|
||||
ReadIntervalTimeout: 0xFFFFFFFF,
|
||||
ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
|
||||
ReadTotalTimeoutConstant: uint32(cycleDuration),
|
||||
WriteTotalTimeoutConstant: 0,
|
||||
WriteTotalTimeoutMultiplier: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return &PortError{code: InvalidTimeoutValue, causedBy: err}
|
||||
}
|
||||
port.readTimeoutCycles = cycles
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createOverlappedEvent() (*syscall.Overlapped, error) {
|
||||
h, err := createEvent(nil, true, false, nil)
|
||||
return &syscall.Overlapped{HEvent: h}, err
|
||||
@@ -434,18 +472,9 @@ func nativeOpen(portName string, mode *Mode) (*windowsPort, error) {
|
||||
return nil, &PortError{code: InvalidSerialPort}
|
||||
}
|
||||
|
||||
// Set timeouts to 1 second
|
||||
timeouts := &commTimeouts{
|
||||
ReadIntervalTimeout: 0xFFFFFFFF,
|
||||
ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
|
||||
ReadTotalTimeoutConstant: 1000, // 1 sec
|
||||
WriteTotalTimeoutConstant: 0,
|
||||
WriteTotalTimeoutMultiplier: 0,
|
||||
}
|
||||
if setCommTimeouts(port.handle, timeouts) != nil {
|
||||
if port.SetReadTimeout(NoTimeout) != nil {
|
||||
port.Close()
|
||||
return nil, &PortError{code: InvalidSerialPort}
|
||||
}
|
||||
|
||||
return port, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user