Merge branch 'usb' into v1
This commit is contained in:
@@ -23,8 +23,9 @@ matrix:
|
||||
before_install:
|
||||
|
||||
script:
|
||||
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go get github.com/stretchr/testify/require
|
||||
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go get golang.org/x/sys/windows
|
||||
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go build -v ./...
|
||||
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go build -v go.bug.st/serial.v1
|
||||
- GOARM=5 GO386=387 GOOS=$TEST_OS GOARCH=$TEST_ARCH go test -c -v go.bug.st/serial.v1
|
||||
|
||||
notifications:
|
||||
|
||||
32
doc.go
32
doc.go
@@ -79,7 +79,35 @@ serial port:
|
||||
fmt.Printf("%v", string(buff[:n]))
|
||||
}
|
||||
|
||||
This library doesn't make use of cgo and "C" package, so it's a pure go library
|
||||
that can be easily cross compiled.
|
||||
If a port is a virtual USB-CDC serial port (for example an USB-to-RS232
|
||||
cable or a microcontroller development board) is possible to retrieve
|
||||
the USB metadata, like VID/PID or USB Serial Number, with the
|
||||
GetDetailedPortsList function in the enumerator package:
|
||||
|
||||
import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
ports, err := enumerator.GetDetailedPortsList()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
fmt.Println("No serial ports found!")
|
||||
return
|
||||
}
|
||||
for _, port := range ports {
|
||||
fmt.Printf("Found port: %s\n", port.Name)
|
||||
if port.IsUSB {
|
||||
fmt.Printf(" USB ID %s:%s\n", port.VID, port.PID)
|
||||
fmt.Printf(" USB serial %s\n", port.SerialNumber)
|
||||
}
|
||||
}
|
||||
|
||||
for details on USB port enumeration see the documentation of the specific package.
|
||||
|
||||
This library tries to avoid the use of the "C" package (and consequently the need
|
||||
of cgo) to simplify cross compiling.
|
||||
Unfortunately the USB enumeration package for darwin (MacOSX) requires cgo
|
||||
to access the IOKit framework. This means that if you need USB enumeration
|
||||
on darwin you're forced to use cgo.
|
||||
*/
|
||||
package serial // import "go.bug.st/serial.v1"
|
||||
|
||||
21
enumerator/doc.go
Normal file
21
enumerator/doc.go
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
/*
|
||||
Package enumerator is a golang cross-platform library for USB serial port discovery.
|
||||
|
||||
WARNING: this library is still beta-testing code! please consider the library
|
||||
and the API as *unstable*. Beware that, even if at this point it's unlike to
|
||||
happen, the API may be subject to change until this notice is removed from
|
||||
the documentation.
|
||||
|
||||
This library has been tested on Linux, Windows and Mac and uses specific OS
|
||||
services to enumerate USB PID/VID, in particular on MacOSX the use of cgo is
|
||||
required in order to access the IOKit Framework. This means that the library
|
||||
cannot be easily cross compiled for GOOS=darwing targets.
|
||||
|
||||
*/
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
43
enumerator/enumerator.go
Normal file
43
enumerator/enumerator.go
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
//go:generate go run ../extras/mksyscall_windows.go -output syscall_windows.go usb_windows.go
|
||||
|
||||
// PortDetails contains detailed information about USB serial port.
|
||||
// Use GetDetailedPortsList function to retrieve it.
|
||||
type PortDetails struct {
|
||||
Name string
|
||||
IsUSB bool
|
||||
VID string
|
||||
PID string
|
||||
SerialNumber string
|
||||
|
||||
// Manufacturer string
|
||||
// Product string
|
||||
}
|
||||
|
||||
// GetDetailedPortsList retrieve ports details like USB VID/PID.
|
||||
// Please note that this function may not be available on all OS:
|
||||
// in that case a FunctionNotImplemented error is returned.
|
||||
func GetDetailedPortsList() ([]*PortDetails, error) {
|
||||
return nativeGetDetailedPortsList()
|
||||
}
|
||||
|
||||
// PortEnumerationError is the error type for serial ports enumeration
|
||||
type PortEnumerationError struct {
|
||||
causedBy error
|
||||
}
|
||||
|
||||
// Error returns the complete error code with details on the cause of the error
|
||||
func (e PortEnumerationError) Error() string {
|
||||
reason := "Error while enumerating serial ports"
|
||||
if e.causedBy != nil {
|
||||
reason += ": " + e.causedBy.Error()
|
||||
}
|
||||
return reason
|
||||
}
|
||||
29
enumerator/example_getdetailedportlist_test.go
Normal file
29
enumerator/example_getdetailedportlist_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator_test
|
||||
|
||||
import "fmt"
|
||||
import "log"
|
||||
import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
func ExampleGetDetailedPortsList() {
|
||||
ports, err := enumerator.GetDetailedPortsList()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
fmt.Println("No serial ports found!")
|
||||
return
|
||||
}
|
||||
for _, port := range ports {
|
||||
fmt.Printf("Found port: %s\n", port.Name)
|
||||
if port.IsUSB {
|
||||
fmt.Printf(" USB ID %s:%s\n", port.VID, port.PID)
|
||||
fmt.Printf(" USB serial %s\n", port.SerialNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
113
enumerator/syscall_windows.go
Normal file
113
enumerator/syscall_windows.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||
|
||||
package enumerator
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
var (
|
||||
modsetupapi = windows.NewLazySystemDLL("setupapi.dll")
|
||||
|
||||
procSetupDiClassGuidsFromNameW = modsetupapi.NewProc("SetupDiClassGuidsFromNameW")
|
||||
procSetupDiGetClassDevsW = modsetupapi.NewProc("SetupDiGetClassDevsW")
|
||||
procSetupDiDestroyDeviceInfoList = modsetupapi.NewProc("SetupDiDestroyDeviceInfoList")
|
||||
procSetupDiEnumDeviceInfo = modsetupapi.NewProc("SetupDiEnumDeviceInfo")
|
||||
procSetupDiGetDeviceInstanceIdW = modsetupapi.NewProc("SetupDiGetDeviceInstanceIdW")
|
||||
procSetupDiOpenDevRegKey = modsetupapi.NewProc("SetupDiOpenDevRegKey")
|
||||
procSetupDiGetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiGetDeviceRegistryPropertyW")
|
||||
)
|
||||
|
||||
func setupDiClassGuidsFromNameInternal(class string, guid *guid, guidSize uint32, requiredSize *uint32) (err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(class)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return _setupDiClassGuidsFromNameInternal(_p0, guid, guidSize, requiredSize)
|
||||
}
|
||||
|
||||
func _setupDiClassGuidsFromNameInternal(class *uint16, guid *guid, guidSize uint32, requiredSize *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procSetupDiClassGuidsFromNameW.Addr(), 4, uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(guid)), uintptr(guidSize), uintptr(unsafe.Pointer(requiredSize)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setupDiGetClassDevs(guid *guid, enumerator *string, hwndParent uintptr, flags uint32) (set devicesSet, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(procSetupDiGetClassDevsW.Addr(), 4, uintptr(unsafe.Pointer(guid)), uintptr(unsafe.Pointer(enumerator)), uintptr(hwndParent), uintptr(flags), 0, 0)
|
||||
set = devicesSet(r0)
|
||||
if set == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setupDiDestroyDeviceInfoList(set devicesSet) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetupDiDestroyDeviceInfoList.Addr(), 1, uintptr(set), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setupDiEnumDeviceInfo(set devicesSet, index uint32, info *devInfoData) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetupDiEnumDeviceInfo.Addr(), 3, uintptr(set), uintptr(index), uintptr(unsafe.Pointer(info)))
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setupDiGetDeviceInstanceId(set devicesSet, devInfo *devInfoData, devInstanceId unsafe.Pointer, devInstanceIdSize uint32, requiredSize *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procSetupDiGetDeviceInstanceIdW.Addr(), 5, uintptr(set), uintptr(unsafe.Pointer(devInfo)), uintptr(devInstanceId), uintptr(devInstanceIdSize), uintptr(unsafe.Pointer(requiredSize)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setupDiOpenDevRegKey(set devicesSet, devInfo *devInfoData, scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (hkey syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(procSetupDiOpenDevRegKey.Addr(), 6, uintptr(set), uintptr(unsafe.Pointer(devInfo)), uintptr(scope), uintptr(hwProfile), uintptr(keyType), uintptr(samDesired))
|
||||
hkey = syscall.Handle(r0)
|
||||
if hkey == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, outSize *uint32, reqSize *uint32) (res bool) {
|
||||
r0, _, _ := syscall.Syscall9(procSetupDiGetDeviceRegistryPropertyW.Addr(), 7, uintptr(set), uintptr(unsafe.Pointer(devInfo)), uintptr(property), uintptr(unsafe.Pointer(propertyType)), uintptr(unsafe.Pointer(outValue)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(reqSize)), 0, 0)
|
||||
res = r0 != 0
|
||||
return
|
||||
}
|
||||
216
enumerator/usb_darwin.go
Normal file
216
enumerator/usb_darwin.go
Normal file
@@ -0,0 +1,216 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
// #cgo LDFLAGS: -framework CoreFoundation -framework IOKit -fconstant-cfstrings
|
||||
// #include <IOKit/IOKitLib.h>
|
||||
// #include <CoreFoundation/CoreFoundation.h>
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
|
||||
var ports []*PortDetails
|
||||
|
||||
services, err := getAllServices("IOSerialBSDClient")
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
for _, service := range services {
|
||||
defer service.Release()
|
||||
|
||||
port, err := extractPortInfo(C.io_registry_entry_t(service))
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
ports = append(ports, port)
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func extractPortInfo(service C.io_registry_entry_t) (*PortDetails, error) {
|
||||
name, err := service.GetStringProperty("IOCalloutDevice")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error extracting port info from device: %s", err.Error())
|
||||
}
|
||||
port := &PortDetails{}
|
||||
port.Name = name
|
||||
port.IsUSB = false
|
||||
|
||||
usbDevice := service
|
||||
for usbDevice.GetClass() != "IOUSBDevice" {
|
||||
if usbDevice, err = usbDevice.GetParent("IOService"); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
// It's an IOUSBDevice
|
||||
vid, _ := usbDevice.GetIntProperty("idVendor", C.kCFNumberSInt16Type)
|
||||
pid, _ := usbDevice.GetIntProperty("idProduct", C.kCFNumberSInt16Type)
|
||||
serialNumber, _ := usbDevice.GetStringProperty("USB Serial Number")
|
||||
//product, _ := usbDevice.GetStringProperty("USB Product Name")
|
||||
//manufacturer, _ := usbDevice.GetStringProperty("USB Vendor Name")
|
||||
//fmt.Println(product + " - " + manufacturer)
|
||||
|
||||
port.IsUSB = true
|
||||
port.VID = fmt.Sprintf("%04X", vid)
|
||||
port.PID = fmt.Sprintf("%04X", pid)
|
||||
port.SerialNumber = serialNumber
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func getAllServices(serviceType string) ([]C.io_object_t, error) {
|
||||
i, err := getMatchingServices(serviceMatching(serviceType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer i.Release()
|
||||
|
||||
var services []C.io_object_t
|
||||
tries := 0
|
||||
for tries < 5 {
|
||||
// Extract all elements from iterator
|
||||
if service, ok := i.Next(); ok {
|
||||
services = append(services, service)
|
||||
continue
|
||||
}
|
||||
// If iterator is still valid return the result
|
||||
if i.IsValid() {
|
||||
return services, nil
|
||||
}
|
||||
// Otherwise empty the result and retry
|
||||
for _, s := range services {
|
||||
s.Release()
|
||||
}
|
||||
services = []C.io_object_t{}
|
||||
i.Reset()
|
||||
tries++
|
||||
}
|
||||
// Give up if the iteration continues to fail...
|
||||
return nil, fmt.Errorf("IOServiceGetMatchingServices failed, data changed while iterating")
|
||||
}
|
||||
|
||||
// serviceMatching create a matching dictionary that specifies an IOService class match.
|
||||
func serviceMatching(serviceType string) C.CFMutableDictionaryRef {
|
||||
t := C.CString(serviceType)
|
||||
defer C.free(unsafe.Pointer(t))
|
||||
return C.IOServiceMatching(t)
|
||||
}
|
||||
|
||||
// getMatchingServices look up registered IOService objects that match a matching dictionary.
|
||||
func getMatchingServices(matcher C.CFMutableDictionaryRef) (C.io_iterator_t, error) {
|
||||
var i C.io_iterator_t
|
||||
err := C.IOServiceGetMatchingServices(C.kIOMasterPortDefault, matcher, &i)
|
||||
if err != C.KERN_SUCCESS {
|
||||
return 0, fmt.Errorf("IOServiceGetMatchingServices failed (code %d)", err)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// CFStringRef
|
||||
|
||||
func cfStringCreateWithString(s string) C.CFStringRef {
|
||||
c := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(c))
|
||||
return C.CFStringCreateWithCString(
|
||||
C.kCFAllocatorDefault, c, C.kCFStringEncodingMacRoman)
|
||||
}
|
||||
|
||||
// io_registry_entry_t
|
||||
|
||||
func (me *C.io_registry_entry_t) GetParent(plane string) (C.io_registry_entry_t, error) {
|
||||
cPlane := C.CString(plane)
|
||||
defer C.free(unsafe.Pointer(cPlane))
|
||||
var parent C.io_registry_entry_t
|
||||
err := C.IORegistryEntryGetParentEntry(*me, cPlane, &parent)
|
||||
if err != 0 {
|
||||
return 0, errors.New("No parent device available")
|
||||
}
|
||||
return parent, nil
|
||||
}
|
||||
|
||||
func (me *C.io_registry_entry_t) GetClass() string {
|
||||
obj := (*C.io_object_t)(me)
|
||||
return obj.GetClass()
|
||||
}
|
||||
|
||||
func (me *C.io_registry_entry_t) GetStringProperty(key string) (string, error) {
|
||||
k := cfStringCreateWithString(key)
|
||||
defer C.CFRelease(C.CFTypeRef(k))
|
||||
property := C.IORegistryEntryCreateCFProperty(*me, k, C.kCFAllocatorDefault, 0)
|
||||
if property == nil {
|
||||
return "", errors.New("Property not found: " + key)
|
||||
}
|
||||
defer C.CFRelease(property)
|
||||
|
||||
if ptr := C.CFStringGetCStringPtr(property, 0); ptr != nil {
|
||||
return C.GoString(ptr), nil
|
||||
}
|
||||
// in certain circumstances CFStringGetCStringPtr may return NULL
|
||||
// and we must retrieve the string by copy
|
||||
buff := make([]C.char, 1024)
|
||||
if C.CFStringGetCString(property, &buff[0], 1024, 0) != C.true {
|
||||
return "", fmt.Errorf("Property '%s' can't be converted", key)
|
||||
}
|
||||
return C.GoString(&buff[0]), nil
|
||||
}
|
||||
|
||||
func (me *C.io_registry_entry_t) GetIntProperty(key string, intType C.CFNumberType) (int, error) {
|
||||
k := cfStringCreateWithString(key)
|
||||
defer C.CFRelease(C.CFTypeRef(k))
|
||||
property := C.IORegistryEntryCreateCFProperty(*me, k, C.kCFAllocatorDefault, 0)
|
||||
if property == nil {
|
||||
return 0, errors.New("Property not found: " + key)
|
||||
}
|
||||
defer C.CFRelease(property)
|
||||
var res int
|
||||
if C.CFNumberGetValue(property, intType, unsafe.Pointer(&res)) != C.true {
|
||||
return res, fmt.Errorf("Property '%s' can't be converted or has been truncated", key)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// io_iterator_t
|
||||
|
||||
// IsValid checks if an iterator is still valid.
|
||||
// Some iterators will be made invalid if changes are made to the
|
||||
// structure they are iterating over. This function checks the iterator
|
||||
// is still valid and should be called when Next returns zero.
|
||||
// An invalid iterator can be Reset and the iteration restarted.
|
||||
func (me *C.io_iterator_t) IsValid() bool {
|
||||
return C.IOIteratorIsValid(*me) == C.true
|
||||
}
|
||||
|
||||
func (me *C.io_iterator_t) Reset() {
|
||||
C.IOIteratorReset(*me)
|
||||
}
|
||||
|
||||
func (me *C.io_iterator_t) Next() (C.io_object_t, bool) {
|
||||
res := C.IOIteratorNext(*me)
|
||||
return res, res != 0
|
||||
}
|
||||
|
||||
func (me *C.io_iterator_t) Release() {
|
||||
C.IOObjectRelease(C.io_object_t(*me))
|
||||
}
|
||||
|
||||
// io_object_t
|
||||
|
||||
func (me *C.io_object_t) Release() {
|
||||
C.IOObjectRelease(*me)
|
||||
}
|
||||
|
||||
func (me *C.io_object_t) GetClass() string {
|
||||
class := make([]C.char, 1024)
|
||||
C.IOObjectGetClass(*me, &class[0])
|
||||
return C.GoString(&class[0])
|
||||
}
|
||||
12
enumerator/usb_freebsd.go
Normal file
12
enumerator/usb_freebsd.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
|
||||
// TODO
|
||||
return nil, &PortEnumerationError{}
|
||||
}
|
||||
109
enumerator/usb_linux.go
Normal file
109
enumerator/usb_linux.go
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.bug.st/serial.v1"
|
||||
)
|
||||
|
||||
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
|
||||
// Retrieve the port list
|
||||
ports, err := serial.GetPortsList()
|
||||
if err != nil {
|
||||
return nil, &PortEnumerationError{causedBy: err}
|
||||
}
|
||||
|
||||
var res []*PortDetails
|
||||
for _, port := range ports {
|
||||
details, err := nativeGetPortDetails(port)
|
||||
if err != nil {
|
||||
return nil, &PortEnumerationError{causedBy: err}
|
||||
}
|
||||
res = append(res, details)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func nativeGetPortDetails(portPath string) (*PortDetails, error) {
|
||||
portName := filepath.Base(portPath)
|
||||
devicePath := fmt.Sprintf("/sys/class/tty/%s/device", portName)
|
||||
if _, err := os.Stat(devicePath); err != nil {
|
||||
return &PortDetails{}, nil
|
||||
}
|
||||
realDevicePath, err := filepath.EvalSymlinks(devicePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't determine real path of %s: %s", devicePath, err.Error())
|
||||
}
|
||||
subSystemPath, err := filepath.EvalSymlinks(filepath.Join(realDevicePath, "subsystem"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't determine real path of %s: %s", filepath.Join(realDevicePath, "subsystem"), err.Error())
|
||||
}
|
||||
subSystem := filepath.Base(subSystemPath)
|
||||
|
||||
result := &PortDetails{Name: portPath}
|
||||
switch subSystem {
|
||||
case "usb-serial":
|
||||
err := parseUSBSysFS(filepath.Dir(filepath.Dir(realDevicePath)), result)
|
||||
return result, err
|
||||
case "usb":
|
||||
err := parseUSBSysFS(filepath.Dir(realDevicePath), result)
|
||||
return result, err
|
||||
// TODO: other cases?
|
||||
default:
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func parseUSBSysFS(usbDevicePath string, details *PortDetails) error {
|
||||
vid, err := readLine(filepath.Join(usbDevicePath, "idVendor"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid, err := readLine(filepath.Join(usbDevicePath, "idProduct"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serial, err := readLine(filepath.Join(usbDevicePath, "serial"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//manufacturer, err := readLine(filepath.Join(usbDevicePath, "manufacturer"))
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//product, err := readLine(filepath.Join(usbDevicePath, "product"))
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
details.IsUSB = true
|
||||
details.VID = vid
|
||||
details.PID = pid
|
||||
details.SerialNumber = serial
|
||||
//details.Manufacturer = manufacturer
|
||||
//details.Product = product
|
||||
return nil
|
||||
}
|
||||
|
||||
func readLine(filename string) (string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
reader := bufio.NewReader(file)
|
||||
line, _, err := reader.ReadLine()
|
||||
return string(line), err
|
||||
}
|
||||
113
enumerator/usb_ole_windows.go
Normal file
113
enumerator/usb_ole_windows.go
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Copyright 2014-2017 Lars Knudsen, Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
// +build ignore
|
||||
|
||||
package enumerator
|
||||
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
|
||||
ole "github.com/go-ole/go-ole"
|
||||
"github.com/go-ole/go-ole/oleutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
|
||||
if err != nil {
|
||||
log.Fatal("Init error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
|
||||
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
defer unknown.Release()
|
||||
|
||||
wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
defer wmi.Release()
|
||||
|
||||
serviceRaw, err := wmi.CallMethod("ConnectServer")
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
service := serviceRaw.ToIDispatch()
|
||||
defer service.Release()
|
||||
|
||||
query := "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0 and Name like '%(COM%'"
|
||||
queryResult, err := oleutil.CallMethod(service, "ExecQuery", query)
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
result := queryResult.ToIDispatch()
|
||||
defer result.Release()
|
||||
|
||||
countVar, err := result.GetProperty("Count")
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
count := int(countVar.Val)
|
||||
|
||||
res := []*PortDetails{}
|
||||
|
||||
// Retrieve all items
|
||||
for i := 0; i < count; i++ {
|
||||
itemRaw, err := result.CallMethod("ItemIndex", i)
|
||||
if err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
item := itemRaw.ToIDispatch()
|
||||
defer item.Release()
|
||||
|
||||
detail := &PortDetails{}
|
||||
if err := getPortDetails(item, detail); err != nil {
|
||||
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||
}
|
||||
// SerialPort{Path: path, VendorId: VID, ProductId: PID, DisplayName: displayName.ToString()}
|
||||
res = append(res, detail)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getPortDetails(item *ole.IDispatch, res *PortDetails) error {
|
||||
// Find port name
|
||||
itemName, err := item.GetProperty("Name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re := regexp.MustCompile("\\((COM[0-9]+)\\)").FindAllStringSubmatch(itemName.ToString(), 1)
|
||||
if re == nil || len(re[0]) < 2 {
|
||||
// Discard items that are not serial ports
|
||||
return nil
|
||||
}
|
||||
res.Name = re[0][1]
|
||||
|
||||
//itemPnPDeviceID, err := item.GetProperty("PnPDeviceID")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//PnPDeviceID := itemPnPDeviceID.ToString()
|
||||
|
||||
itemDeviceID, err := item.GetProperty("DeviceID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseDeviceID(itemDeviceID.ToString(), res)
|
||||
|
||||
itemManufacturer, err := item.GetProperty("Product")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.Manufacturer = itemManufacturer.ToString()
|
||||
return nil
|
||||
}
|
||||
302
enumerator/usb_windows.go
Normal file
302
enumerator/usb_windows.go
Normal file
@@ -0,0 +1,302 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func parseDeviceID(deviceID string, details *PortDetails) {
|
||||
// Windows stock USB-CDC driver
|
||||
if len(deviceID) >= 3 && deviceID[:3] == "USB" {
|
||||
re := regexp.MustCompile("VID_(....)&PID_(....)(\\\\(\\w+)$)?").FindAllStringSubmatch(deviceID, -1)
|
||||
if re == nil || len(re[0]) < 2 {
|
||||
// Silently ignore unparsable strings
|
||||
return
|
||||
}
|
||||
details.IsUSB = true
|
||||
details.VID = re[0][1]
|
||||
details.PID = re[0][2]
|
||||
if len(re[0]) >= 4 {
|
||||
details.SerialNumber = re[0][4]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FTDI driver
|
||||
if len(deviceID) >= 7 && deviceID[:7] == "FTDIBUS" {
|
||||
re := regexp.MustCompile("VID_(....)\\+PID_(....)(\\+(\\w+))?").FindAllStringSubmatch(deviceID, -1)
|
||||
if re == nil || len(re[0]) < 2 {
|
||||
// Silently ignore unparsable strings
|
||||
return
|
||||
}
|
||||
details.IsUSB = true
|
||||
details.VID = re[0][1]
|
||||
details.PID = re[0][2]
|
||||
if len(re[0]) >= 4 {
|
||||
details.SerialNumber = re[0][4]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Other unidentified device type
|
||||
}
|
||||
|
||||
// setupapi based
|
||||
// --------------
|
||||
|
||||
//sys setupDiClassGuidsFromNameInternal(class string, guid *guid, guidSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiClassGuidsFromNameW
|
||||
//sys setupDiGetClassDevs(guid *guid, enumerator *string, hwndParent uintptr, flags uint32) (set devicesSet, err error) = setupapi.SetupDiGetClassDevsW
|
||||
//sys setupDiDestroyDeviceInfoList(set devicesSet) (err error) = setupapi.SetupDiDestroyDeviceInfoList
|
||||
//sys setupDiEnumDeviceInfo(set devicesSet, index uint32, info *devInfoData) (err error) = setupapi.SetupDiEnumDeviceInfo
|
||||
//sys setupDiGetDeviceInstanceId(set devicesSet, devInfo *devInfoData, devInstanceId unsafe.Pointer, devInstanceIdSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetDeviceInstanceIdW
|
||||
//sys setupDiOpenDevRegKey(set devicesSet, devInfo *devInfoData, scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (hkey syscall.Handle, err error) = setupapi.SetupDiOpenDevRegKey
|
||||
//sys setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, outSize *uint32, reqSize *uint32) (res bool) = setupapi.SetupDiGetDeviceRegistryPropertyW
|
||||
|
||||
// Device registry property codes
|
||||
// (Codes marked as read-only (R) may only be used for
|
||||
// SetupDiGetDeviceRegistryProperty)
|
||||
//
|
||||
// These values should cover the same set of registry properties
|
||||
// as defined by the CM_DRP codes in cfgmgr32.h.
|
||||
//
|
||||
// Note that SPDRP codes are zero based while CM_DRP codes are one based!
|
||||
type deviceProperty uint32
|
||||
|
||||
const (
|
||||
spdrpDeviceDesc deviceProperty = 0x00000000 // DeviceDesc = R/W
|
||||
spdrpHardwareID = 0x00000001 // HardwareID = R/W
|
||||
spdrpCompatibleIDS = 0x00000002 // CompatibleIDs = R/W
|
||||
spdrpUnused0 = 0x00000003 // Unused
|
||||
spdrpService = 0x00000004 // Service = R/W
|
||||
spdrpUnused1 = 0x00000005 // Unused
|
||||
spdrpUnused2 = 0x00000006 // Unused
|
||||
spdrpClass = 0x00000007 // Class = R--tied to ClassGUID
|
||||
spdrpClassGUID = 0x00000008 // ClassGUID = R/W
|
||||
spdrpDriver = 0x00000009 // Driver = R/W
|
||||
spdrpConfigFlags = 0x0000000A // ConfigFlags = R/W
|
||||
spdrpMFG = 0x0000000B // Mfg = R/W
|
||||
spdrpFriendlyName = 0x0000000C // FriendlyName = R/W
|
||||
spdrpLocationIinformation = 0x0000000D // LocationInformation = R/W
|
||||
spdrpPhysicalDeviceObjectName = 0x0000000E // PhysicalDeviceObjectName = R
|
||||
spdrpCapabilities = 0x0000000F // Capabilities = R
|
||||
spdrpUINumber = 0x00000010 // UiNumber = R
|
||||
spdrpUpperFilters = 0x00000011 // UpperFilters = R/W
|
||||
spdrpLowerFilters = 0x00000012 // LowerFilters = R/W
|
||||
spdrpBusTypeGUID = 0x00000013 // BusTypeGUID = R
|
||||
spdrpLegactBusType = 0x00000014 // LegacyBusType = R
|
||||
spdrpBusNumber = 0x00000015 // BusNumber = R
|
||||
spdrpEnumeratorName = 0x00000016 // Enumerator Name = R
|
||||
spdrpSecurity = 0x00000017 // Security = R/W, binary form
|
||||
spdrpSecuritySDS = 0x00000018 // Security = W, SDS form
|
||||
spdrpDevType = 0x00000019 // Device Type = R/W
|
||||
spdrpExclusive = 0x0000001A // Device is exclusive-access = R/W
|
||||
spdrpCharacteristics = 0x0000001B // Device Characteristics = R/W
|
||||
spdrpAddress = 0x0000001C // Device Address = R
|
||||
spdrpUINumberDescFormat = 0X0000001D // UiNumberDescFormat = R/W
|
||||
spdrpDevicePowerData = 0x0000001E // Device Power Data = R
|
||||
spdrpRemovalPolicy = 0x0000001F // Removal Policy = R
|
||||
spdrpRemovalPolicyHWDefault = 0x00000020 // Hardware Removal Policy = R
|
||||
spdrpRemovalPolicyOverride = 0x00000021 // Removal Policy Override = RW
|
||||
spdrpInstallState = 0x00000022 // Device Install State = R
|
||||
spdrpLocationPaths = 0x00000023 // Device Location Paths = R
|
||||
spdrpBaseContainerID = 0x00000024 // Base ContainerID = R
|
||||
|
||||
spdrpMaximumProperty = 0x00000025 // Upper bound on ordinals
|
||||
)
|
||||
|
||||
// Values specifying the scope of a device property change
|
||||
type dicsScope uint32
|
||||
|
||||
const (
|
||||
dicsFlagGlobal dicsScope = 0x00000001 // make change in all hardware profiles
|
||||
dicsFlagConfigSspecific = 0x00000002 // make change in specified profile only
|
||||
dicsFlagConfigGeneral = 0x00000004 // 1 or more hardware profile-specific
|
||||
)
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878(v=vs.85).aspx
|
||||
type regsam uint32
|
||||
|
||||
const (
|
||||
keyAllAccess regsam = 0xF003F
|
||||
keyCreateLink = 0x00020
|
||||
keyCreateSubKey = 0x00004
|
||||
keyEnumerateSubKeys = 0x00008
|
||||
keyExecute = 0x20019
|
||||
keyNotify = 0x00010
|
||||
keyQueryValue = 0x00001
|
||||
keyRead = 0x20019
|
||||
keySetValue = 0x00002
|
||||
keyWOW64_32key = 0x00200
|
||||
keyWOW64_64key = 0x00100
|
||||
keyWrite = 0x20006
|
||||
)
|
||||
|
||||
// KeyType values for SetupDiCreateDevRegKey, SetupDiOpenDevRegKey, and
|
||||
// SetupDiDeleteDevRegKey.
|
||||
const (
|
||||
diregDev = 0x00000001 // Open/Create/Delete device key
|
||||
diregDrv = 0x00000002 // Open/Create/Delete driver key
|
||||
diregBoth = 0x00000004 // Delete both driver and Device key
|
||||
)
|
||||
|
||||
// https://msdn.microsoft.com/it-it/library/windows/desktop/aa373931(v=vs.85).aspx
|
||||
type guid struct {
|
||||
data1 uint32
|
||||
data2 uint16
|
||||
data3 uint16
|
||||
data4 [8]byte
|
||||
}
|
||||
|
||||
func (g guid) String() string {
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||
g.data1, g.data2, g.data3,
|
||||
g.data4[0], g.data4[1], g.data4[2], g.data4[3],
|
||||
g.data4[4], g.data4[5], g.data4[6], g.data4[7])
|
||||
}
|
||||
|
||||
func classGuidsFromName(className string) ([]guid, error) {
|
||||
// Determine the number of GUIDs for className
|
||||
n := uint32(0)
|
||||
if err := setupDiClassGuidsFromNameInternal(className, nil, 0, &n); err != nil {
|
||||
// ignore error: UIDs array size too small
|
||||
}
|
||||
|
||||
res := make([]guid, n)
|
||||
err := setupDiClassGuidsFromNameInternal(className, &res[0], n, &n)
|
||||
return res, err
|
||||
}
|
||||
|
||||
const (
|
||||
digcfDefault = 0x00000001 // only valid with digcfDeviceInterface
|
||||
digcfPresent = 0x00000002
|
||||
digcfAllClasses = 0x00000004
|
||||
digcfProfile = 0x00000008
|
||||
digcfDeviceInterface = 0x00000010
|
||||
)
|
||||
|
||||
type devicesSet syscall.Handle
|
||||
|
||||
func (g *guid) getDevicesSet() (devicesSet, error) {
|
||||
return setupDiGetClassDevs(g, nil, 0, digcfPresent)
|
||||
}
|
||||
|
||||
func (set devicesSet) destroy() {
|
||||
setupDiDestroyDeviceInfoList(set)
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx
|
||||
type devInfoData struct {
|
||||
size uint32
|
||||
guid guid
|
||||
devInst uint32
|
||||
reserved uintptr
|
||||
}
|
||||
|
||||
type deviceInfo struct {
|
||||
set devicesSet
|
||||
data devInfoData
|
||||
}
|
||||
|
||||
func (set devicesSet) getDeviceInfo(index int) (*deviceInfo, error) {
|
||||
result := &deviceInfo{set: set}
|
||||
|
||||
result.data.size = uint32(unsafe.Sizeof(result.data))
|
||||
err := setupDiEnumDeviceInfo(set, uint32(index), &result.data)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (dev *deviceInfo) getInstanceID() (string, error) {
|
||||
n := uint32(0)
|
||||
setupDiGetDeviceInstanceId(dev.set, &dev.data, nil, 0, &n)
|
||||
buff := make([]uint16, n)
|
||||
if err := setupDiGetDeviceInstanceId(dev.set, &dev.data, unsafe.Pointer(&buff[0]), uint32(len(buff)), &n); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return windows.UTF16ToString(buff[:]), nil
|
||||
}
|
||||
|
||||
func (dev *deviceInfo) openDevRegKey(scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (syscall.Handle, error) {
|
||||
return setupDiOpenDevRegKey(dev.set, &dev.data, scope, hwProfile, keyType, samDesired)
|
||||
}
|
||||
|
||||
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
|
||||
guids, err := classGuidsFromName("Ports")
|
||||
if err != nil {
|
||||
return nil, &PortEnumerationError{causedBy: err}
|
||||
}
|
||||
|
||||
var res []*PortDetails
|
||||
for _, g := range guids {
|
||||
devsSet, err := g.getDevicesSet()
|
||||
if err != nil {
|
||||
return nil, &PortEnumerationError{causedBy: err}
|
||||
}
|
||||
defer devsSet.destroy()
|
||||
|
||||
for i := 0; ; i++ {
|
||||
device, err := devsSet.getDeviceInfo(i)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
details := &PortDetails{}
|
||||
portName, err := retrievePortNameFromDevInfo(device)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(portName) < 3 || portName[0:3] != "COM" {
|
||||
// Accept only COM ports
|
||||
continue
|
||||
}
|
||||
details.Name = portName
|
||||
|
||||
if err := retrievePortDetailsFromDevInfo(device, details); err != nil {
|
||||
return nil, &PortEnumerationError{causedBy: err}
|
||||
}
|
||||
res = append(res, details)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func retrievePortNameFromDevInfo(device *deviceInfo) (string, error) {
|
||||
h, err := device.openDevRegKey(dicsFlagGlobal, 0, diregDev, keyRead)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer syscall.RegCloseKey(h)
|
||||
|
||||
var name [1024]uint16
|
||||
nameP := (*byte)(unsafe.Pointer(&name[0]))
|
||||
nameSize := uint32(len(name) * 2)
|
||||
if err := syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr("PortName"), nil, nil, nameP, &nameSize); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return syscall.UTF16ToString(name[:]), nil
|
||||
}
|
||||
|
||||
func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) error {
|
||||
deviceID, err := device.getInstanceID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseDeviceID(deviceID, details)
|
||||
|
||||
var friendlyName [1024]uint16
|
||||
friendlyNameP := (*byte)(unsafe.Pointer(&friendlyName[0]))
|
||||
friendlyNameSize := uint32(len(friendlyName) * 2)
|
||||
if setupDiGetDeviceRegistryProperty(device.set, &device.data, spdrpDeviceDesc /* spdrpFriendlyName */, nil, friendlyNameP, &friendlyNameSize, nil) {
|
||||
//details.Product = syscall.UTF16ToString(friendlyName[:])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
49
enumerator/usb_windows_test.go
Normal file
49
enumerator/usb_windows_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright 2014-2017 Cristian Maglie. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
|
||||
package enumerator // import "go.bug.st/serial.v1/enumerator"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func parseAndReturnDeviceID(deviceID string) *PortDetails {
|
||||
res := &PortDetails{}
|
||||
parseDeviceID(deviceID, res)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestParseDeviceID(t *testing.T) {
|
||||
r := require.New(t)
|
||||
test := func(deviceId, vid, pid, serialNo string) {
|
||||
res := parseAndReturnDeviceID(deviceId)
|
||||
r.True(res.IsUSB)
|
||||
r.Equal(vid, res.VID)
|
||||
r.Equal(pid, res.PID)
|
||||
r.Equal(serialNo, res.SerialNumber)
|
||||
}
|
||||
|
||||
test("FTDIBUS\\VID_0403+PID_6001+A6004CCFA\\0000", "0403", "6001", "A6004CCFA")
|
||||
test("USB\\VID_16C0&PID_0483\\12345", "16C0", "0483", "12345")
|
||||
test("USB\\VID_2341&PID_0000\\64936333936351400000", "2341", "0000", "64936333936351400000")
|
||||
test("USB\\VID_2341&PID_0000\\6493234373835191F1F1", "2341", "0000", "6493234373835191F1F1")
|
||||
test("USB\\VID_2341&PID_804E&MI_00\\6&279A3900&0&0000", "2341", "804E", "")
|
||||
test("USB\\VID_2341&PID_004E\\5&C3DC240&0&1", "2341", "004E", "")
|
||||
test("USB\\VID_03EB&PID_2111&MI_01\\6&21F3553F&0&0001", "03EB", "2111", "") // Atmel EDBG
|
||||
test("USB\\VID_2341&PID_804D&MI_00\\6&1026E213&0&0000", "2341", "804D", "")
|
||||
test("USB\\VID_2341&PID_004D\\5&C3DC240&0&1", "2341", "004D", "")
|
||||
test("USB\\VID_067B&PID_2303\\6&2C4CB384&0&3", "067B", "2303", "") // PL2303
|
||||
}
|
||||
|
||||
func TestParseDeviceIDWithInvalidStrings(t *testing.T) {
|
||||
r := require.New(t)
|
||||
res := parseAndReturnDeviceID("ABC")
|
||||
r.False(res.IsUSB)
|
||||
res2 := parseAndReturnDeviceID("USB")
|
||||
r.False(res2.IsUSB)
|
||||
}
|
||||
@@ -123,6 +123,8 @@ const (
|
||||
ErrorEnumeratingPorts
|
||||
// PortClosed the port has been closed while the operation is in progress
|
||||
PortClosed
|
||||
// FunctionNotImplemented the requested function is not implemented
|
||||
FunctionNotImplemented
|
||||
)
|
||||
|
||||
// EncodedErrorString returns a string explaining the error code
|
||||
@@ -148,6 +150,8 @@ func (e PortError) EncodedErrorString() string {
|
||||
return "Could not enumerate serial ports"
|
||||
case PortClosed:
|
||||
return "Port has been closed"
|
||||
case FunctionNotImplemented:
|
||||
return "Function not implemented"
|
||||
default:
|
||||
return "Other error"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user