commit c0438ea53f4e0440870f00d2714b65bf8d82093f Author: Cristian Maglie Date: Sun Dec 7 21:25:14 2014 +0100 first commit diff --git a/serial/Makefile b/serial/Makefile new file mode 100644 index 0000000..16ce88f --- /dev/null +++ b/serial/Makefile @@ -0,0 +1,8 @@ +include $(GOROOT)/src/Make.inc + +TARG=github.com/bugst/go-serial/serial + +GOFILES=serial.go native_$(GOOS).go + +include $(GOROOT)/src/Make.pkg + diff --git a/serial/native_linux.go b/serial/native_linux.go new file mode 100644 index 0000000..1fc4a69 --- /dev/null +++ b/serial/native_linux.go @@ -0,0 +1,258 @@ +package serial + +/* + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Define (eventually) missing constants +#ifndef IUCLC + static const tcflag_t IUCLC = 0; +#endif + +// ioctl call is not available through syscall package +//int ioctl_wrapper(int d, unsigned long request) { +// return ioctl(d, request); +//} + +//int fcntl_wrapper(int fd, int cmd, int arg) { +// return fcntl(fd, cmd, arg); +//} + +// Gain exclusive access to serial port +void setTIOCEXCL(int handle) { +#if defined TIOCEXCL + ioctl(handle, TIOCEXCL); +#endif +} + +// Release exclusive access to serial port +void setTIOCNXCL(int handle) { +#if defined TIOCNXCL + ioctl(handle, TIOCNXCL); +#endif +} + +//int selectRead(int handle) { +// fd_set rfds; +// FD_ZERO(&rfds); +// FD_SET(handle, &rfds); +// int ret = select(handle+1, &rfds, NULL, NULL, NULL); +// if (ret==-1) +// return -1; +// else +// return 0; +//} + +*/ +import "C" +import "io/ioutil" +import "regexp" +import "syscall" + +// native syscall wrapper functions + +func getExclusiveAccess(handle int) error { + _, err := C.setTIOCEXCL(C.int(handle)) + return err +} + +func releaseExclusiveAccess(handle int) error { + _, err := C.setTIOCNXCL(C.int(handle)) + return err +} + +func getTermSettings(handle int) (*C.struct_termios, error) { + settings := new(C.struct_termios) + _, err := C.tcgetattr(C.int(handle), settings) + return settings, err +} + +func setTermSettings(handle int, settings *C.struct_termios) error { + _, err := C.tcsetattr(C.int(handle), C.TCSANOW, settings) + return err +} + +func getErrno(err error) int { + return int(err.(syscall.Errno)) +} + +// OS dependent values + +const devFolder = "/dev" +const regexFilter = "(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}" + +// opaque type that implements SerialPort interface for linux +type linuxSerialPort struct { + Handle int +} + +func GetPortsList() ([]string, error) { + files, err := ioutil.ReadDir(devFolder) + if err != nil { + return nil, err + } + + ports := make([]string, len(files)) + found := 0 + for _, f := range files { + // Skip folders + if f.IsDir() { + continue + } + + // Keep only devices with the correct name + match, err := regexp.MatchString(regexFilter, f.Name()) + if err != nil { + return nil, err + } + if !match { + continue + } + + portName := devFolder + "/" + f.Name() + + // Check if serial port is real or is a placeholder serial port "ttySxx" + if f.Name()[:4] == "ttyS" { + port, err := OpenPort(portName, false) + if err != nil { + serr, ok := err.(*SerialPortError) + if ok && serr.Code() == ERROR_INVALID_SERIAL_PORT { + continue + } + } else { + port.Close() + } + } + + // Save found serial port in the resulting list + ports[found] = portName + found++ + } + + ports = ports[:found] + return ports, nil +} + +func (port *linuxSerialPort) Close() error { + releaseExclusiveAccess(port.Handle) + return syscall.Close(port.Handle) +} + +func (port *linuxSerialPort) Read(p []byte) (n int, err error) { + return syscall.Read(port.Handle, p) +} + +func (port *linuxSerialPort) Write(p []byte) (n int, err error) { + return syscall.Write(port.Handle, p) +} + +var baudrateMap = map[int]C.speed_t{ + 0: C.B0, + 50: C.B50, + 75: C.B75, + 110: C.B110, + 134: C.B134, + 150: C.B150, + 200: C.B200, + 300: C.B300, + 600: C.B600, + 1200: C.B1200, + 1800: C.B1800, + 2400: C.B2400, + 4800: C.B4800, + 9600: C.B9600, + 19200: C.B19200, + 38400: C.B38400, + 57600: C.B57600, + 115200: C.B115200, + 230400: C.B230400, + 460800: C.B460800, + 500000: C.B500000, + 576000: C.B576000, + 921600: C.B921600, + 1000000: C.B1000000, + 1152000: C.B1152000, + 1500000: C.B1500000, + 2000000: C.B2000000, + 2500000: C.B2500000, + 3000000: C.B3000000, + 3500000: C.B3500000, + 4000000: C.B4000000, +} + +func (port *linuxSerialPort) SetSpeed(speed int) error { + baudrate, ok := baudrateMap[speed] + if !ok { + return &SerialPortError{code: ERROR_INVALID_PORT_SPEED} + } + settings, err := getTermSettings(port.Handle) + if err != nil { + return err + } + C.cfsetispeed(settings, baudrate) + C.cfsetospeed(settings, baudrate) + return setTermSettings(port.Handle, settings) +} + +func OpenPort(portName string, exclusive bool) (SerialPort, error) { + handle, err := syscall.Open(portName, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NDELAY, 0) + if err != nil { + switch err { + case syscall.EBUSY: + return nil, &SerialPortError{code: ERROR_PORT_BUSY} + case syscall.EACCES: + return nil, &SerialPortError{code: ERROR_PERMISSION_DENIED} + } + return nil, err + } + + // Setup serial port with defaults + + settings, err := getTermSettings(handle) + if err != nil { + syscall.Close(handle) + return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT} + } + + // Set local mode + settings.c_cflag |= C.CREAD | C.CLOCAL + + // Set raw mode + settings.c_lflag &= ^C.tcflag_t(C.ICANON | C.ECHO | C.ECHOE | C.ECHOK | C.ECHONL | C.ECHOCTL | C.ECHOPRT | C.ECHOKE | C.ISIG | C.IEXTEN) + settings.c_iflag &= ^C.tcflag_t(C.IXON | C.IXOFF | C.IXANY | C.INPCK | C.IGNPAR | C.PARMRK | C.ISTRIP | C.IGNBRK | C.BRKINT | C.INLCR | C.IGNCR | C.ICRNL | C.IUCLC) + settings.c_oflag &= ^C.tcflag_t(C.OPOST) + + // Block reads until at least one char is available (no timeout) + settings.c_cc[C.VMIN] = 1; + settings.c_cc[C.VTIME] = 0; + + err = setTermSettings(handle, settings) + if err != nil { + syscall.Close(handle) + return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT} + } +/* + settings->c_cflag &= ~CRTSCTS; +*/ + syscall.SetNonblock(handle, false) + + if exclusive { + getExclusiveAccess(handle) + } + + serialPort := &linuxSerialPort{ + Handle: handle, + } + return serialPort, nil +} + +// vi:ts=2 diff --git a/serial/native_windows.go b/serial/native_windows.go new file mode 100644 index 0000000..d496bd1 --- /dev/null +++ b/serial/native_windows.go @@ -0,0 +1,113 @@ +package serial + +/* + +#include +#include + +*/ +import "C" +import "syscall" + +// OS dependent values + +const devFolder = "" +const regexFilter = "(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}" + +// opaque type that implements SerialPort interface for linux +type windowsSerialPort struct { + Handle int +} + +func GetPortsList() ([]string, error) { + return nil, nil + /* + private static String[] getWindowsPortNames(Pattern pattern, Comparator comparator) { + String[] portNames = serialInterface.getSerialPortNames(); + if(portNames == null){ + return new String[]{}; + } + TreeSet ports = new TreeSet(comparator); + for(String portName : portNames){ + if(pattern.matcher(portName).find()){ + ports.add(portName); + } + } + return ports.toArray(new String[ports.size()]); + } + */ +} + +func (port *windowsSerialPort) Close() error { + return nil +} + +func (port *windowsSerialPort) Read(p []byte) (n int, err error) { + return syscall.Read(port.Handle, p) +} + +func (port *windowsSerialPort) Write(p []byte) (n int, err error) { + return syscall.Write(port.Handle, p) +} + +func OpenPort(portName string, useTIOCEXCL bool) (SerialPort, error) { + portName = "\\\\.\\" + portName + + handle, err := syscall.CreateFile(portName, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED, 0) + //handle := C.CreateFile(C.CString(portName), C.GENERIC_READ | C.GENERIC_WRITE, 0, 0, C.OPEN_EXISTING, C.FILE_FLAG_OVERLAPPED, 0) + + /* + JNIEXPORT jlong JNICALL Java_jssc_SerialNativeInterface_openPort(JNIEnv *env, jobject object, jstring portName, jboolean useTIOCEXCL){ + char prefix[] = "\\\\.\\"; + const char* port = env->GetStringUTFChars(portName, JNI_FALSE); + + //since 2.1.0 -> string concat fix + char portFullName[strlen(prefix) + strlen(port) + 1]; + strcpy(portFullName, prefix); + strcat(portFullName, port); + //<- since 2.1.0 + + HANDLE hComm = CreateFile(portFullName, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + 0); + env->ReleaseStringUTFChars(portName, port); + */ + /* + if handle != syscall.INVALID_HANDLE_VALUE { + var dcb C.DCB + if C.GetCommState(handle, &dcb) != 0 { + C.CloseHandle(handle) + return nil, + } + } + */ + /* //since 2.3.0 -> + if(hComm != INVALID_HANDLE_VALUE){ + DCB *dcb = new DCB(); + if(!GetCommState(hComm, dcb)){ + CloseHandle(hComm);//since 2.7.0 + hComm = (HANDLE)jssc_SerialNativeInterface_ERR_INCORRECT_SERIAL_PORT;//(-4)Incorrect serial port + } + delete dcb; + } + else { + DWORD errorValue = GetLastError(); + if(errorValue == ERROR_ACCESS_DENIED){ + hComm = (HANDLE)jssc_SerialNativeInterface_ERR_PORT_BUSY;//(-1)Port busy + } + else if(errorValue == ERROR_FILE_NOT_FOUND){ + hComm = (HANDLE)jssc_SerialNativeInterface_ERR_PORT_NOT_FOUND;//(-2)Port not found + } + } + //<- since 2.3.0 + return (jlong)hComm;//since 2.4.0 changed to jlong + }; + + */ +} + +// vi:ts=2 diff --git a/serial/serial.go b/serial/serial.go new file mode 100644 index 0000000..7a1f470 --- /dev/null +++ b/serial/serial.go @@ -0,0 +1,51 @@ +package serial + +import "io" + +// SerialPort object +type SerialPort interface { + // Read(p []byte) (n int, err error) + // Write(p []byte) (n int, err error) + // Close() error + io.ReadWriteCloser + + // Set port speed + SetSpeed(baudrate int) error +} + +// Platform independent error type for serial ports +type SerialPortError struct { + err string + code int +} + +const ( + ERROR_PORT_BUSY = 1 + ERROR_NOT_FOUND = 2 + ERROR_INVALID_SERIAL_PORT = 3 + ERROR_PERMISSION_DENIED = 4 + ERROR_INVALID_PORT_SPEED = 5 + ERROR_OTHER = 99 +) + +func (e SerialPortError) Error() string { + switch e.code { + case ERROR_PORT_BUSY: + return "Serial port busy" + case ERROR_NOT_FOUND: + return "Serial port not found" + case ERROR_INVALID_SERIAL_PORT: + return "Invalid serial port" + case ERROR_PERMISSION_DENIED: + return "Permission denied" + case ERROR_INVALID_PORT_SPEED: + return "Invalid port speed" + } + return e.err +} + +func (e SerialPortError) Code() int { + return e.code +} + +// vi:ts=2 diff --git a/test.go b/test.go new file mode 100644 index 0000000..68c2e3f --- /dev/null +++ b/test.go @@ -0,0 +1,46 @@ +package main + +import "github.com/bugst/go-serial/serial" +import "fmt" +import "log" + +func main() { + ports, err := serial.GetPortsList() + if err != nil { + log.Fatal(err) + } + for _, port := range ports { + fmt.Printf("Found port: %v\n", port) + } + + port, err := serial.OpenPort("/dev/ttyACM0", false) + if err != nil { + log.Fatal(err) + } + err = port.SetSpeed(115200) + if err != nil { + log.Fatal(err) + } + + n, err := port.Write([]byte("10,20,30\n\r")) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Sent %v bytes\n", n) + + buff := make([]byte, 100) + for { + n, err := port.Read(buff) + if err != nil { + log.Fatal(err) + break + } + if n == 0 { + fmt.Println("\nEOF") + break + } + fmt.Printf("%v", string(buff[:n])) + } +} + +// vi:ts=2