Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added port list functionality and increased range of baud rates available via serial_posix.go #37

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions posix_all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// +build !windows

package serial

import (
"io/ioutil"
"regexp"
"strings"
"time"
)

const (
devFolder = "/dev"
)


// Converts the timeout values for Linux / POSIX systems
// Moved to this new source module from serial.go
func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) {
const MAXUINT8 = 1<<8 - 1 // 255
// set blocking / non-blocking read
var minBytesToRead uint8 = 1
var readTimeoutInDeci int64
if readTimeout > 0 {
// EOF on zero read
minBytesToRead = 0
// convert timeout to deciseconds as expected by VTIME
readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100)
// capping the timeout
if readTimeoutInDeci < 1 {
// min possible timeout 1 Deciseconds (0.1s)
readTimeoutInDeci = 1
} else if readTimeoutInDeci > MAXUINT8 {
// max possible timeout is 255 deciseconds (25.5s)
readTimeoutInDeci = MAXUINT8
}
}
return minBytesToRead, uint8(readTimeoutInDeci)
}

/*
This function was taken with minor modifications from the go.bug.st/serial package (https://github.com/bugst/go-serial), and is subject to the conditions of its license (reproduced below):

Copyright (c) 2014, Cristian Maglie.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
func listPorts() ([]string, error) {
files, err := ioutil.ReadDir(devFolder)
if err != nil {
return nil, err
}

ports := make([]string, 0, len(files))
for _, f := range files {
// Skip folders
if f.IsDir() {
continue
}

// Keep only devices with name matching the port name filter defined for this platform
// (discarding placeholder entries for non-existent onboard COM ports)
match, err := regexp.MatchString(portNameFilter, f.Name())
if err != nil {
return nil, err
}
if !match || isALegacyPlaceholder(f.Name()) {
continue
}

// Save serial port in the resulting list
ports = append(ports, devFolder + "/" + f.Name())
}

return ports, nil
}

// Checks whether port entry is just a placeholder -- e.g. reserved for a legacy ISA COM
// port that doesn't exist
func isALegacyPlaceholder(portName string) (bool){
const legacyComPortPrefix = "ttyS"
if strings.HasPrefix(portName, legacyComPortPrefix) {
port, err := openPort(devFolder + "/" + portName, 9600, 100)
if err != nil {
return true;
} else {
port.Close()
}
}
return false;
}
15 changes: 15 additions & 0 deletions posix_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//+build darwin

package serial

//
// Apparently OSX creates a CU (Call-Up) and a tty device entry for
// each attached serial device, prefixing them with 'cu.' and 'tty.'
// respectively
// Should maybe restrict filter to cu devices?
// (see http://pbxbook.com/other/mac-tty.html)
// Although linux has dispensed with / deprecated this distinction:
// http://tldp.org/HOWTO/Modem-HOWTO-9.html#ss9.8
const (
portNameFilter = `^(cu|tty)\..*`
)
15 changes: 15 additions & 0 deletions posix_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//+build linux

package serial

/*
This heuristic regex filter should catch most serial devices under linux.
The prefixes represent devices of the following types
ttyS: onboard uarts
ttyUSB: USB<->uart bridges
ttyACM: Abstract Control Model devices (e.g. modems -- see https://www.rfc1149.net/blog/2013/03/05/what-is-the-difference-between-devttyusbx-and-devttyacmx/)
ttyAMA: Don't know what AMA stands for, but seems to be used for Raspberry PI onboard ports at least
*/
const (
portNameFilter = `^(ttyS|ttyUSB|ttyACM|ttyAMA)\d+`
)
10 changes: 10 additions & 0 deletions posix_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build !linux,!darwin

package serial

/*
This is a catchall for posix platforms other than linux and OS X
*/
const (
portNameFilter = `^tty.*`
)
30 changes: 8 additions & 22 deletions serial.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,32 +90,18 @@ type Config struct {
// CRLFTranslate bool
}

// OpenPort opens a serial port with the specified configuration
func OpenPort(c *Config) (*Port, error) {
return openPort(c.Name, c.Baud, c.ReadTimeout)
}

// Converts the timeout values for Linux / POSIX systems
func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) {
const MAXUINT8 = 1<<8 - 1 // 255
// set blocking / non-blocking read
var minBytesToRead uint8 = 1
var readTimeoutInDeci int64
if readTimeout > 0 {
// EOF on zero read
minBytesToRead = 0
// convert timeout to deciseconds as expected by VTIME
readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100)
// capping the timeout
if readTimeoutInDeci < 1 {
// min possible timeout 1 Deciseconds (0.1s)
readTimeoutInDeci = 1
} else if readTimeoutInDeci > MAXUINT8 {
// max possible timeout is 255 deciseconds (25.5s)
readTimeoutInDeci = MAXUINT8
}
}
return minBytesToRead, uint8(readTimeoutInDeci)
// ListPorts returns a list of serial ports identified on the system.
// The windows implementation queries the registry and should be accurate.
// The implementation for posix platforms just uses an heuristic approach, applying a regex
// filter to the contents of /dev. Sensible defaults for linux and OS X have been defined in
// 'posix_linux.go' and 'posix_darwin.go', respectively. On any other posix platform,
// all devices matching the overly-inclusive pattern '^tty.*' will be returned.
func ListPorts() ([]string, error) {
return listPorts()
}

