├── .gitignore ├── LICENSE ├── README.markdown ├── genc.sh ├── rs232.go ├── rs232_c.c ├── rs232_test.go └── serialcat ├── README.md └── serialcat.go /.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.6 3 | *.a 4 | *.o 5 | *~ 6 | /serialcat/serialcat 7 | 6.out 8 | _* 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2008 Dustin Sallings 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | A simple serial interface for go. 2 | 3 | This is a yak I shaved so I could use go both for Arduino work and 4 | Amateur radio (APRS) work. There are many things that can be done 5 | with serial ports that can't be done here. If you are affected by 6 | these, please help make it better. 7 | -------------------------------------------------------------------------------- /genc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | out=rs232_c.c 4 | 5 | rates=$(cat < $out < 45 | #include 46 | #include 47 | #include 48 | 49 | #include "_cgo_export.h" 50 | 51 | /* 52 | This is automatically generated by genc.sh 53 | */ 54 | 55 | void initBaudRates() { 56 | EOF 57 | 58 | for r in $rates 59 | do 60 | echo "#ifdef B$r" >> $out 61 | echo " addBaudRate($r, B$r);" >> $out 62 | echo "#endif" >> $out 63 | done 64 | 65 | echo "}" >> $out 66 | -------------------------------------------------------------------------------- /rs232.go: -------------------------------------------------------------------------------- 1 | // The RS232 package lets you access old serial junk from go 2 | package rs232 3 | 4 | //go:generate ./genc.sh 5 | 6 | /* 7 | #include 8 | #include 9 | #include 10 | 11 | void initBaudRates(); 12 | */ 13 | import "C" 14 | 15 | import ( 16 | "fmt" 17 | "os" 18 | "syscall" 19 | "time" 20 | ) 21 | 22 | var baudConversionMap = map[int]_Ctype_speed_t{} 23 | 24 | // This is your serial port handle. 25 | type SerialPort struct { 26 | port *os.File 27 | } 28 | 29 | func init() { 30 | C.initBaudRates() 31 | } 32 | 33 | //export addBaudRate 34 | func addBaudRate(num int, val _Ctype_speed_t) { 35 | baudConversionMap[num] = val 36 | } 37 | 38 | func baudConversion(rate int) (flag _Ctype_speed_t) { 39 | return baudConversionMap[rate] 40 | } 41 | 42 | // SerConf represents the basic serial configuration to provide to OpenPort. 43 | type SerConf int 44 | 45 | const ( 46 | S_8N1 SerConf = iota 47 | S_7E1 48 | S_7O1 49 | ) 50 | 51 | // Opens and returns a non-blocking serial port. 52 | // The device, baud rate, and SerConf is specified. 53 | // 54 | // Example: rs232.OpenPort("/dev/ttyS0", 115200, rs232.S_8N1) 55 | func OpenPort(port string, baudRate int, serconf SerConf) (*SerialPort, error) { 56 | rv := &SerialPort{} 57 | f, err := os.OpenFile(port, 58 | syscall.O_RDWR|syscall.O_NOCTTY, 0666) 59 | if err != nil { 60 | return nil, err 61 | } 62 | rv.port = f 63 | 64 | fd := rv.port.Fd() 65 | 66 | var options C.struct_termios 67 | if C.tcgetattr(C.int(fd), &options) < 0 { 68 | defer f.Close() 69 | return nil, fmt.Errorf("tcgetattr failed") 70 | } 71 | 72 | if C.cfsetispeed(&options, baudConversion(baudRate)) < 0 { 73 | defer f.Close() 74 | return nil, fmt.Errorf("cfsetispeed failed") 75 | } 76 | if C.cfsetospeed(&options, baudConversion(baudRate)) < 0 { 77 | defer f.Close() 78 | return nil, fmt.Errorf("cfsetospeed failed") 79 | } 80 | switch serconf { 81 | case S_8N1: 82 | { 83 | options.c_cflag &^= C.PARENB 84 | options.c_cflag &^= C.CSTOPB 85 | options.c_cflag &^= C.CSIZE 86 | options.c_cflag |= C.CS8 87 | } 88 | case S_7E1: 89 | { 90 | options.c_cflag |= C.PARENB 91 | options.c_cflag &^= C.PARODD 92 | options.c_cflag &^= C.CSTOPB 93 | options.c_cflag &^= C.CSIZE 94 | options.c_cflag |= C.CS7 95 | } 96 | case S_7O1: 97 | { 98 | options.c_cflag |= C.PARENB 99 | options.c_cflag |= C.PARODD 100 | options.c_cflag &^= C.CSTOPB 101 | options.c_cflag &^= C.CSIZE 102 | options.c_cflag |= C.CS7 103 | } 104 | } 105 | // Local 106 | options.c_cflag |= (C.CLOCAL | C.CREAD) 107 | // no hardware flow control 108 | options.c_cflag &^= C.CRTSCTS 109 | // Don't EOF on a zero read, just block 110 | options.c_cc[C.VMIN] = 1 111 | 112 | if C.tcsetattr(C.int(fd), C.TCSANOW, &options) < 0 { 113 | defer f.Close() 114 | return nil, fmt.Errorf("tcsetattr failed") 115 | } 116 | 117 | return rv, nil 118 | } 119 | 120 | // SetInputAttr sets VMIN and VTIME for control serial reads. 121 | // 122 | // In non-canonical input processing mode, input is not assembled into 123 | // lines and input processing (erase, kill, delete, etc.) does not 124 | // occur. Two parameters control the behavior of this mode: 125 | // c_cc[VTIME] sets the character timer, and c_cc[VMIN] sets the 126 | // minimum number of characters to receive before satisfying the read. 127 | // 128 | // If MIN > 0 and TIME = 0, MIN sets the number of characters to 129 | //receive before the read is satisfied. As TIME is zero, the timer is 130 | //not used. 131 | // 132 | // If MIN = 0 and TIME > 0, TIME serves as a timeout value. The read 133 | //will be satisfied if a single character is read, or TIME is exceeded 134 | //(t = TIME *0.1 s). If TIME is exceeded, no character will be 135 | //returned. 136 | // 137 | // If MIN > 0 and TIME > 0, TIME serves as an inter-character 138 | //timer. The read will be satisfied if MIN characters are received, or 139 | //the time between two characters exceeds TIME. The timer is restarted 140 | //every time a character is received and only becomes active after the 141 | //first character has been received. 142 | // 143 | // If MIN = 0 and TIME = 0, read will be satisfied immediately. The 144 | //number of characters currently available, or the number of 145 | //characters requested will be returned. According to Antonino (see 146 | //contributions), you could issue a fcntl(fd, F_SETFL, FNDELAY); 147 | //before reading to get the same result. 148 | // 149 | // By modifying newtio.c_cc[VTIME] and newtio.c_cc[VMIN] all modes 150 | //described above can be tested. 151 | // 152 | // -- copied from http://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html 153 | func (port *SerialPort) SetInputAttr(minBytes int, timeout time.Duration) error { 154 | fd := port.port.Fd() 155 | 156 | var options C.struct_termios 157 | if C.tcgetattr(C.int(fd), &options) < 0 { 158 | return fmt.Errorf("tcgetattr failed") 159 | } 160 | options.c_cc[C.VMIN] = _Ctype_cc_t(minBytes) 161 | options.c_cc[C.VTIME] = _Ctype_cc_t(timeout / (time.Second / 10)) 162 | 163 | if C.tcsetattr(C.int(fd), C.TCSANOW, &options) < 0 { 164 | return fmt.Errorf("tcsetattr failed") 165 | } 166 | return nil 167 | } 168 | 169 | // SetNonblock enables nonblocking serial IO. 170 | func (port *SerialPort) SetNonblock() error { 171 | fd := port.port.Fd() 172 | 173 | return syscall.SetNonblock(int(fd), true) 174 | } 175 | 176 | // Read from the port. 177 | func (port *SerialPort) Read(p []byte) (int, error) { 178 | return port.port.Read(p) 179 | } 180 | 181 | // Write to the port. 182 | func (port *SerialPort) Write(p []byte) (int, error) { 183 | return port.port.Write(p) 184 | } 185 | 186 | // Close the port. 187 | func (port *SerialPort) Close() error { 188 | return port.port.Close() 189 | } 190 | -------------------------------------------------------------------------------- /rs232_c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "_cgo_export.h" 7 | 8 | /* 9 | This is automatically generated by genc.sh 10 | */ 11 | 12 | void initBaudRates() { 13 | #ifdef B0 14 | addBaudRate(0, B0); 15 | #endif 16 | #ifdef B50 17 | addBaudRate(50, B50); 18 | #endif 19 | #ifdef B75 20 | addBaudRate(75, B75); 21 | #endif 22 | #ifdef B110 23 | addBaudRate(110, B110); 24 | #endif 25 | #ifdef B134 26 | addBaudRate(134, B134); 27 | #endif 28 | #ifdef B150 29 | addBaudRate(150, B150); 30 | #endif 31 | #ifdef B200 32 | addBaudRate(200, B200); 33 | #endif 34 | #ifdef B300 35 | addBaudRate(300, B300); 36 | #endif 37 | #ifdef B600 38 | addBaudRate(600, B600); 39 | #endif 40 | #ifdef B1200 41 | addBaudRate(1200, B1200); 42 | #endif 43 | #ifdef B1800 44 | addBaudRate(1800, B1800); 45 | #endif 46 | #ifdef B2400 47 | addBaudRate(2400, B2400); 48 | #endif 49 | #ifdef B4800 50 | addBaudRate(4800, B4800); 51 | #endif 52 | #ifdef B7200 53 | addBaudRate(7200, B7200); 54 | #endif 55 | #ifdef B9600 56 | addBaudRate(9600, B9600); 57 | #endif 58 | #ifdef B14400 59 | addBaudRate(14400, B14400); 60 | #endif 61 | #ifdef B19200 62 | addBaudRate(19200, B19200); 63 | #endif 64 | #ifdef B28800 65 | addBaudRate(28800, B28800); 66 | #endif 67 | #ifdef B38400 68 | addBaudRate(38400, B38400); 69 | #endif 70 | #ifdef B57600 71 | addBaudRate(57600, B57600); 72 | #endif 73 | #ifdef B76800 74 | addBaudRate(76800, B76800); 75 | #endif 76 | #ifdef B115200 77 | addBaudRate(115200, B115200); 78 | #endif 79 | #ifdef B230400 80 | addBaudRate(230400, B230400); 81 | #endif 82 | #ifdef B460800 83 | addBaudRate(460800, B460800); 84 | #endif 85 | #ifdef B500000 86 | addBaudRate(500000, B500000); 87 | #endif 88 | #ifdef B576000 89 | addBaudRate(576000, B576000); 90 | #endif 91 | #ifdef B921600 92 | addBaudRate(921600, B921600); 93 | #endif 94 | #ifdef B1000000 95 | addBaudRate(1000000, B1000000); 96 | #endif 97 | #ifdef B1152000 98 | addBaudRate(1152000, B1152000); 99 | #endif 100 | #ifdef B1500000 101 | addBaudRate(1500000, B1500000); 102 | #endif 103 | #ifdef B2000000 104 | addBaudRate(2000000, B2000000); 105 | #endif 106 | #ifdef B2500000 107 | addBaudRate(2500000, B2500000); 108 | #endif 109 | #ifdef B3000000 110 | addBaudRate(3000000, B3000000); 111 | #endif 112 | #ifdef B3500000 113 | addBaudRate(3500000, B3500000); 114 | #endif 115 | #ifdef B4000000 116 | addBaudRate(4000000, B4000000); 117 | #endif 118 | } 119 | -------------------------------------------------------------------------------- /rs232_test.go: -------------------------------------------------------------------------------- 1 | package rs232 2 | 3 | import "io" 4 | 5 | var _ = io.ReadWriteCloser(&SerialPort{}) 6 | -------------------------------------------------------------------------------- /serialcat/README.md: -------------------------------------------------------------------------------- 1 | # serialcat 2 | 3 | Serialcat is a simple program that copies all of the data read from a 4 | serial port to stdout. 5 | 6 | Usage: serialcat [args] /dev/tty.whatever 7 | -baud=57600: Baud rate 8 | -mode="8N1": 8N1 | 7E1 | 7O1 9 | 10 | ## Example 11 | 12 | serialcat -baud 115200 /dev/tty.usbserial-A900acmg | tee output 13 | 14 | -------------------------------------------------------------------------------- /serialcat/serialcat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | 10 | "github.com/dustin/go-rs232" 11 | ) 12 | 13 | func init() { 14 | flag.Usage = func() { 15 | fmt.Fprintf(os.Stderr, "Usage: %s [args] /dev/tty.whatever\n", os.Args[0]) 16 | flag.PrintDefaults() 17 | } 18 | } 19 | 20 | var modes = map[string]rs232.SerConf{ 21 | "8N1": rs232.S_8N1, 22 | "7E1": rs232.S_7E1, 23 | "7O1": rs232.S_7O1, 24 | } 25 | 26 | func parseMode(s string) rs232.SerConf { 27 | rv, ok := modes[s] 28 | if !ok { 29 | log.Fatalf("Invalid mode: %v", s) 30 | } 31 | return rv 32 | } 33 | 34 | func main() { 35 | baudRate := flag.Int("baud", 57600, "Baud rate") 36 | mode := flag.String("mode", "8N1", "8N1 | 7E1 | 7O1") 37 | flag.Parse() 38 | 39 | path := flag.Arg(0) 40 | if path == "" { 41 | flag.Usage() 42 | os.Exit(64) 43 | } 44 | 45 | port, err := rs232.OpenPort(path, *baudRate, parseMode(*mode)) 46 | if err != nil { 47 | log.Fatalf("Error opening port %q: %s", path, err) 48 | } 49 | 50 | io.Copy(os.Stdout, port) 51 | } 52 | --------------------------------------------------------------------------------