├── doc.go ├── README.md ├── basic_test.go ├── LICENSE ├── serial_linux.go ├── serial_posix.go ├── serial_windows.go └── serial.go /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Golang package for serial port 3 | 4 | A Go package that allow you to read and write from the serial port. 5 | 6 | This is a forked repo written by [@tarm](github.com/tarm). 7 | 8 | Example usage: 9 | 10 | package main 11 | 12 | import ( 13 | "time" 14 | "github.com/argandas/serial" 15 | ) 16 | 17 | func main() { 18 | sp := serial.New() 19 | err := sp.Open("COM1", 9600) 20 | if err != nil { 21 | panic(err) 22 | } 23 | defer sp.Close() 24 | sp.Println("AT") 25 | sp.WaitForRegexTimeout("OK.*", time.Second * 10) 26 | } 27 | */ 28 | 29 | package serial 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serial 2 | 3 | Golang package for serial port 4 | 5 | [![GoDoc](http://godoc.org/github.com/argandas/serial?status.svg)](http://godoc.org/github.com/argandas/serial) 6 | 7 | A Go package that allow you to read and write from the serial port. 8 | 9 | This is a forked repo written by [@tarm](github.com/tarm). 10 | 11 | ## Usage 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "time" 18 | "github.com/argandas/serial" 19 | ) 20 | 21 | func main() { 22 | sp := serial.New() 23 | err := sp.Open("COM1", 9600) 24 | if err != nil { 25 | panic(err) 26 | } 27 | defer sp.Close() 28 | sp.Println("AT") 29 | sp.WaitForRegexTimeout("OK.*", time.Second * 10) 30 | } 31 | ``` 32 | 33 | ## NonBlocking Mode 34 | 35 | By default the returned serial port 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 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. 36 | 37 | ```go 38 | sp := serial.New() 39 | err := sp.Open("COM1", 9600, time.Second * 5) 40 | ``` -------------------------------------------------------------------------------- /basic_test.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestConnection(t *testing.T) { 9 | c0 := &Config{Name: "/dev/ttyUSB0", Baud: 115200} 10 | c1 := &Config{Name: "/dev/ttyUSB1", Baud: 115200} 11 | 12 | s1, err := OpenPort(c0) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | s2, err := OpenPort(c1) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | ch := make(chan int, 1) 23 | go func() { 24 | buf := make([]byte, 128) 25 | var readCount int 26 | for { 27 | n, err := s2.Read(buf) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | readCount++ 32 | t.Logf("Read %v %v bytes: % 02x %s", readCount, n, buf[:n], buf[:n]) 33 | select { 34 | case <-ch: 35 | ch <- readCount 36 | close(ch) 37 | default: 38 | } 39 | } 40 | }() 41 | 42 | if _, err = s1.Write([]byte("hello")); err != nil { 43 | t.Fatal(err) 44 | } 45 | if _, err = s1.Write([]byte(" ")); err != nil { 46 | t.Fatal(err) 47 | } 48 | time.Sleep(time.Second) 49 | if _, err = s1.Write([]byte("world")); err != nil { 50 | t.Fatal(err) 51 | } 52 | time.Sleep(time.Second / 10) 53 | 54 | ch <- 0 55 | s1.Write([]byte(" ")) // We could be blocked in the read without this 56 | c := <-ch 57 | exp := 5 58 | if c >= exp { 59 | t.Fatalf("Expected less than %v read, got %v", exp, c) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /serial_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux,!cgo 2 | 3 | package serial 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "time" 9 | "unsafe" 10 | ) 11 | 12 | func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) { 13 | var bauds = map[int]uint32{ 14 | 50: syscall.B50, 15 | 75: syscall.B75, 16 | 110: syscall.B110, 17 | 134: syscall.B134, 18 | 150: syscall.B150, 19 | 200: syscall.B200, 20 | 300: syscall.B300, 21 | 600: syscall.B600, 22 | 1200: syscall.B1200, 23 | 1800: syscall.B1800, 24 | 2400: syscall.B2400, 25 | 4800: syscall.B4800, 26 | 9600: syscall.B9600, 27 | 19200: syscall.B19200, 28 | 38400: syscall.B38400, 29 | 57600: syscall.B57600, 30 | 115200: syscall.B115200, 31 | 230400: syscall.B230400, 32 | 460800: syscall.B460800, 33 | 500000: syscall.B500000, 34 | 576000: syscall.B576000, 35 | 921600: syscall.B921600, 36 | 1000000: syscall.B1000000, 37 | 1152000: syscall.B1152000, 38 | 1500000: syscall.B1500000, 39 | 2000000: syscall.B2000000, 40 | 2500000: syscall.B2500000, 41 | 3000000: syscall.B3000000, 42 | 3500000: syscall.B3500000, 43 | 4000000: syscall.B4000000, 44 | } 45 | 46 | rate := bauds[baud] 47 | 48 | if rate == 0 { 49 | return 50 | } 51 | 52 | f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | defer func() { 58 | if err != nil && f != nil { 59 | f.Close() 60 | } 61 | }() 62 | 63 | fd := f.Fd() 64 | vmin, vtime := posixTimeoutValues(readTimeout) 65 | t := syscall.Termios{ 66 | Iflag: syscall.IGNPAR, 67 | Cflag: syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate, 68 | Cc: [32]uint8{syscall.VMIN: vmin, syscall.VTIME: vtime}, 69 | Ispeed: rate, 70 | Ospeed: rate, 71 | } 72 | 73 | if _, _, errno := syscall.Syscall6( 74 | syscall.SYS_IOCTL, 75 | uintptr(fd), 76 | uintptr(syscall.TCSETS), 77 | uintptr(unsafe.Pointer(&t)), 78 | 0, 79 | 0, 80 | 0, 81 | ); errno != 0 { 82 | return nil, errno 83 | } 84 | 85 | if err = syscall.SetNonblock(int(fd), false); err != nil { 86 | return 87 | } 88 | 89 | return &Port{f: f}, nil 90 | } 91 | 92 | type Port struct { 93 | // We intentionly do not use an "embedded" struct so that we 94 | // don't export File 95 | f *os.File 96 | } 97 | 98 | func (p *Port) Read(b []byte) (n int, err error) { 99 | return p.f.Read(b) 100 | } 101 | 102 | func (p *Port) Write(b []byte) (n int, err error) { 103 | return p.f.Write(b) 104 | } 105 | 106 | // Discards data written to the port but not transmitted, 107 | // or data received but not read 108 | func (p *Port) Flush() error { 109 | const TCFLSH = 0x540B 110 | _, _, err := syscall.Syscall( 111 | syscall.SYS_IOCTL, 112 | uintptr(p.f.Fd()), 113 | uintptr(TCFLSH), 114 | uintptr(syscall.TCIOFLUSH), 115 | ) 116 | return err 117 | } 118 | 119 | func (p *Port) Close() (err error) { 120 | return p.f.Close() 121 | } 122 | -------------------------------------------------------------------------------- /serial_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows,cgo 2 | 3 | package serial 4 | 5 | // #include 6 | // #include 7 | import "C" 8 | 9 | // TODO: Maybe change to using syscall package + ioctl instead of cgo 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "os" 15 | "syscall" 16 | "time" 17 | //"unsafe" 18 | ) 19 | 20 | func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) { 21 | f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666) 22 | if err != nil { 23 | return 24 | } 25 | 26 | fd := C.int(f.Fd()) 27 | if C.isatty(fd) != 1 { 28 | f.Close() 29 | return nil, errors.New("File is not a tty") 30 | } 31 | 32 | var st C.struct_termios 33 | _, err = C.tcgetattr(fd, &st) 34 | if err != nil { 35 | f.Close() 36 | return nil, err 37 | } 38 | var speed C.speed_t 39 | switch baud { 40 | case 115200: 41 | speed = C.B115200 42 | case 57600: 43 | speed = C.B57600 44 | case 38400: 45 | speed = C.B38400 46 | case 19200: 47 | speed = C.B19200 48 | case 9600: 49 | speed = C.B9600 50 | case 4800: 51 | speed = C.B4800 52 | case 2400: 53 | speed = C.B2400 54 | default: 55 | f.Close() 56 | return nil, fmt.Errorf("Unknown baud rate %v", baud) 57 | } 58 | 59 | _, err = C.cfsetispeed(&st, speed) 60 | if err != nil { 61 | f.Close() 62 | return nil, err 63 | } 64 | _, err = C.cfsetospeed(&st, speed) 65 | if err != nil { 66 | f.Close() 67 | return nil, err 68 | } 69 | 70 | // Turn off break interrupts, CR->NL, Parity checks, strip, and IXON 71 | st.c_iflag &= ^C.tcflag_t(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXOFF | C.IXON | C.PARMRK) 72 | 73 | // Select local mode, turn off parity, set to 8 bits 74 | st.c_cflag &= ^C.tcflag_t(C.CSIZE | C.PARENB) 75 | st.c_cflag |= (C.CLOCAL | C.CREAD | C.CS8) 76 | 77 | // Select raw mode 78 | st.c_lflag &= ^C.tcflag_t(C.ICANON | C.ECHO | C.ECHOE | C.ISIG) 79 | st.c_oflag &= ^C.tcflag_t(C.OPOST) 80 | 81 | // set blocking / non-blocking read 82 | /* 83 | * http://man7.org/linux/man-pages/man3/termios.3.html 84 | * - Supports blocking read and read with timeout operations 85 | */ 86 | vmin, vtime := posixTimeoutValues(readTimeout) 87 | st.c_cc[C.VMIN] = C.cc_t(vmin) 88 | st.c_cc[C.VTIME] = C.cc_t(vtime) 89 | 90 | _, err = C.tcsetattr(fd, C.TCSANOW, &st) 91 | if err != nil { 92 | f.Close() 93 | return nil, err 94 | } 95 | 96 | //fmt.Println("Tweaking", name) 97 | r1, _, e := syscall.Syscall(syscall.SYS_FCNTL, 98 | uintptr(f.Fd()), 99 | uintptr(syscall.F_SETFL), 100 | uintptr(0)) 101 | if e != 0 || r1 != 0 { 102 | s := fmt.Sprint("Clearing NONBLOCK syscall error:", e, r1) 103 | f.Close() 104 | return nil, errors.New(s) 105 | } 106 | 107 | /* 108 | r1, _, e = syscall.Syscall(syscall.SYS_IOCTL, 109 | uintptr(f.Fd()), 110 | uintptr(0x80045402), // IOSSIOSPEED 111 | uintptr(unsafe.Pointer(&baud))); 112 | if e != 0 || r1 != 0 { 113 | s := fmt.Sprint("Baudrate syscall error:", e, r1) 114 | f.Close() 115 | return nil, os.NewError(s) 116 | } 117 | */ 118 | 119 | return &Port{f: f}, nil 120 | } 121 | 122 | type Port struct { 123 | // We intentionly do not use an "embedded" struct so that we 124 | // don't export File 125 | f *os.File 126 | } 127 | 128 | func (p *Port) Read(b []byte) (n int, err error) { 129 | return p.f.Read(b) 130 | } 131 | 132 | func (p *Port) Write(b []byte) (n int, err error) { 133 | return p.f.Write(b) 134 | } 135 | 136 | // Discards data written to the port but not transmitted, 137 | // or data received but not read 138 | func (p *Port) Flush() error { 139 | _, err := C.tcflush(C.int(p.f.Fd()), C.TCIOFLUSH) 140 | return err 141 | } 142 | 143 | func (p *Port) Close() (err error) { 144 | return p.f.Close() 145 | } 146 | -------------------------------------------------------------------------------- /serial_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package serial 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "sync" 9 | "syscall" 10 | "time" 11 | "unsafe" 12 | ) 13 | 14 | type Port struct { 15 | f *os.File 16 | fd syscall.Handle 17 | rl sync.Mutex 18 | wl sync.Mutex 19 | ro *syscall.Overlapped 20 | wo *syscall.Overlapped 21 | } 22 | 23 | type structDCB struct { 24 | DCBlength, BaudRate uint32 25 | flags [4]byte 26 | wReserved, XonLim, XoffLim uint16 27 | ByteSize, Parity, StopBits byte 28 | XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte 29 | wReserved1 uint16 30 | } 31 | 32 | type structTimeouts struct { 33 | ReadIntervalTimeout uint32 34 | ReadTotalTimeoutMultiplier uint32 35 | ReadTotalTimeoutConstant uint32 36 | WriteTotalTimeoutMultiplier uint32 37 | WriteTotalTimeoutConstant uint32 38 | } 39 | 40 | func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) { 41 | if len(name) > 0 && name[0] != '\\' { 42 | name = "\\\\.\\" + name 43 | } 44 | 45 | h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name), 46 | syscall.GENERIC_READ|syscall.GENERIC_WRITE, 47 | 0, 48 | nil, 49 | syscall.OPEN_EXISTING, 50 | syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED, 51 | 0) 52 | if err != nil { 53 | return nil, err 54 | } 55 | f := os.NewFile(uintptr(h), name) 56 | defer func() { 57 | if err != nil { 58 | f.Close() 59 | } 60 | }() 61 | 62 | if err = setCommState(h, baud); err != nil { 63 | return 64 | } 65 | if err = setupComm(h, 64, 64); err != nil { 66 | return 67 | } 68 | if err = setCommTimeouts(h, readTimeout); err != nil { 69 | return 70 | } 71 | if err = setCommMask(h); err != nil { 72 | return 73 | } 74 | 75 | ro, err := newOverlapped() 76 | if err != nil { 77 | return 78 | } 79 | wo, err := newOverlapped() 80 | if err != nil { 81 | return 82 | } 83 | port := new(Port) 84 | port.f = f 85 | port.fd = h 86 | port.ro = ro 87 | port.wo = wo 88 | 89 | return port, nil 90 | } 91 | 92 | func (p *Port) Close() error { 93 | return p.f.Close() 94 | } 95 | 96 | func (p *Port) Write(buf []byte) (int, error) { 97 | p.wl.Lock() 98 | defer p.wl.Unlock() 99 | 100 | if err := resetEvent(p.wo.HEvent); err != nil { 101 | return 0, err 102 | } 103 | var n uint32 104 | err := syscall.WriteFile(p.fd, buf, &n, p.wo) 105 | if err != nil && err != syscall.ERROR_IO_PENDING { 106 | return int(n), err 107 | } 108 | return getOverlappedResult(p.fd, p.wo) 109 | } 110 | 111 | func (p *Port) Read(buf []byte) (int, error) { 112 | if p == nil || p.f == nil { 113 | return 0, fmt.Errorf("Invalid port on read %v %v", p, p.f) 114 | } 115 | 116 | p.rl.Lock() 117 | defer p.rl.Unlock() 118 | 119 | if err := resetEvent(p.ro.HEvent); err != nil { 120 | return 0, err 121 | } 122 | var done uint32 123 | err := syscall.ReadFile(p.fd, buf, &done, p.ro) 124 | if err != nil && err != syscall.ERROR_IO_PENDING { 125 | return int(done), err 126 | } 127 | return getOverlappedResult(p.fd, p.ro) 128 | } 129 | 130 | // Discards data written to the port but not transmitted, 131 | // or data received but not read 132 | func (p *Port) Flush() error { 133 | return purgeComm(p.fd) 134 | } 135 | 136 | var ( 137 | nSetCommState, 138 | nSetCommTimeouts, 139 | nSetCommMask, 140 | nSetupComm, 141 | nGetOverlappedResult, 142 | nCreateEvent, 143 | nResetEvent, 144 | nPurgeComm, 145 | nFlushFileBuffers uintptr 146 | ) 147 | 148 | func init() { 149 | k32, err := syscall.LoadLibrary("kernel32.dll") 150 | if err != nil { 151 | panic("LoadLibrary " + err.Error()) 152 | } 153 | defer syscall.FreeLibrary(k32) 154 | 155 | nSetCommState = getProcAddr(k32, "SetCommState") 156 | nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts") 157 | nSetCommMask = getProcAddr(k32, "SetCommMask") 158 | nSetupComm = getProcAddr(k32, "SetupComm") 159 | nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult") 160 | nCreateEvent = getProcAddr(k32, "CreateEventW") 161 | nResetEvent = getProcAddr(k32, "ResetEvent") 162 | nPurgeComm = getProcAddr(k32, "PurgeComm") 163 | nFlushFileBuffers = getProcAddr(k32, "FlushFileBuffers") 164 | } 165 | 166 | func getProcAddr(lib syscall.Handle, name string) uintptr { 167 | addr, err := syscall.GetProcAddress(lib, name) 168 | if err != nil { 169 | panic(name + " " + err.Error()) 170 | } 171 | return addr 172 | } 173 | 174 | func setCommState(h syscall.Handle, baud int) error { 175 | var params structDCB 176 | params.DCBlength = uint32(unsafe.Sizeof(params)) 177 | 178 | params.flags[0] = 0x01 // fBinary 179 | params.flags[0] |= 0x10 // Assert DSR 180 | 181 | params.BaudRate = uint32(baud) 182 | params.ByteSize = 8 183 | 184 | r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(¶ms)), 0) 185 | if r == 0 { 186 | return err 187 | } 188 | return nil 189 | } 190 | 191 | func setCommTimeouts(h syscall.Handle, readTimeout time.Duration) error { 192 | var timeouts structTimeouts 193 | const MAXDWORD = 1<<32 - 1 194 | 195 | if readTimeout > 0 { 196 | // non-blocking read 197 | timeoutMs := readTimeout.Nanoseconds() / 1e6 198 | if timeoutMs < 1 { 199 | timeoutMs = 1 200 | } else if timeoutMs > MAXDWORD { 201 | timeoutMs = MAXDWORD 202 | } 203 | timeouts.ReadIntervalTimeout = 0 204 | timeouts.ReadTotalTimeoutMultiplier = 0 205 | timeouts.ReadTotalTimeoutConstant = uint32(timeoutMs) 206 | } else { 207 | // blocking read 208 | timeouts.ReadIntervalTimeout = MAXDWORD 209 | timeouts.ReadTotalTimeoutMultiplier = MAXDWORD 210 | timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1 211 | } 212 | 213 | /* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx 214 | 215 | For blocking I/O see below: 216 | 217 | Remarks: 218 | 219 | If an application sets ReadIntervalTimeout and 220 | ReadTotalTimeoutMultiplier to MAXDWORD and sets 221 | ReadTotalTimeoutConstant to a value greater than zero and 222 | less than MAXDWORD, one of the following occurs when the 223 | ReadFile function is called: 224 | 225 | If there are any bytes in the input buffer, ReadFile returns 226 | immediately with the bytes in the buffer. 227 | 228 | If there are no bytes in the input buffer, ReadFile waits 229 | until a byte arrives and then returns immediately. 230 | 231 | If no bytes arrive within the time specified by 232 | ReadTotalTimeoutConstant, ReadFile times out. 233 | */ 234 | 235 | r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0) 236 | if r == 0 { 237 | return err 238 | } 239 | return nil 240 | } 241 | 242 | func setupComm(h syscall.Handle, in, out int) error { 243 | r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out)) 244 | if r == 0 { 245 | return err 246 | } 247 | return nil 248 | } 249 | 250 | func setCommMask(h syscall.Handle) error { 251 | const EV_RXCHAR = 0x0001 252 | r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0) 253 | if r == 0 { 254 | return err 255 | } 256 | return nil 257 | } 258 | 259 | func resetEvent(h syscall.Handle) error { 260 | r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0) 261 | if r == 0 { 262 | return err 263 | } 264 | return nil 265 | } 266 | 267 | func purgeComm(h syscall.Handle) error { 268 | const PURGE_TXABORT = 0x0001 269 | const PURGE_RXABORT = 0x0002 270 | const PURGE_TXCLEAR = 0x0004 271 | const PURGE_RXCLEAR = 0x0008 272 | r, _, err := syscall.Syscall(nPurgeComm, 2, uintptr(h), 273 | PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR, 0) 274 | if r == 0 { 275 | return err 276 | } 277 | return nil 278 | } 279 | 280 | func newOverlapped() (*syscall.Overlapped, error) { 281 | var overlapped syscall.Overlapped 282 | r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0) 283 | if r == 0 { 284 | return nil, err 285 | } 286 | overlapped.HEvent = syscall.Handle(r) 287 | return &overlapped, nil 288 | } 289 | 290 | func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) { 291 | var n int 292 | r, _, err := syscall.Syscall6(nGetOverlappedResult, 4, 293 | uintptr(h), 294 | uintptr(unsafe.Pointer(overlapped)), 295 | uintptr(unsafe.Pointer(&n)), 1, 0, 0) 296 | if r == 0 { 297 | return n, err 298 | } 299 | 300 | return n, nil 301 | } 302 | -------------------------------------------------------------------------------- /serial.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "regexp" 11 | "time" 12 | ) 13 | 14 | // End of line character (AKA EOL), newline character (ASCII 10, CR, '\n'). is used by default. 15 | const EOL_DEFAULT byte = '\n' 16 | 17 | /******************************************************************************************* 18 | ******************************* TYPE DEFINITIONS **************************************** 19 | *******************************************************************************************/ 20 | 21 | type SerialPort struct { 22 | port io.ReadWriteCloser 23 | name string 24 | baud int 25 | eol uint8 26 | rxChar chan byte 27 | closeReqChann chan bool 28 | closeAckChann chan error 29 | buff *bytes.Buffer 30 | logger *log.Logger 31 | portIsOpen bool 32 | Verbose bool 33 | // openPort func(port string, baud int) (io.ReadWriteCloser, error) 34 | } 35 | 36 | /******************************************************************************************* 37 | ******************************** BASIC FUNCTIONS **************************************** 38 | *******************************************************************************************/ 39 | 40 | func New() *SerialPort { 41 | // Create new file 42 | file, err := os.OpenFile(fmt.Sprintf("log_serial_%d.txt", time.Now().Unix()), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 43 | if err != nil { 44 | log.Fatalln("Failed to open log file", ":", err) 45 | } 46 | multi := io.MultiWriter(file, os.Stdout) 47 | return &SerialPort{ 48 | logger: log.New(multi, "PREFIX: ", log.Ldate|log.Ltime), 49 | eol: EOL_DEFAULT, 50 | buff: bytes.NewBuffer(make([]uint8, 256)), 51 | Verbose: true, 52 | } 53 | } 54 | 55 | func (sp *SerialPort) Open(name string, baud int, timeout ...time.Duration) error { 56 | // Check if port is open 57 | if sp.portIsOpen { 58 | return fmt.Errorf("\"%s\" is already open", name) 59 | } 60 | var readTimeout time.Duration 61 | if len(timeout) > 0 { 62 | readTimeout = timeout[0] 63 | } 64 | // Open serial port 65 | comPort, err := openPort(name, baud, readTimeout) 66 | if err != nil { 67 | return fmt.Errorf("Unable to open port \"%s\" - %s", name, err) 68 | } 69 | // Open port succesfull 70 | sp.name = name 71 | sp.baud = baud 72 | sp.port = comPort 73 | sp.portIsOpen = true 74 | sp.buff.Reset() 75 | // Open channels 76 | sp.rxChar = make(chan byte) 77 | // Enable threads 78 | go sp.readSerialPort() 79 | go sp.processSerialPort() 80 | sp.logger.SetPrefix(fmt.Sprintf("[%s] ", sp.name)) 81 | sp.log("Serial port %s@%d open", sp.name, sp.baud) 82 | return nil 83 | } 84 | 85 | // This method close the current Serial Port. 86 | func (sp *SerialPort) Close() error { 87 | if sp.portIsOpen { 88 | sp.portIsOpen = false 89 | close(sp.rxChar) 90 | sp.log("Serial port %s closed", sp.name) 91 | return sp.port.Close() 92 | } 93 | return nil 94 | } 95 | 96 | // This method prints data trough the serial port. 97 | func (sp *SerialPort) Write(data []byte) (n int, err error) { 98 | if sp.portIsOpen { 99 | n, err = sp.port.Write(data) 100 | if err != nil { 101 | // Do nothing 102 | } else { 103 | sp.log("Tx >> %s", string(data)) 104 | } 105 | } else { 106 | err = fmt.Errorf("Serial port is not open") 107 | } 108 | return 109 | } 110 | 111 | // This method prints data trough the serial port. 112 | func (sp *SerialPort) Print(str string) error { 113 | if sp.portIsOpen { 114 | _, err := sp.port.Write([]byte(str)) 115 | if err != nil { 116 | return err 117 | } else { 118 | sp.log("Tx >> %s", str) 119 | } 120 | } else { 121 | return fmt.Errorf("Serial port is not open") 122 | } 123 | return nil 124 | } 125 | 126 | // Prints data to the serial port as human-readable ASCII text followed by a carriage return character 127 | // (ASCII 13, CR, '\r') and a newline character (ASCII 10, LF, '\n'). 128 | func (sp *SerialPort) Println(str string) error { 129 | return sp.Print(str + "\r\n") 130 | } 131 | 132 | // Printf formats according to a format specifier and print data trough the serial port. 133 | func (sp *SerialPort) Printf(format string, args ...interface{}) error { 134 | str := format 135 | if len(args) > 0 { 136 | str = fmt.Sprintf(format, args...) 137 | } 138 | return sp.Print(str) 139 | } 140 | 141 | //This method send a binary file trough the serial port. If EnableLog is active then this method will log file related data. 142 | func (sp *SerialPort) SendFile(filepath string) error { 143 | // Aux Vars 144 | sentBytes := 0 145 | q := 512 146 | data := []byte{} 147 | // Read file 148 | file, err := ioutil.ReadFile(filepath) 149 | if err != nil { 150 | sp.log("DBG >> %s", "Invalid filepath") 151 | return err 152 | } else { 153 | fileSize := len(file) 154 | sp.log("INF >> %s", "File size is %d bytes", fileSize) 155 | 156 | for sentBytes <= fileSize { 157 | //Try sending slices of less or equal than 512 bytes at time 158 | if len(file[sentBytes:]) > q { 159 | data = file[sentBytes:(sentBytes + q)] 160 | } else { 161 | data = file[sentBytes:] 162 | } 163 | // Write binaries 164 | _, err := sp.port.Write(data) 165 | if err != nil { 166 | sp.log("DBG >> %s", "Error while sending the file") 167 | return err 168 | } else { 169 | sentBytes += q 170 | time.Sleep(time.Millisecond * 100) 171 | } 172 | } 173 | } 174 | //Encode data to send 175 | return nil 176 | } 177 | 178 | // Read the first byte of the serial buffer. 179 | func (sp *SerialPort) Read() (byte, error) { 180 | if sp.portIsOpen { 181 | return sp.buff.ReadByte() 182 | } else { 183 | return 0x00, fmt.Errorf("Serial port is not open") 184 | } 185 | return 0x00, nil 186 | } 187 | 188 | // Read first available line from serial port buffer. 189 | // 190 | // Line is delimited by the EOL character, newline character (ASCII 10, LF, '\n') is used by default. 191 | // 192 | // The text returned from ReadLine does not include the line end ("\r\n" or '\n'). 193 | func (sp *SerialPort) ReadLine() (string, error) { 194 | if sp.portIsOpen { 195 | line, err := sp.buff.ReadString(sp.eol) 196 | if err != nil { 197 | return "", err 198 | } else { 199 | return removeEOL(line), nil 200 | } 201 | } else { 202 | return "", fmt.Errorf("Serial port is not open") 203 | } 204 | return "", nil 205 | } 206 | 207 | // Wait for a defined regular expression for a defined amount of time. 208 | func (sp *SerialPort) WaitForRegexTimeout(exp string, timeout time.Duration) (string, error) { 209 | 210 | if sp.portIsOpen { 211 | //Decode received data 212 | timeExpired := false 213 | 214 | regExpPatttern := regexp.MustCompile(exp) 215 | 216 | //Timeout structure 217 | c1 := make(chan string, 1) 218 | go func() { 219 | sp.log("INF >> Waiting for RegExp: \"%s\"", exp) 220 | result := []string{} 221 | for !timeExpired { 222 | line, err := sp.ReadLine() 223 | if err != nil { 224 | // Do nothing 225 | } else { 226 | result = regExpPatttern.FindAllString(line, -1) 227 | if len(result) > 0 { 228 | c1 <- result[0] 229 | break 230 | } 231 | } 232 | } 233 | }() 234 | select { 235 | case data := <-c1: 236 | sp.log("INF >> The RegExp: \"%s\"", exp) 237 | sp.log("INF >> Has been matched: \"%s\"", data) 238 | return data, nil 239 | case <-time.After(timeout): 240 | timeExpired = true 241 | sp.log("INF >> Unable to match RegExp: \"%s\"", exp) 242 | return "", fmt.Errorf("Timeout expired") 243 | } 244 | } else { 245 | return "", fmt.Errorf("Serial port is not open") 246 | } 247 | return "", nil 248 | } 249 | 250 | // Available return the total number of available unread bytes on the serial buffer. 251 | func (sp *SerialPort) Available() int { 252 | return sp.buff.Len() 253 | } 254 | 255 | // Change end of line character (AKA EOL), newline character (ASCII 10, LF, '\n') is used by default. 256 | func (sp *SerialPort) EOL(c byte) { 257 | sp.eol = c 258 | } 259 | 260 | /******************************************************************************************* 261 | ****************************** PRIVATE FUNCTIONS **************************************** 262 | *******************************************************************************************/ 263 | 264 | func (sp *SerialPort) readSerialPort() { 265 | rxBuff := make([]byte, 256) 266 | for sp.portIsOpen { 267 | n, _ := sp.port.Read(rxBuff) 268 | // Write data to serial buffer 269 | sp.buff.Write(rxBuff[:n]) 270 | for _, b := range rxBuff[:n] { 271 | if sp.portIsOpen { 272 | sp.rxChar <- b 273 | } 274 | } 275 | } 276 | } 277 | 278 | func (sp *SerialPort) processSerialPort() { 279 | screenBuff := make([]byte, 0) 280 | var lastRxByte byte 281 | for { 282 | if sp.portIsOpen { 283 | lastRxByte = <-sp.rxChar 284 | // Print received lines 285 | switch lastRxByte { 286 | case sp.eol: 287 | // EOL - Print received data 288 | sp.log("Rx << %s", string(append(screenBuff, lastRxByte))) 289 | screenBuff = make([]byte, 0) //Clean buffer 290 | break 291 | default: 292 | screenBuff = append(screenBuff, lastRxByte) 293 | } 294 | } else { 295 | break 296 | } 297 | } 298 | } 299 | 300 | func (sp *SerialPort) log(format string, a ...interface{}) { 301 | if sp.Verbose { 302 | sp.logger.Printf(format, a...) 303 | } 304 | } 305 | 306 | func removeEOL(line string) string { 307 | var data []byte 308 | // Remove CR byte "\r" 309 | for _, b := range []byte(line) { 310 | switch b { 311 | case '\r': 312 | // Do nothing 313 | case '\n': 314 | // Do nothing 315 | default: 316 | data = append(data, b) 317 | } 318 | } 319 | return string(data) 320 | } 321 | 322 | // Converts the timeout values for Linux / POSIX systems 323 | func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) { 324 | const MAXUINT8 = 1<<8 - 1 // 255 325 | // set blocking / non-blocking read 326 | var minBytesToRead uint8 = 1 327 | var readTimeoutInDeci int64 328 | if readTimeout > 0 { 329 | // EOF on zero read 330 | minBytesToRead = 0 331 | // convert timeout to deciseconds as expected by VTIME 332 | readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100) 333 | // capping the timeout 334 | if readTimeoutInDeci < 1 { 335 | // min possible timeout 1 Deciseconds (0.1s) 336 | readTimeoutInDeci = 1 337 | } else if readTimeoutInDeci > MAXUINT8 { 338 | // max possible timeout is 255 deciseconds (25.5s) 339 | readTimeoutInDeci = MAXUINT8 340 | } 341 | } 342 | return minBytesToRead, uint8(readTimeoutInDeci) 343 | } 344 | --------------------------------------------------------------------------------