// func SendBreak()
Expand Down
2 changes: 0 additions & 2 deletions serial_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"os"
"syscall"
"time"
//"unsafe"
)

func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err error) {
Expand Down Expand Up @@ -55,7 +54,6 @@ func openPort(name string, baud int, readTimeout time.Duration) (p *Port, err er
f.Close()
return nil, fmt.Errorf("Unknown baud rate %v", baud)
}

_, err = C.cfsetispeed(&st, speed)
if err != nil {
f.Close()
Expand Down
105 changes: 103 additions & 2 deletions serial_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package serial

import (
"errors"
"fmt"
"os"
"sync"
Expand Down Expand Up @@ -130,7 +131,83 @@ func (p *Port) Read(buf []byte) (int, error) {
// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
return purgeComm(p.fd)
err := purgeComm(p.fd)
clearCommError(p.fd)
return err
}

/*
This function was taken with minor modifications from the go.bug.st/serial package (https://github.com/bugst/go-serial), and is subject to the conditions of its license (reproduced below):

Copyright (c) 2014, Cristian Maglie.
All rights reserved.

Redistribution and use in source and binary forms, with or without
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost the standard Go License, which tarm/serial is already under. The Go license is a 3 clause BSD license like this one Copyright the Go Authors. I would like to keep the existing license text and not add this.

Unfortunately @cmaglie is not in the standard Go AUTHORS file right now:
https://golang.org/AUTHORS

modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
func listPorts() ([]string, error) {
subKey, err := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM\\")
if err != nil {
return nil, errors.New("Error enumerating ports")
}

var h syscall.Handle
if syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, subKey, 0, syscall.KEY_READ, &h) != nil {
return nil, errors.New("Error enumerating ports")
}
defer syscall.RegCloseKey(h)

var valuesCount uint32
if syscall.RegQueryInfoKey(h, nil, nil, nil, nil, nil, nil, &valuesCount, nil, nil, nil, nil) != nil {
return nil, errors.New("Error enumerating ports")
}

list := make([]string, valuesCount)
for i := range list {
var data [1024]uint16
dataSize := uint32(len(data))
var name [1024]uint16
nameSize := uint32(len(name))
if regEnumValue(h, uint32(i), &name[0], &nameSize, nil, nil, &data[0], &dataSize) != nil {
return nil, errors.New("Error enumerating ports")
}
list[i] = syscall.UTF16ToString(data[:])
}
return list, nil
}

func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) {
r0, _, _ := syscall.Syscall9(nRegEnumValueW, 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(value)), uintptr(unsafe.Pointer(valueLen)), 0)
if r0 != 0 {
return syscall.Errno(r0)
}
return nil
}

var (
Expand All @@ -142,7 +219,9 @@ var (
nCreateEvent,
nResetEvent,
nPurgeComm,
nFlushFileBuffers uintptr
nFlushFileBuffers,
nClearCommError,
nRegEnumValueW uintptr
)

func init() {
Expand All @@ -151,6 +230,11 @@ func init() {
panic("LoadLibrary " + err.Error())
}
defer syscall.FreeLibrary(k32)
api32, err := syscall.LoadLibrary("advapi32.dll")
if err != nil {
panic("LoadLibrary " + err.Error())
}
defer syscall.FreeLibrary(api32)

nSetCommState = getProcAddr(k32, "SetCommState")
nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
Expand All @@ -161,6 +245,12 @@ func init() {
nResetEvent = getProcAddr(k32, "ResetEvent")
nPurgeComm = getProcAddr(k32, "PurgeComm")
nFlushFileBuffers = getProcAddr(k32, "FlushFileBuffers")
nClearCommError = getProcAddr(k32, "ClearCommError")
nRegEnumValueW = getProcAddr(api32, "RegEnumValueW")
}

func clearCommError(h syscall.Handle) error {
return processSyscall(nClearCommError, 1, uintptr(h), 0, 0)
}

func getProcAddr(lib syscall.Handle, name string) uintptr {
Expand All @@ -171,13 +261,24 @@ func getProcAddr(lib syscall.Handle, name string) uintptr {
return addr
}

func processSyscall(systemMethod, nargs, a1, a2, a3 uintptr) error {
result, _, err := syscall.Syscall(systemMethod, nargs, a1, a2, a3)
if result == 0 {
return err
}
return nil
}

func setCommState(h syscall.Handle, baud int) error {
var params structDCB
params.DCBlength = uint32(unsafe.Sizeof(params))

params.flags[0] = 0x01 // fBinary
params.flags[0] |= 0x10 // Assert DSR

//ADDITION: To help with this problem: http://zachsaw.blogspot.ie/2010/07/net-serialport-woes.html
params.flags[1] &= ^byte(0x40)

params.BaudRate = uint32(baud)
params.ByteSize = 8

Expand Down