diff --git a/README.md b/README.md index b6e9915..81db23a 100644 --- a/README.md +++ b/README.md @@ -60,16 +60,18 @@ func main() { NonBlocking Mode ---------------- -By default goserial reads in blocking mode. Which means Read() will +By default goserial reads in blocking mode. Which means `Read()` will block until at least one byte is returned. If that's not what you want, -specify a ReadTimeout in milliseconds and the Read() will timeout -returning 0 bytes if no bytes are read. +specify a positive ReadTimeout and the Read() will timeout returning 0 +bytes if no bytes are read. +Please note that this is the total timeout the read operation will wait +and not the interval timeout between two bytes. ```go - c := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: 1000} + c := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Second * 5} // In this mode, you will want to suppress error for read - // as 0 bytes returns EOF error on Linux / POSIX + // as 0 bytes return EOF error on Linux / POSIX n, _ = s.Read(buf) ``` diff --git a/serial.go b/serial.go index f0c8d6b..14bab42 100644 --- a/serial.go +++ b/serial.go @@ -55,7 +55,10 @@ Example usage: */ package serial -import "io" +import ( + "io" + "time" +) // Config contains the information needed to open a serial port. // @@ -65,16 +68,17 @@ import "io" // // For example: // -// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: 1000} +// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500} // or // c1 := new(serial.Config) // c1.Name = "/dev/tty.usbserial" // c1.Baud = 115200 -// c1.ReadTimeout = 1000 +// c1.ReadTimeout = time.Millisecond * 500 // type Config struct { - Name string - Baud int + Name string + Baud int + ReadTimeout time.Duration // Total timeout // Size int // 0 get translated to 8 // Parity SomeNewTypeToGetCorrectDefaultOf_None @@ -85,7 +89,6 @@ type Config struct { // XONFlowControl bool // CRLFTranslate bool - ReadTimeout uint32 } // OpenPort opens a serial port with the specified configuration @@ -93,6 +96,31 @@ func OpenPort(c *Config) (io.ReadWriteCloser, error) { return openPort(c.Name, c.Baud, c.ReadTimeout) } +// Converts the timeout values for Linux / POSIX systems +func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) { + const MAXUINT8 = 1<<8 - 1 // 255 + // set blocking / non-blocking read + var minBytesToRead uint8 = 1 + var readTimeoutInDeci uint8 = 0 + if readTimeout > 0 { + // EOF on zero read + minBytesToRead = 0 + timeoutMs := uint32(readTimeout.Nanoseconds() / 1e6) + // capping the timeout + if timeoutMs < 100 { + // min possible timeout 1 Deciseconds (0.1s) + readTimeoutInDeci = 1 + } else if timeoutMs > (MAXUINT8 * 100) { + // max possible timeout is 255 Deciseconds (25.5s) + readTimeoutInDeci = MAXUINT8 + } else { + // convert milliseconds to deciseconds as expected by VTIME + readTimeoutInDeci = uint8(timeoutMs / 100) + } + } + return minBytesToRead, readTimeoutInDeci +} + // func Flush() // func SendBreak() diff --git a/serial_linux.go b/serial_linux.go index 5978157..4217a55 100644 --- a/serial_linux.go +++ b/serial_linux.go @@ -6,10 +6,11 @@ import ( "io" "os" "syscall" + "time" "unsafe" ) -func openPort(name string, baud int, readTimeout uint32) (rwc io.ReadWriteCloser, err error) { +func openPort(name string, baud int, readTimeout time.Duration) (rwc io.ReadWriteCloser, err error) { var bauds = map[int]uint32{ 50: syscall.B50, @@ -62,26 +63,11 @@ func openPort(name string, baud int, readTimeout uint32) (rwc io.ReadWriteCloser }() fd := f.Fd() - // set blocking / non-blocking read - var minBytesToRead uint8 = 1 - if readTimeout > 0 { - // EOF on zero read - minBytesToRead = 0 - // capping the timeout - if readTimeout < 100 { - // min possible timeout - readTimeout = 100 - } else if readTimeout > 25500 { - // max possible timeout - readTimeout = 25500 - } - // convert milliseconds to deciseconds as expected by VTIME - readTimeout = readTimeout / 100 - } + vmin, vtime := posixTimeoutValues(readTimeout) t := syscall.Termios{ Iflag: syscall.IGNPAR, Cflag: syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate, - Cc: [32]uint8{syscall.VMIN: minBytesToRead, syscall.VTIME: uint8(readTimeout)}, + Cc: [32]uint8{syscall.VMIN: vmin, syscall.VTIME: vtime}, Ispeed: rate, Ospeed: rate, } diff --git a/serial_posix.go b/serial_posix.go index b69e29e..2ccdea0 100644 --- a/serial_posix.go +++ b/serial_posix.go @@ -14,10 +14,11 @@ import ( "io" "os" "syscall" + "time" //"unsafe" ) -func openPort(name string, baud int, readTimeout uint32) (rwc io.ReadWriteCloser, err error) { +func openPort(name string, baud int, readTimeout time.Duration) (rwc io.ReadWriteCloser, err error) { f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666) if err != nil { return @@ -83,23 +84,9 @@ func openPort(name string, baud int, readTimeout uint32) (rwc io.ReadWriteCloser * http://man7.org/linux/man-pages/man3/termios.3.html * - Supports blocking read and read with timeout operations */ - var minBytesToRead uint8 = 1 - if readTimeout > 0 { - // EOF on zero read - minBytesToRead = 0 - // capping the timeout - if readTimeout < 100 { - // min possible timeout - readTimeout = 100 - } else if readTimeout > 25500 { - // max possible timeout - readTimeout = 25500 - } - // convert milliseconds to deciseconds as expected by VTIME - readTimeout = readTimeout / 100 - } - st.c_cc[C.VMIN] = C.cc_t(minBytesToRead) - st.c_cc[C.VTIME] = C.cc_t(readTimeout) + vmin, vtime := posixTimeoutValues(readTimeout) + st.c_cc[C.VMIN] = C.cc_t(vmin) + st.c_cc[C.VTIME] = C.cc_t(vtime) _, err = C.tcsetattr(fd, C.TCSANOW, &st) if err != nil { diff --git a/serial_windows.go b/serial_windows.go index 0892d76..f7b5004 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -8,6 +8,7 @@ import ( "os" "sync" "syscall" + "time" "unsafe" ) @@ -37,7 +38,7 @@ type structTimeouts struct { WriteTotalTimeoutConstant uint32 } -func openPort(name string, baud int, readTimeout uint32) (rwc io.ReadWriteCloser, err error) { +func openPort(name string, baud int, readTimeout time.Duration) (rwc io.ReadWriteCloser, err error) { if len(name) > 0 && name[0] != '\\' { name = "\\\\.\\" + name } @@ -178,14 +179,16 @@ func setCommState(h syscall.Handle, baud int) error { return nil } -func setCommTimeouts(h syscall.Handle, readTimeout uint32) error { +func setCommTimeouts(h syscall.Handle, readTimeout time.Duration) error { var timeouts structTimeouts const MAXDWORD = 1<<32 - 1 - if readTimeout > 0 { + timeoutMs := uint32(readTimeout.Nanoseconds() / 1e6) + + if timeoutMs > 0 { // non-blocking read - timeouts.ReadIntervalTimeout = 1000 + timeouts.ReadIntervalTimeout = 0 timeouts.ReadTotalTimeoutMultiplier = 0 - timeouts.ReadTotalTimeoutConstant = readTimeout + timeouts.ReadTotalTimeoutConstant = timeoutMs } else { // blocking read timeouts.ReadIntervalTimeout = MAXDWORD