From 5050c501856544bdfc7e145c49347600f133a81a Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 8 Oct 2016 11:15:50 +0200 Subject: [PATCH 01/12] Added USB API --- example_getdetailedportlist_test.go | 29 +++++++++++++++++++++++++++++ serial.go | 23 +++++++++++++++++++++++ usb_darwin.go | 12 ++++++++++++ usb_freebsd.go | 12 ++++++++++++ usb_linux.go | 12 ++++++++++++ usb_windows.go | 12 ++++++++++++ 6 files changed, 100 insertions(+) create mode 100644 example_getdetailedportlist_test.go create mode 100644 usb_darwin.go create mode 100644 usb_freebsd.go create mode 100644 usb_linux.go create mode 100644 usb_windows.go diff --git a/example_getdetailedportlist_test.go b/example_getdetailedportlist_test.go new file mode 100644 index 0000000..1c7a491 --- /dev/null +++ b/example_getdetailedportlist_test.go @@ -0,0 +1,29 @@ +// +// Copyright 2014-2016 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 serial_test + +import "fmt" +import "log" +import "go.bug.st/serial.v1" + +func ExampleGetDetailedPortsList() { + ports, err := serial.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) + } + } +} diff --git a/serial.go b/serial.go index 331db34..91b3332 100644 --- a/serial.go +++ b/serial.go @@ -55,6 +55,25 @@ func GetPortsList() ([]string, error) { return nativeGetPortsList() } +// 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() +} + // Mode describes a serial port configuration. type Mode struct { BaudRate int // The serial port bitrate (aka Baudrate) @@ -121,6 +140,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 @@ -146,6 +167,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" } diff --git a/usb_darwin.go b/usb_darwin.go new file mode 100644 index 0000000..8a60a19 --- /dev/null +++ b/usb_darwin.go @@ -0,0 +1,12 @@ +// +// Copyright 2014-2016 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 serial // import "go.bug.st/serial.v1" + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // TODO + return nil, &PortError{code: FunctionNotImplemented} +} diff --git a/usb_freebsd.go b/usb_freebsd.go new file mode 100644 index 0000000..8a60a19 --- /dev/null +++ b/usb_freebsd.go @@ -0,0 +1,12 @@ +// +// Copyright 2014-2016 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 serial // import "go.bug.st/serial.v1" + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // TODO + return nil, &PortError{code: FunctionNotImplemented} +} diff --git a/usb_linux.go b/usb_linux.go new file mode 100644 index 0000000..8a60a19 --- /dev/null +++ b/usb_linux.go @@ -0,0 +1,12 @@ +// +// Copyright 2014-2016 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 serial // import "go.bug.st/serial.v1" + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // TODO + return nil, &PortError{code: FunctionNotImplemented} +} diff --git a/usb_windows.go b/usb_windows.go new file mode 100644 index 0000000..8a60a19 --- /dev/null +++ b/usb_windows.go @@ -0,0 +1,12 @@ +// +// Copyright 2014-2016 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 serial // import "go.bug.st/serial.v1" + +func nativeGetDetailedPortsList() ([]*PortDetails, error) { + // TODO + return nil, &PortError{code: FunctionNotImplemented} +} From 2d0a54e6c886e4d0fcea823f799d535f999aa366 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 12 Oct 2016 16:18:58 +0200 Subject: [PATCH 02/12] Added USB discovery for Linux --- usb_linux.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/usb_linux.go b/usb_linux.go index 8a60a19..35355bb 100644 --- a/usb_linux.go +++ b/usb_linux.go @@ -6,7 +6,102 @@ package serial // import "go.bug.st/serial.v1" +import ( + "bufio" + "fmt" + "os" + "path/filepath" +) + func nativeGetDetailedPortsList() ([]*PortDetails, error) { - // TODO - return nil, &PortError{code: FunctionNotImplemented} + // Retrieve the port list + ports, err := nativeGetPortsList() + if err != nil { + return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + } + + var res []*PortDetails + for _, port := range ports { + details, err := nativeGetPortDetails(port) + if err != nil { + return nil, &PortError{code: ErrorEnumeratingPorts, 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 } From ad7966a4346876652598745876d0f6412c3935f8 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 22 Oct 2016 18:36:35 +0200 Subject: [PATCH 03/12] USB detection for Windows (based on setupapi) --- .travis.yml | 1 + serial_windows.go | 2 +- syscall_windows.go | 109 +++++++++++++++- usb_windows.go | 293 +++++++++++++++++++++++++++++++++++++++++++- usb_windows_test.go | 49 ++++++++ 5 files changed, 444 insertions(+), 10 deletions(-) create mode 100644 usb_windows_test.go diff --git a/.travis.yml b/.travis.yml index 2783c85..3ad5c64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ 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 test -c -v go.bug.st/serial.v1 diff --git a/serial_windows.go b/serial_windows.go index ed04e6b..500e85a 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -23,7 +23,7 @@ type windowsPort struct { handle syscall.Handle } -//go:generate go run extras/mksyscall_windows.go -output syscall_windows.go serial_windows.go +//go:generate go run extras/mksyscall_windows.go -output syscall_windows.go serial_windows.go usb_windows.go //sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) = advapi32.RegEnumValueW diff --git a/syscall_windows.go b/syscall_windows.go index dfa3f84..add2cce 100644 --- a/syscall_windows.go +++ b/syscall_windows.go @@ -14,13 +14,21 @@ var _ unsafe.Pointer var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + modsetupapi = windows.NewLazySystemDLL("setupapi.dll") - procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") - procGetCommState = modkernel32.NewProc("GetCommState") - procSetCommState = modkernel32.NewProc("SetCommState") - procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") - procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") - procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") + procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") + procGetCommState = modkernel32.NewProc("GetCommState") + procSetCommState = modkernel32.NewProc("SetCommState") + procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") + procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") + procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") + 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 regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) { @@ -78,3 +86,92 @@ func getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) { res = r0 != 0 return } + +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 +} diff --git a/usb_windows.go b/usb_windows.go index 8a60a19..32d78e2 100644 --- a/usb_windows.go +++ b/usb_windows.go @@ -6,7 +6,294 @@ package serial // import "go.bug.st/serial.v1" -func nativeGetDetailedPortsList() ([]*PortDetails, error) { - // TODO - return nil, &PortError{code: FunctionNotImplemented} +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 +} + +//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, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + } + + var res []*PortDetails + for _, g := range guids { + devsSet, err := g.getDevicesSet() + if err != nil { + return nil, &PortError{code: ErrorEnumeratingPorts, 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, &PortError{code: ErrorEnumeratingPorts, 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 } diff --git a/usb_windows_test.go b/usb_windows_test.go new file mode 100644 index 0000000..ee58d47 --- /dev/null +++ b/usb_windows_test.go @@ -0,0 +1,49 @@ +// +// Copyright 2014-2016 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 serial // import "go.bug.st/serial.v1" + +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) +} From 3d6dd00ccbd524165fcd48da75ceb6463d31344c Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 22 Oct 2016 18:55:05 +0200 Subject: [PATCH 04/12] USB detection for Windows (alternative implementation with OLE) --- usb_ole_windows.go | 113 +++++++++++++++++++++++++++++++++++++++++++++ usb_windows.go | 3 ++ 2 files changed, 116 insertions(+) create mode 100644 usb_ole_windows.go diff --git a/usb_ole_windows.go b/usb_ole_windows.go new file mode 100644 index 0000000..128aa86 --- /dev/null +++ b/usb_ole_windows.go @@ -0,0 +1,113 @@ +// +// Copyright 2014-2016 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 serial + +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 +} diff --git a/usb_windows.go b/usb_windows.go index 32d78e2..c053297 100644 --- a/usb_windows.go +++ b/usb_windows.go @@ -51,6 +51,9 @@ func parseDeviceID(deviceID string, details *PortDetails) { // 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 From 5769f9680411996fc4e01d881f5db27d0c365bab Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 11 Dec 2016 23:46:15 +0100 Subject: [PATCH 05/12] Temporarily remove USB manufacturer/product info I need to find a better way to obtain those infos. --- serial.go | 5 +++-- usb_linux.go | 20 ++++++++++---------- usb_windows.go | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/serial.go b/serial.go index 91b3332..54a1703 100644 --- a/serial.go +++ b/serial.go @@ -63,8 +63,9 @@ type PortDetails struct { VID string PID string SerialNumber string - Manufacturer string - Product string + + // Manufacturer string + // Product string } // GetDetailedPortsList retrieve ports details like USB VID/PID. diff --git a/usb_linux.go b/usb_linux.go index 35355bb..c671140 100644 --- a/usb_linux.go +++ b/usb_linux.go @@ -74,21 +74,21 @@ func parseUSBSysFS(usbDevicePath string, details *PortDetails) error { 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 - } + //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 + //details.Manufacturer = manufacturer + //details.Product = product return nil } diff --git a/usb_windows.go b/usb_windows.go index c053297..1e3a6c4 100644 --- a/usb_windows.go +++ b/usb_windows.go @@ -295,7 +295,7 @@ func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) er 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[:]) + //details.Product = syscall.UTF16ToString(friendlyName[:]) } return nil From ecd31dc766f224bbc809ebc859fe0a4dd3ae47ea Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 13 Dec 2016 23:15:12 +0100 Subject: [PATCH 06/12] Added USB VID/PID detection for osx --- usb_darwin.go | 196 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 2 deletions(-) diff --git a/usb_darwin.go b/usb_darwin.go index 8a60a19..9b5c16c 100644 --- a/usb_darwin.go +++ b/usb_darwin.go @@ -6,7 +6,199 @@ package serial // import "go.bug.st/serial.v1" +// #cgo LDFLAGS: -framework CoreFoundation -framework IOKit -fconstant-cfstrings +// #include +// #include +// #include +import "C" +import ( + "errors" + "fmt" + "unsafe" +) + func nativeGetDetailedPortsList() ([]*PortDetails, error) { - // TODO - return nil, &PortError{code: FunctionNotImplemented} + 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) + return C.GoString(C.CFStringGetCStringPtr(property, 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 + C.CFNumberGetValue(property, intType, unsafe.Pointer(&res)) + 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]) } From b1f15cfb64808127de9bc257f0dfac01cc82b22f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 25 Dec 2016 18:26:18 +0100 Subject: [PATCH 07/12] Improved error checking on osx USB detection --- usb_darwin.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/usb_darwin.go b/usb_darwin.go index 9b5c16c..fa63667 100644 --- a/usb_darwin.go +++ b/usb_darwin.go @@ -151,7 +151,17 @@ func (me *C.io_registry_entry_t) GetStringProperty(key string) (string, error) { return "", errors.New("Property not found: " + key) } defer C.CFRelease(property) - return C.GoString(C.CFStringGetCStringPtr(property, 0)), nil + + 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) { @@ -163,7 +173,9 @@ func (me *C.io_registry_entry_t) GetIntProperty(key string, intType C.CFNumberTy } defer C.CFRelease(property) var res int - C.CFNumberGetValue(property, intType, unsafe.Pointer(&res)) + 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 } From 9d2bfefb78ee4f80c1a86676c546f6af416a045d Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 25 Dec 2016 18:46:04 +0100 Subject: [PATCH 08/12] Updated documentation (USB detection) Added a section for USB detection. cgo and "C" package are now used (to access osx frameworks). --- doc.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index 647e88f..5d9bc6f 100644 --- a/doc.go +++ b/doc.go @@ -79,7 +79,26 @@ 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 virutal USB-CDC serial port (an USB-to-RS232 cable or a +microcontroller development board are typical examples) is possible to +retrieve the USB metadata, like VID/PID or USB Serial Number, with the +GetDetailedPortsList function: + + ports, err := serial.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) + } + } + */ package serial // import "go.bug.st/serial.v1" From c8b1c23a56e6a7683f2544848c03ef328007a80b Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 7 Jan 2017 23:38:51 +0100 Subject: [PATCH 09/12] Moved USB enumerations subroutines in their own package --- doc.go | 19 ++- enumerator/enumerator.go | 41 +++++++ .../example_getdetailedportlist_test.go | 8 +- enumerator/syscall_windows.go | 113 ++++++++++++++++++ usb_darwin.go => enumerator/usb_darwin.go | 4 +- usb_freebsd.go => enumerator/usb_freebsd.go | 6 +- usb_linux.go => enumerator/usb_linux.go | 12 +- .../usb_ole_windows.go | 4 +- usb_windows.go => enumerator/usb_windows.go | 12 +- .../usb_windows_test.go | 4 +- serial.go | 20 ---- serial_windows.go | 2 +- syscall_windows.go | 109 +---------------- 13 files changed, 202 insertions(+), 152 deletions(-) create mode 100644 enumerator/enumerator.go rename example_getdetailedportlist_test.go => enumerator/example_getdetailedportlist_test.go (74%) create mode 100644 enumerator/syscall_windows.go rename usb_darwin.go => enumerator/usb_darwin.go (98%) rename usb_freebsd.go => enumerator/usb_freebsd.go (53%) rename usb_linux.go => enumerator/usb_linux.go (89%) rename usb_ole_windows.go => enumerator/usb_ole_windows.go (97%) rename usb_windows.go => enumerator/usb_windows.go (96%) rename usb_windows_test.go => enumerator/usb_windows_test.go (92%) diff --git a/doc.go b/doc.go index 5d9bc6f..c9b9901 100644 --- a/doc.go +++ b/doc.go @@ -79,12 +79,14 @@ serial port: fmt.Printf("%v", string(buff[:n])) } -If a port is a virutal USB-CDC serial port (an USB-to-RS232 cable or a -microcontroller development board are typical examples) is possible to -retrieve the USB metadata, like VID/PID or USB Serial Number, with the -GetDetailedPortsList function: +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: - ports, err := serial.GetDetailedPortsList() + import "go.bug.st/serial.v1/enumerator" + + ports, err := enumerator.GetDetailedPortsList() if err != nil { log.Fatal(err) } @@ -100,5 +102,12 @@ GetDetailedPortsList function: } } +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" diff --git a/enumerator/enumerator.go b/enumerator/enumerator.go new file mode 100644 index 0000000..b72a72f --- /dev/null +++ b/enumerator/enumerator.go @@ -0,0 +1,41 @@ +// +// 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" + +// 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 +} diff --git a/example_getdetailedportlist_test.go b/enumerator/example_getdetailedportlist_test.go similarity index 74% rename from example_getdetailedportlist_test.go rename to enumerator/example_getdetailedportlist_test.go index 1c7a491..18c7738 100644 --- a/example_getdetailedportlist_test.go +++ b/enumerator/example_getdetailedportlist_test.go @@ -1,17 +1,17 @@ // -// Copyright 2014-2016 Cristian Maglie. All rights reserved. +// 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 serial_test +package enumerator_test import "fmt" import "log" -import "go.bug.st/serial.v1" +import "go.bug.st/serial.v1/enumerator" func ExampleGetDetailedPortsList() { - ports, err := serial.GetDetailedPortsList() + ports, err := enumerator.GetDetailedPortsList() if err != nil { log.Fatal(err) } diff --git a/enumerator/syscall_windows.go b/enumerator/syscall_windows.go new file mode 100644 index 0000000..cd598c7 --- /dev/null +++ b/enumerator/syscall_windows.go @@ -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 +} diff --git a/usb_darwin.go b/enumerator/usb_darwin.go similarity index 98% rename from usb_darwin.go rename to enumerator/usb_darwin.go index fa63667..a4b632a 100644 --- a/usb_darwin.go +++ b/enumerator/usb_darwin.go @@ -1,10 +1,10 @@ // -// Copyright 2014-2016 Cristian Maglie. All rights reserved. +// 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 serial // import "go.bug.st/serial.v1" +package enumerator // import "go.bug.st/serial.v1/enumerator" // #cgo LDFLAGS: -framework CoreFoundation -framework IOKit -fconstant-cfstrings // #include diff --git a/usb_freebsd.go b/enumerator/usb_freebsd.go similarity index 53% rename from usb_freebsd.go rename to enumerator/usb_freebsd.go index 8a60a19..d60d13f 100644 --- a/usb_freebsd.go +++ b/enumerator/usb_freebsd.go @@ -1,12 +1,12 @@ // -// Copyright 2014-2016 Cristian Maglie. All rights reserved. +// 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 serial // import "go.bug.st/serial.v1" +package enumerator // import "go.bug.st/serial.v1/enumerator" func nativeGetDetailedPortsList() ([]*PortDetails, error) { // TODO - return nil, &PortError{code: FunctionNotImplemented} + return nil, &PortEnumerationError{} } diff --git a/usb_linux.go b/enumerator/usb_linux.go similarity index 89% rename from usb_linux.go rename to enumerator/usb_linux.go index c671140..d2aca90 100644 --- a/usb_linux.go +++ b/enumerator/usb_linux.go @@ -1,30 +1,32 @@ // -// Copyright 2014-2016 Cristian Maglie. All rights reserved. +// 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 serial // import "go.bug.st/serial.v1" +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 := nativeGetPortsList() + ports, err := serial.GetPortsList() if err != nil { - return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + return nil, &PortEnumerationError{causedBy: err} } var res []*PortDetails for _, port := range ports { details, err := nativeGetPortDetails(port) if err != nil { - return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + return nil, &PortEnumerationError{causedBy: err} } res = append(res, details) } diff --git a/usb_ole_windows.go b/enumerator/usb_ole_windows.go similarity index 97% rename from usb_ole_windows.go rename to enumerator/usb_ole_windows.go index 128aa86..bbc0435 100644 --- a/usb_ole_windows.go +++ b/enumerator/usb_ole_windows.go @@ -1,12 +1,12 @@ // -// Copyright 2014-2016 Lars Knudsen, Cristian Maglie. All rights reserved. +// 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 serial +package enumerator import ( "log" diff --git a/usb_windows.go b/enumerator/usb_windows.go similarity index 96% rename from usb_windows.go rename to enumerator/usb_windows.go index 1e3a6c4..1431deb 100644 --- a/usb_windows.go +++ b/enumerator/usb_windows.go @@ -1,10 +1,10 @@ // -// Copyright 2014-2016 Cristian Maglie. All rights reserved. +// 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 serial // import "go.bug.st/serial.v1" +package enumerator // import "go.bug.st/serial.v1/enumerator" import ( "fmt" @@ -54,6 +54,8 @@ func parseDeviceID(deviceID string, details *PortDetails) { // setupapi based // -------------- +//go:generate go run ../extras/mksyscall_windows.go -output syscall_windows.go usb_windows.go + //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 @@ -232,14 +234,14 @@ func (dev *deviceInfo) openDevRegKey(scope dicsScope, hwProfile uint32, keyType func nativeGetDetailedPortsList() ([]*PortDetails, error) { guids, err := classGuidsFromName("Ports") if err != nil { - return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + return nil, &PortEnumerationError{causedBy: err} } var res []*PortDetails for _, g := range guids { devsSet, err := g.getDevicesSet() if err != nil { - return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + return nil, &PortEnumerationError{causedBy: err} } defer devsSet.destroy() @@ -260,7 +262,7 @@ func nativeGetDetailedPortsList() ([]*PortDetails, error) { details.Name = portName if err := retrievePortDetailsFromDevInfo(device, details); err != nil { - return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err} + return nil, &PortEnumerationError{causedBy: err} } res = append(res, details) } diff --git a/usb_windows_test.go b/enumerator/usb_windows_test.go similarity index 92% rename from usb_windows_test.go rename to enumerator/usb_windows_test.go index ee58d47..853c1a2 100644 --- a/usb_windows_test.go +++ b/enumerator/usb_windows_test.go @@ -1,10 +1,10 @@ // -// Copyright 2014-2016 Cristian Maglie. All rights reserved. +// 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 serial // import "go.bug.st/serial.v1" +package enumerator // import "go.bug.st/serial.v1/enumerator" import ( "testing" diff --git a/serial.go b/serial.go index 54a1703..391911c 100644 --- a/serial.go +++ b/serial.go @@ -55,26 +55,6 @@ func GetPortsList() ([]string, error) { return nativeGetPortsList() } -// 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() -} - // Mode describes a serial port configuration. type Mode struct { BaudRate int // The serial port bitrate (aka Baudrate) diff --git a/serial_windows.go b/serial_windows.go index 500e85a..ed04e6b 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -23,7 +23,7 @@ type windowsPort struct { handle syscall.Handle } -//go:generate go run extras/mksyscall_windows.go -output syscall_windows.go serial_windows.go usb_windows.go +//go:generate go run extras/mksyscall_windows.go -output syscall_windows.go serial_windows.go //sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) = advapi32.RegEnumValueW diff --git a/syscall_windows.go b/syscall_windows.go index add2cce..dfa3f84 100644 --- a/syscall_windows.go +++ b/syscall_windows.go @@ -14,21 +14,13 @@ var _ unsafe.Pointer var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - modsetupapi = windows.NewLazySystemDLL("setupapi.dll") - procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") - procGetCommState = modkernel32.NewProc("GetCommState") - procSetCommState = modkernel32.NewProc("SetCommState") - procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") - procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") - procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") - 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") + procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") + procGetCommState = modkernel32.NewProc("GetCommState") + procSetCommState = modkernel32.NewProc("SetCommState") + procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") + procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") + procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") ) func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) { @@ -86,92 +78,3 @@ func getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) { res = r0 != 0 return } - -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 -} From 3ad7c6b7d20f166d15a5ba4b597750d6a0fd5ccc Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 4 Feb 2017 00:24:21 +0100 Subject: [PATCH 10/12] Moved go:generate into non-GOOS specific file See #19 --- enumerator/enumerator.go | 2 ++ enumerator/usb_windows.go | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/enumerator/enumerator.go b/enumerator/enumerator.go index b72a72f..918db57 100644 --- a/enumerator/enumerator.go +++ b/enumerator/enumerator.go @@ -6,6 +6,8 @@ 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 { diff --git a/enumerator/usb_windows.go b/enumerator/usb_windows.go index 1431deb..12ded68 100644 --- a/enumerator/usb_windows.go +++ b/enumerator/usb_windows.go @@ -54,8 +54,6 @@ func parseDeviceID(deviceID string, details *PortDetails) { // setupapi based // -------------- -//go:generate go run ../extras/mksyscall_windows.go -output syscall_windows.go usb_windows.go - //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 From fbd0f9fb33557538e5d450dd86eeb4e4bea10044 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 4 Feb 2017 00:31:57 +0100 Subject: [PATCH 11/12] Exclude 'enumerator' module from Travis-CI build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3ad5c64..40d48ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ 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: From 11d1e54246caacb208aa8fe57e609713d58cf513 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sat, 4 Feb 2017 00:53:51 +0100 Subject: [PATCH 12/12] Updated docs for USB --- doc.go | 2 +- enumerator/doc.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 enumerator/doc.go diff --git a/doc.go b/doc.go index c9b9901..b83f924 100644 --- a/doc.go +++ b/doc.go @@ -84,7 +84,7 @@ 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" + import "go.bug.st/serial.v1/enumerator" ports, err := enumerator.GetDetailedPortsList() if err != nil { diff --git a/enumerator/doc.go b/enumerator/doc.go new file mode 100644 index 0000000..d214715 --- /dev/null +++ b/enumerator/doc.go @@ -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"