From 2468af4fc4ba862c7322b64b3a78d9193b769437 Mon Sep 17 00:00:00 2001
From: Attila Tajti <attila.tajti@gmail.com>
Date: Fri, 11 Dec 2015 11:47:17 +0100
Subject: [PATCH] Added support for byte size, stop bits and parity

Based on the work of Calvi Paolo <calvip@it.mobile.marconi.com>
---
 serial.go         | 52 +++++++++++++++++++++++++++++++++++++++++++----
 serial_linux.go   | 40 ++++++++++++++++++++++++++++++++++--
 serial_posix.go   | 39 ++++++++++++++++++++++++++++++++---
 serial_windows.go | 35 +++++++++++++++++++++++++++----
 4 files changed, 153 insertions(+), 13 deletions(-)

diff --git a/serial.go b/serial.go
index 4717738..f61ea28 100644
--- a/serial.go
+++ b/serial.go
@@ -56,9 +56,29 @@ Example usage:
 package serial
 
 import (
+	"errors"
 	"time"
 )
 
+const DefaultSize = 8 // Default value for Config.Size
+
+type StopBits byte
+type Parity byte
+
+const (
+	Stop1     StopBits = 1
+	Stop1Half StopBits = 15
+	Stop2     StopBits = 2
+)
+
+const (
+	ParityNone  Parity = 'N'
+	ParityOdd   Parity = 'O'
+	ParityEven  Parity = 'E'
+	ParityMark  Parity = 'M' // parity bit is always 1
+	ParitySpace Parity = 'S' // parity bit is always 0
+)
+
 // Config contains the information needed to open a serial port.
 //
 // Currently few options are implemented, but more may be added in the
@@ -79,9 +99,14 @@ type Config struct {
 	Baud        int
 	ReadTimeout time.Duration // Total timeout
 
-	// Size     int // 0 get translated to 8
-	// Parity   SomeNewTypeToGetCorrectDefaultOf_None
-	// StopBits SomeNewTypeToGetCorrectDefaultOf_1
+	// Size is the number of data bits. If 0, DefaultSize is used.
+	Size byte
+
+	// Parity is the bit to use and defaults to ParityNone (no parity bit).
+	Parity Parity
+
+	// Number of stop bits to use. Default is 1 (1 stop bit).
+	StopBits StopBits
 
 	// RTSFlowControl bool
 	// DTRFlowControl bool
@@ -90,9 +115,28 @@ type Config struct {
 	// CRLFTranslate bool
 }
 
+// ErrBadSize is returned if Size is not supported.
+var ErrBadSize error = errors.New("unsupported serial data size")
+
+// ErrBadStopBits is returned if the specified StopBits setting not supported.
+var ErrBadStopBits error = errors.New("unsupported stop bit setting")
+
+// ErrBadParity is returned if the parity is not supported.
+var ErrBadParity error = errors.New("unsupported parity setting")
+
 // OpenPort opens a serial port with the specified configuration
 func OpenPort(c *Config) (*Port, error) {
-	return openPort(c.Name, c.Baud, c.ReadTimeout)
+	size, par, stop := c.Size, c.Parity, c.StopBits
+	if size == 0 {
+		size = DefaultSize
+	}
+	if par == 0 {
+		par = ParityNone
+	}
+	if stop == 0 {
+		stop = Stop1
+	}
+	return openPort(c.Name, c.Baud, size, par, stop, c.ReadTimeout)
 }
 
 // Converts the timeout values for Linux / POSIX systems
diff --git a/serial_linux.go b/serial_linux.go
index 9f0f884..adf18c6 100644
--- a/serial_linux.go
+++ b/serial_linux.go
@@ -9,7 +9,7 @@ import (
 	"unsafe"
 )
 
-func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) {
+func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
 	var bauds = map[int]uint32{
 		50:      syscall.B50,
 		75:      syscall.B75,
@@ -60,11 +60,47 @@ func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err er
 		}
 	}()
 
