diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..956f20b --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/jacobsa/go-serial + +go 1.17 + +require golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d570d24 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7 h1:c20P3CcPbopVp2f7099WLOqSNKURf30Z0uq66HpijZY= +golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/serial/open_darwin.go b/serial/open_darwin.go index 6e50629..1137200 100644 --- a/serial/open_darwin.go +++ b/serial/open_darwin.go @@ -27,11 +27,13 @@ package serial import ( "errors" - "io" + "fmt" + "os" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" ) -import "os" -import "syscall" -import "unsafe" // termios types type cc_t byte @@ -196,7 +198,9 @@ func convertOptions(options OpenOptions) (*termios, error) { return &result, nil } -func openInternal(options OpenOptions) (io.ReadWriteCloser, error) { +type port struct{ *os.File } + +func openInternal(options OpenOptions) (*port, error) { // Open the serial port in non-blocking mode, since otherwise the OS will // wait for the CARRIER line to be asserted. file, err := @@ -254,5 +258,26 @@ func openInternal(options OpenOptions) (io.ReadWriteCloser, error) { } // We're done. - return file, nil + return &port{file}, nil +} + +func (p *port) Flush(in, out bool) error { + var what int + if in && out { + what = 0x03 + } else if in { + what = 0x01 + } else if out { + what = 0x02 + } else { + return nil + } + if err := unix.IoctlSetPointerInt(int(p.Fd()), unix.TIOCFLUSH, what); err != nil { + return fmt.Errorf("ioctl(TIOCFLUSH) failed: %w", err) + } + return nil +} + +func (p *port) PortName() string { + return p.Name() } diff --git a/serial/open_freebsd.go b/serial/open_freebsd.go index f1e8990..2901347 100644 --- a/serial/open_freebsd.go +++ b/serial/open_freebsd.go @@ -16,6 +16,6 @@ package serial import "io" -func openInternal(options OpenOptions) (io.ReadWriteCloser, error) { +func openInternal(options OpenOptions) (Port, error) { return nil, "Not implemented on this OS." } diff --git a/serial/open_linux.go b/serial/open_linux.go index ccce12c..6283fe1 100644 --- a/serial/open_linux.go +++ b/serial/open_linux.go @@ -2,7 +2,7 @@ package serial import ( "errors" - "io" + "fmt" "os" "syscall" "unsafe" @@ -138,7 +138,9 @@ func makeTermios2(options OpenOptions) (*termios2, error) { return t2, nil } -func openInternal(options OpenOptions) (io.ReadWriteCloser, error) { +type port struct{ *os.File } + +func openInternal(options OpenOptions) (*port, error) { file, openErr := os.OpenFile( @@ -205,5 +207,27 @@ func openInternal(options OpenOptions) (io.ReadWriteCloser, error) { } } - return file, nil + return &port{file}, nil +} + +func (p *port) Flush(in, out bool) error { + var queueSel int + if in && out { + queueSel = unix.TCIOFLUSH + } else if in { + queueSel = unix.TCIFLUSH + } else if out { + queueSel = unix.TCOFLUSH + } else { + return nil + } + // TCFLSH = 0x540B aka tcflush() + if err := unix.IoctlSetInt(int(p.Fd()), 0x540B, queueSel); err != nil { + return fmt.Errorf("tcflush failed: %w", err) + } + return nil +} + +func (p *port) PortName() string { + return p.Name() } diff --git a/serial/open_windows.go b/serial/open_windows.go index fb398f1..7e4f00d 100644 --- a/serial/open_windows.go +++ b/serial/open_windows.go @@ -16,7 +16,6 @@ package serial import ( "fmt" - "io" "os" "sync" "syscall" @@ -49,7 +48,7 @@ type structTimeouts struct { WriteTotalTimeoutConstant uint32 } -func openInternal(options OpenOptions) (io.ReadWriteCloser, error) { +func openInternal(options OpenOptions) (*serialPort, error) { if len(options.PortName) > 0 && options.PortName[0] != '\\' { options.PortName = "\\\\.\\" + options.PortName } @@ -139,6 +138,30 @@ func (p *serialPort) Read(buf []byte) (int, error) { return getOverlappedResult(p.fd, p.ro) } +func (p *serialPort) Fd() uintptr { + return p.f.Fd() +} + +func (p *serialPort) Flush(in, out bool) error { + var flags uintptr + if in { + flags |= 0x0008 + } + if out { + flags |= 0x0004 + } + if flags == 0 { + return nil + } + + // BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) + r, _, err := syscall.Syscall(nPurgeComm, 2, p.Fd(), flags, 0) + if r == 0 { + return fmt.Errorf("PurgeComm failed: %w", err) + } + return nil +} + var ( nSetCommState, nSetCommTimeouts, @@ -146,7 +169,8 @@ var ( nSetupComm, nGetOverlappedResult, nCreateEvent, - nResetEvent uintptr + nResetEvent, + nPurgeComm uintptr ) func init() { @@ -163,6 +187,7 @@ func init() { nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult") nCreateEvent = getProcAddr(k32, "CreateEventW") nResetEvent = getProcAddr(k32, "ResetEvent") + nPurgeComm = getProcAddr(k32, "PurgeComm") } func getProcAddr(lib syscall.Handle, name string) uintptr { @@ -320,3 +345,7 @@ func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, return n, nil } + +func (p *serialPort) PortName() string { + return p.f.Name() +} diff --git a/serial/serial.go b/serial/serial.go index 9d9f26b..652da28 100644 --- a/serial/serial.go +++ b/serial/serial.go @@ -159,8 +159,16 @@ type OpenOptions struct { Rs485DelayRtsAfterSend int } -// Open creates an io.ReadWriteCloser based on the supplied options struct. -func Open(options OpenOptions) (io.ReadWriteCloser, error) { +// Port defines the serial port. +type Port interface { + io.ReadWriteCloser + Fd() uintptr + Flush(in, out bool) error + PortName() string +} + +// Open creates a Port based on the supplied options struct. +func Open(options OpenOptions) (Port, error) { // Redirect to the OS-specific function. return openInternal(options) }