+	// Base settings
+	cflagToUse := syscall.CREAD | syscall.CLOCAL | rate
+	switch databits {
+	case 5:
+		cflagToUse |= syscall.CS5
+	case 6:
+		cflagToUse |= syscall.CS6
+	case 7:
+		cflagToUse |= syscall.CS7
+	case 8:
+		cflagToUse |= syscall.CS8
+	default:
+		return nil, ErrBadSize
+	}
+	// Stop bits settings
+	switch stopbits {
+	case Stop1:
+		// default is 1 stop bit
+	case Stop2:
+		cflagToUse |= syscall.CSTOPB
+	default:
+		// Don't know how to set 1.5
+		return nil, ErrBadStopBits
+	}
+	// Parity settings
+	switch parity {
+	case ParityNone:
+		// default is no parity
+	case ParityOdd:
+		cflagToUse |= syscall.PARENB
+		cflagToUse |= syscall.PARODD
+	case ParityEven:
+		cflagToUse |= syscall.PARENB
+	default:
+		return nil, ErrBadParity
+	}
 	fd := f.Fd()
 	vmin, vtime := posixTimeoutValues(readTimeout)
 	t := syscall.Termios{
 		Iflag:  syscall.IGNPAR,
-		Cflag:  syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate,
+		Cflag:  cflagToUse,
 		Cc:     [32]uint8{syscall.VMIN: vmin, syscall.VTIME: vtime},
 		Ispeed: rate,
 		Ospeed: rate,
diff --git a/serial_posix.go b/serial_posix.go
index 95a592b..ac2bc21 100644
--- a/serial_posix.go
+++ b/serial_posix.go
@@ -17,7 +17,7 @@ import (
 	//"unsafe"
 )
 
-func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) {
+func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
 	f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
 	if err != nil {
 		return
@@ -72,8 +72,41 @@ func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err er
 
 	// Select local mode, turn off parity, set to 8 bits
 	st.c_cflag &= ^C.tcflag_t(C.CSIZE | C.PARENB)
-	st.c_cflag |= (C.CLOCAL | C.CREAD | C.CS8)
-
+	st.c_cflag |= (C.CLOCAL | C.CREAD)
+	// databits
+	switch databits {
+	case 5:
+		st.c_cflag |= C.CS5
+	case 6:
+		st.c_cflag |= C.CS6
+	case 7:
+		st.c_cflag |= C.CS7
+	case 8:
+		st.c_cflag |= C.CS8
+	default:
+		return nil, ErrBadSize
+	}
+	// Parity settings
+	switch parity {
+	case ParityNone:
+		// default is no parity
+	case ParityOdd:
+		st.c_cflag |= C.PARENB
+		st.c_cflag |= C.PARODD
+	case ParityEven:
+		st.c_cflag |= C.PARENB
+	default:
+		return nil, ErrBadParity
+	}
+	// Stop bits settings
+	switch stopbits {
+	case Stop1:
+		// as is, default is 1 bit
+	case Stop2:
+		st.c_cflag |= C.CSTOPB
+	default:
+		return nil, ErrBadStopBits
+	}
 	// Select raw mode
 	st.c_lflag &= ^C.tcflag_t(C.ICANON | C.ECHO | C.ECHOE | C.ISIG)
 	st.c_oflag &= ^C.tcflag_t(C.OPOST)
diff --git a/serial_windows.go b/serial_windows.go
index c158dfa..ca2eeac 100644
--- a/serial_windows.go
+++ b/serial_windows.go
@@ -37,7 +37,7 @@ type structTimeouts struct {
 	WriteTotalTimeoutConstant   uint32
 }
 
-func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) {
+func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
 	if len(name) > 0 && name[0] != '\\' {
 		name = "\\\\.\\" + name
 	}
@@ -59,7 +59,7 @@ func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err er
 		}
 	}()
 
-	if err = setCommState(h, baud); err != nil {
+	if err = setCommState(h, baud, databits, parity, stopbits); err != nil {
 		return
 	}
 	if err = setupComm(h, 64, 64); err != nil {
@@ -171,7 +171,7 @@ func getProcAddr(lib syscall.Handle, name string) uintptr {
 	return addr
 }
 
-func setCommState(h syscall.Handle, baud int) error {
+func setCommState(h syscall.Handle, baud int, databits byte, parity Parity, stopbits StopBits) error {
 	var params structDCB
 	params.DCBlength = uint32(unsafe.Sizeof(params))
 
@@ -179,7 +179,34 @@ func setCommState(h syscall.Handle, baud int) error {
 	params.flags[0] |= 0x10 // Assert DSR
 
 	params.BaudRate = uint32(baud)
-	params.ByteSize = 8
+
+	params.ByteSize = databits
+
+	switch parity {
+	case ParityNone:
+		params.Parity = 0
+	case ParityOdd:
+		params.Parity = 1
+	case ParityEven:
+		params.Parity = 2
+	case ParityMark:
+		params.Parity = 3
+	case ParitySpace:
+		params.Parity = 4
+	default:
+		return ErrBadParity
+	}
+
+	switch stopbits {
+	case Stop1:
+		params.StopBits = 0
+	case Stop1Half:
+		params.StopBits = 1
+	case Stop2:
+		params.StopBits = 2
+	default:
+		return ErrBadStopBits
+	}
 
 	r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(&params)), 0)
 	if r == 0 {