pax_global_header00006660000000000000000000000064145636614610014526gustar00rootroot0000000000000052 comment=9a2045a5ff3bba615fb2f18588dab5c99a30d827 go-serial-1.6.2/000077500000000000000000000000001456366146100134165ustar00rootroot00000000000000go-serial-1.6.2/.github/000077500000000000000000000000001456366146100147565ustar00rootroot00000000000000go-serial-1.6.2/.github/workflows/000077500000000000000000000000001456366146100170135ustar00rootroot00000000000000go-serial-1.6.2/.github/workflows/test.yaml000066400000000000000000000026101456366146100206550ustar00rootroot00000000000000name: test on: push: branches: - master pull_request: jobs: native-os-build: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - uses: actions/setup-go@v1 with: go-version: "1.17" - name: Build native run: GOARCH=amd64 go build -v ./... shell: bash - name: Install socat if: matrix.os == 'ubuntu-latest' run: sudo apt-get install socat shell: bash - name: Run unit tests run: go test -v -race ./... shell: bash - name: Cross-build for 386 if: matrix.os != 'macOS-latest' run: GOARCH=386 go build -v ./... shell: bash - name: Cross-build for arm if: matrix.os != 'macOS-latest' run: GOARCH=arm go build -v ./... shell: bash cross-os-build: strategy: matrix: go-os-pairs: - "freebsd amd64" - "openbsd amd64" - "openbsd 386" - "openbsd arm" - "linux ppc64le" runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v1 - uses: actions/setup-go@v1 with: go-version: "1.17" - name: Cross-build run: | set ${{ matrix.go-os-pairs }} GOOS=$1 GOARCH=$2 go build -v ./... shell: bash go-serial-1.6.2/LICENSE000066400000000000000000000027501456366146100144270ustar00rootroot00000000000000 Copyright (c) 2014-2023, Cristian Maglie. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-serial-1.6.2/README.md000066400000000000000000000016031456366146100146750ustar00rootroot00000000000000[![Build Status](https://github.com/bugst/go-serial/workflows/test/badge.svg)](https://github.com/bugst/go-serial/actions?workflow=test) # go.bug.st/serial A cross-platform serial library for go-lang. ## Documentation and examples See the godoc here: https://godoc.org/go.bug.st/serial ## go.mod transition This library now support `go.mod` with the import `go.bug.st/serial`. If you came from the pre-`go.mod` era please update your import paths from `go.bug.st/serial.v1` to `go.bug.st/serial` to receive new updates. Anyway, the latest `v1` release should still be avaiable using the old import. ## Credits :sparkles: Thanks to all awesome [contributors]! :sparkles: ## License The software is release under a [BSD 3-clause license] [contributors]: https://github.com/bugst/go-serial/graphs/contributors [BSD 3-clause license]: https://github.com/bugst/go-serial/blob/master/LICENSE go-serial-1.6.2/doc.go000066400000000000000000000056741456366146100145260ustar00rootroot00000000000000// // Copyright 2014-2023 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 is a cross-platform serial library for the go language. The canonical import for this library is go.bug.st/serial so the import line is the following: import "go.bug.st/serial" It is possible to get the list of available serial ports with the GetPortsList function: ports, err := serial.GetPortsList() if err != nil { log.Fatal(err) } if len(ports) == 0 { log.Fatal("No serial ports found!") } for _, port := range ports { fmt.Printf("Found port: %v\n", port) } The serial port can be opened with the Open function: mode := &serial.Mode{ BaudRate: 115200, } port, err := serial.Open("/dev/ttyUSB0", mode) if err != nil { log.Fatal(err) } The Open function needs a "mode" parameter that specifies the configuration options for the serial port. If not specified the default options are 9600_N81, in the example above only the speed is changed so the port is opened using 115200_N81. The following snippets shows how to declare a configuration for 57600_E71: mode := &serial.Mode{ BaudRate: 57600, Parity: serial.EvenParity, DataBits: 7, StopBits: serial.OneStopBit, } The configuration can be changed at any time with the SetMode function: err := port.SetMode(mode) if err != nil { log.Fatal(err) } The port object implements the io.ReadWriteCloser interface, so we can use the usual Read, Write and Close functions to send and receive data from the serial port: n, err := port.Write([]byte("10,20,30\n\r")) if err != nil { log.Fatal(err) } fmt.Printf("Sent %v bytes\n", n) buff := make([]byte, 100) for { n, err := port.Read(buff) if err != nil { log.Fatal(err) break } if n == 0 { fmt.Println("\nEOF") break } fmt.Printf("%v", string(buff[:n])) } 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/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 go-serial-1.6.2/enumerator/000077500000000000000000000000001456366146100155775ustar00rootroot00000000000000go-serial-1.6.2/enumerator/doc.go000066400000000000000000000010671456366146100166770ustar00rootroot00000000000000// // Copyright 2014-2023 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. 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 darwin/* targets. */ package enumerator go-serial-1.6.2/enumerator/enumerator.go000066400000000000000000000025661456366146100203200ustar00rootroot00000000000000// // Copyright 2014-2023 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 //go:generate go run golang.org/x/sys/windows/mkwinsyscall -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 is an OS-dependent string that describes the serial port, it may // be not always available and it may be different across OS. 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 } go-serial-1.6.2/enumerator/example_getdetailedportlist_test.go000066400000000000000000000012331456366146100247530ustar00rootroot00000000000000// // Copyright 2014-2023 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" "log" "go.bug.st/serial/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) } } } go-serial-1.6.2/enumerator/syscall_windows.go000066400000000000000000000134751456366146100213640ustar00rootroot00000000000000// Code generated by 'go generate'; DO NOT EDIT. package enumerator import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return nil case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } // TODO: add more here, after collecting data on the common // error values see on Windows. (perhaps when running // all.bat?) return e } var ( modsetupapi = windows.NewLazySystemDLL("setupapi.dll") modcfgmgr32 = windows.NewLazySystemDLL("cfgmgr32.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") procCM_Get_Parent = modcfgmgr32.NewProc("CM_Get_Parent") procCM_Get_Device_ID_Size = modcfgmgr32.NewProc("CM_Get_Device_ID_Size") procCM_Get_Device_IDW = modcfgmgr32.NewProc("CM_Get_Device_IDW") procCM_MapCrToWin32Err = modcfgmgr32.NewProc("CM_MapCrToWin32Err") ) 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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(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 = errnoErr(e1) } else { err = syscall.EINVAL } } return } func setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, bufSize 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(bufSize), uintptr(unsafe.Pointer(reqSize)), 0, 0) res = r0 != 0 return } func cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) { r0, _, _ := syscall.Syscall(procCM_Get_Parent.Addr(), 3, uintptr(unsafe.Pointer(outParentDev)), uintptr(dev), uintptr(flags)) cmErr = cmError(r0) return } func cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) { r0, _, _ := syscall.Syscall(procCM_Get_Device_ID_Size.Addr(), 3, uintptr(unsafe.Pointer(outLen)), uintptr(dev), uintptr(flags)) cmErr = cmError(r0) return } func cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) { r0, _, _ := syscall.Syscall6(procCM_Get_Device_IDW.Addr(), 4, uintptr(dev), uintptr(buffer), uintptr(bufferSize), uintptr(flags), 0, 0) err = cmError(r0) return } func cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) { r0, _, _ := syscall.Syscall(procCM_MapCrToWin32Err.Addr(), 2, uintptr(cmErr), uintptr(defaultErr), 0) err = uint32(r0) return } go-serial-1.6.2/enumerator/usb_darwin.go000066400000000000000000000164031456366146100202670ustar00rootroot00000000000000// // Copyright 2014-2023 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 // #cgo LDFLAGS: -framework CoreFoundation -framework IOKit // #include // #include // #include import "C" import ( "errors" "fmt" "time" "unsafe" ) func nativeGetDetailedPortsList() ([]*PortDetails, error) { var ports []*PortDetails services, err := getAllServices("IOSerialBSDClient") if err != nil { return nil, &PortEnumerationError{causedBy: err} } for _, service := range services { defer service.Release() port, err := extractPortInfo(io_registry_entry_t(service)) if err != nil { return nil, &PortEnumerationError{causedBy: err} } ports = append(ports, port) } return ports, nil } func extractPortInfo(service io_registry_entry_t) (*PortDetails, error) { port := &PortDetails{} // If called too early the port may still not be ready or fully enumerated // so we retry 5 times before returning error. for retries := 5; retries > 0; retries-- { name, err := service.GetStringProperty("IOCalloutDevice") if err == nil { port.Name = name break } if retries == 0 { return nil, fmt.Errorf("error extracting port info from device: %w", err) } time.Sleep(50 * time.Millisecond) } port.IsUSB = false validUSBDeviceClass := map[string]bool{ "IOUSBDevice": true, "IOUSBHostDevice": true, } usbDevice := service var searchErr error for !validUSBDeviceClass[usbDevice.GetClass()] { if usbDevice, searchErr = usbDevice.GetParent("IOService"); searchErr != nil { break } } if searchErr == 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) ([]io_object_t, error) { i, err := getMatchingServices(serviceMatching(serviceType)) if err != nil { return nil, err } defer i.Release() var services []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 the list of services is empty or the iterator is still valid return the result if len(services) == 0 || i.IsValid() { return services, nil } // Otherwise empty the result and retry for _, s := range services { s.Release() } services = []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) (io_iterator_t, error) { var i C.io_iterator_t err := C.IOServiceGetMatchingServices(C.kIOMasterPortDefault, C.CFDictionaryRef(matcher), &i) if err != C.KERN_SUCCESS { return 0, fmt.Errorf("IOServiceGetMatchingServices failed (code %d)", err) } return io_iterator_t(i), nil } // CFStringRef type cfStringRef C.CFStringRef func cfStringCreateWithString(s string) cfStringRef { c := C.CString(s) defer C.free(unsafe.Pointer(c)) return cfStringRef(C.CFStringCreateWithCString( C.kCFAllocatorDefault, c, C.kCFStringEncodingMacRoman)) } func (ref cfStringRef) Release() { C.CFRelease(C.CFTypeRef(ref)) } // CFTypeRef type cfTypeRef C.CFTypeRef func (ref cfTypeRef) Release() { C.CFRelease(C.CFTypeRef(ref)) } // io_registry_entry_t type io_registry_entry_t C.io_registry_entry_t func (me *io_registry_entry_t) GetParent(plane string) (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(C.io_registry_entry_t(*me), cPlane, &parent) if err != 0 { return 0, errors.New("No parent device available") } return io_registry_entry_t(parent), nil } func (me *io_registry_entry_t) CreateCFProperty(key string) (cfTypeRef, error) { k := cfStringCreateWithString(key) defer k.Release() property := C.IORegistryEntryCreateCFProperty(C.io_registry_entry_t(*me), C.CFStringRef(k), C.kCFAllocatorDefault, 0) if property == 0 { return 0, errors.New("Property not found: " + key) } return cfTypeRef(property), nil } func (me *io_registry_entry_t) GetStringProperty(key string) (string, error) { property, err := me.CreateCFProperty(key) if err != nil { return "", err } defer property.Release() if ptr := C.CFStringGetCStringPtr(C.CFStringRef(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(C.CFStringRef(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 *io_registry_entry_t) GetIntProperty(key string, intType C.CFNumberType) (int, error) { property, err := me.CreateCFProperty(key) if err != nil { return 0, err } defer property.Release() var res int if C.CFNumberGetValue((C.CFNumberRef)(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 } func (me *io_registry_entry_t) Release() { C.IOObjectRelease(C.io_object_t(*me)) } func (me *io_registry_entry_t) GetClass() string { class := make([]C.char, 1024) C.IOObjectGetClass(C.io_object_t(*me), &class[0]) return C.GoString(&class[0]) } // io_iterator_t type io_iterator_t C.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 *io_iterator_t) IsValid() bool { return C.IOIteratorIsValid(C.io_iterator_t(*me)) == C.true } func (me *io_iterator_t) Reset() { C.IOIteratorReset(C.io_iterator_t(*me)) } func (me *io_iterator_t) Next() (io_object_t, bool) { res := C.IOIteratorNext(C.io_iterator_t(*me)) return io_object_t(res), res != 0 } func (me *io_iterator_t) Release() { C.IOObjectRelease(C.io_object_t(*me)) } // io_object_t type io_object_t C.io_object_t func (me *io_object_t) Release() { C.IOObjectRelease(C.io_object_t(*me)) } func (me *io_object_t) GetClass() string { class := make([]C.char, 1024) C.IOObjectGetClass(C.io_object_t(*me), &class[0]) return C.GoString(&class[0]) } go-serial-1.6.2/enumerator/usb_freebsd.go000066400000000000000000000004541456366146100204140ustar00rootroot00000000000000// // Copyright 2014-2023 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 func nativeGetDetailedPortsList() ([]*PortDetails, error) { // TODO return nil, &PortEnumerationError{} } go-serial-1.6.2/enumerator/usb_linux.go000066400000000000000000000052761456366146100201500ustar00rootroot00000000000000// // Copyright 2014-2023 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 ( "bufio" "fmt" "os" "path/filepath" "go.bug.st/serial" ) 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 } go-serial-1.6.2/enumerator/usb_openbsd.go000066400000000000000000000004541456366146100204340ustar00rootroot00000000000000// // Copyright 2014-2023 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 func nativeGetDetailedPortsList() ([]*PortDetails, error) { // TODO return nil, &PortEnumerationError{} } go-serial-1.6.2/enumerator/usb_windows.go000066400000000000000000000321431456366146100204740ustar00rootroot00000000000000// // Copyright 2014-2023 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 ( "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, bufSize uint32, reqSize *uint32) (res bool) = setupapi.SetupDiGetDeviceRegistryPropertyW //sys cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Parent //sys cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Device_ID_Size //sys cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) = cfgmgr32.CM_Get_Device_IDW //sys cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) = cfgmgr32.CM_MapCrToWin32Err // 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) } type cmError uint32 // https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx type devInfoData struct { size uint32 guid guid devInst devInstance reserved uintptr } type devInstance uint32 func cmConvertError(cmErr cmError) error { if cmErr == 0 { return nil } winErr := cmMapCrToWin32Err(cmErr, 0) return fmt.Errorf("error %d", winErr) } func (dev devInstance) getParent() (devInstance, error) { var res devInstance errN := cmGetParent(&res, dev, 0) return res, cmConvertError(errN) } func (dev devInstance) GetDeviceID() (string, error) { var size uint32 cmErr := cmGetDeviceIDSize(&size, dev, 0) if err := cmConvertError(cmErr); err != nil { return "", err } buff := make([]uint16, size) cmErr = cmGetDeviceID(dev, unsafe.Pointer(&buff[0]), uint32(len(buff)), 0) if err := cmConvertError(cmErr); err != nil { return "", err } return windows.UTF16ToString(buff[:]), nil } 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) // On composite USB devices the serial number is usually reported on the parent // device, so let's navigate up one level and see if we can get this information if details.IsUSB && details.SerialNumber == "" { if parentInfo, err := device.data.devInst.getParent(); err == nil { if parentDeviceID, err := parentInfo.GetDeviceID(); err == nil { d := &PortDetails{} parseDeviceID(parentDeviceID, d) if details.VID == d.VID && details.PID == d.PID { details.SerialNumber = d.SerialNumber } } } } /* spdrpDeviceDesc returns a generic name, e.g.: "CDC-ACM", which will be the same for 2 identical devices attached while spdrpFriendlyName returns a specific name, e.g.: "CDC-ACM (COM44)", the result of spdrpFriendlyName is therefore unique and suitable as an alternative string to for a port choice */ n := uint32(0) setupDiGetDeviceRegistryProperty(device.set, &device.data, spdrpFriendlyName /* spdrpDeviceDesc */, nil, nil, 0, &n) if n > 0 { buff := make([]uint16, n*2) buffP := (*byte)(unsafe.Pointer(&buff[0])) if setupDiGetDeviceRegistryProperty(device.set, &device.data, spdrpFriendlyName /* spdrpDeviceDesc */, nil, buffP, n, &n) { details.Product = syscall.UTF16ToString(buff[:]) } } return nil } go-serial-1.6.2/enumerator/usb_windows_test.go000066400000000000000000000031471456366146100215350ustar00rootroot00000000000000// // Copyright 2014-2023 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 ( "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) } go-serial-1.6.2/example_getportlist_test.go000066400000000000000000000007571456366146100211100ustar00rootroot00000000000000// // Copyright 2014-2023 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" "log" "go.bug.st/serial" ) func ExampleGetPortsList() { ports, err := serial.GetPortsList() if err != nil { log.Fatal(err) } if len(ports) == 0 { fmt.Println("No serial ports found!") } else { for _, port := range ports { fmt.Printf("Found port: %v\n", port) } } } go-serial-1.6.2/example_modem_bits_test.go000066400000000000000000000023401456366146100206400ustar00rootroot00000000000000// // Copyright 2014-2023 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" "log" "time" "go.bug.st/serial" ) func ExampleGetSetModemBits() { // Open the first serial port detected at 9600bps N81 mode := &serial.Mode{ BaudRate: 9600, Parity: serial.NoParity, DataBits: 8, StopBits: serial.OneStopBit, } port, err := serial.Open("/dev/ttyACM1", mode) if err != nil { log.Fatal(err) } defer port.Close() count := 0 for count < 25 { status, err := port.GetModemStatusBits() if err != nil { log.Fatal(err) } fmt.Printf("Status: %+v\n", status) time.Sleep(time.Second) count++ if count == 5 { err := port.SetDTR(false) if err != nil { log.Fatal(err) } fmt.Println("Set DTR OFF") } if count == 10 { err := port.SetDTR(true) if err != nil { log.Fatal(err) } fmt.Println("Set DTR ON") } if count == 15 { err := port.SetRTS(false) if err != nil { log.Fatal(err) } fmt.Println("Set RTS OFF") } if count == 20 { err := port.SetRTS(true) if err != nil { log.Fatal(err) } fmt.Println("Set RTS ON") } } } go-serial-1.6.2/example_serialport_test.go000066400000000000000000000011201456366146100206750ustar00rootroot00000000000000// // Copyright 2014-2023 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" "log" "go.bug.st/serial" ) func ExampleSerialPort_SetMode() { port, err := serial.Open("/dev/ttyACM0", &serial.Mode{}) if err != nil { log.Fatal(err) } mode := &serial.Mode{ BaudRate: 9600, Parity: serial.NoParity, DataBits: 8, StopBits: serial.OneStopBit, } if err := port.SetMode(mode); err != nil { log.Fatal(err) } fmt.Println("Port set to 9600 N81") } go-serial-1.6.2/example_test.go000066400000000000000000000027151456366146100164440ustar00rootroot00000000000000// // Copyright 2014-2023 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" "log" "strings" "go.bug.st/serial" ) // This example prints the list of serial ports and use the first one // to send a string "10,20,30" and prints the response on the screen. func Example_sendAndReceive() { // Retrieve the port list ports, err := serial.GetPortsList() if err != nil { log.Fatal(err) } if len(ports) == 0 { log.Fatal("No serial ports found!") } // Print the list of detected ports for _, port := range ports { fmt.Printf("Found port: %v\n", port) } // Open the first serial port detected at 9600bps N81 mode := &serial.Mode{ BaudRate: 9600, Parity: serial.NoParity, DataBits: 8, StopBits: serial.OneStopBit, } port, err := serial.Open(ports[0], mode) if err != nil { log.Fatal(err) } // Send the string "10,20,30\n\r" to the serial port n, err := port.Write([]byte("10,20,30\n\r")) if err != nil { log.Fatal(err) } fmt.Printf("Sent %v bytes\n", n) // Read and print the response buff := make([]byte, 100) for { // Reads up to 100 bytes n, err := port.Read(buff) if err != nil { log.Fatal(err) } if n == 0 { fmt.Println("\nEOF") break } fmt.Printf("%s", string(buff[:n])) // If we receive a newline stop reading if strings.Contains(string(buff[:n]), "\n") { break } } } go-serial-1.6.2/go.mod000066400000000000000000000005311456366146100145230ustar00rootroot00000000000000module go.bug.st/serial go 1.17 require ( github.com/creack/goselect v0.1.2 github.com/stretchr/testify v1.7.0 golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 ) require ( github.com/davecgh/go-spew v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) go-serial-1.6.2/go.sum000066400000000000000000000031111456366146100145450ustar00rootroot00000000000000github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-serial-1.6.2/portlist/000077500000000000000000000000001456366146100152765ustar00rootroot00000000000000go-serial-1.6.2/portlist/portlist.go000066400000000000000000000017101456366146100175040ustar00rootroot00000000000000// // Copyright 2014-2023 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. // // portlist is a tool to list all the available serial ports. // Just run it and it will produce an output like: // // $ go run portlist.go // Port: /dev/cu.Bluetooth-Incoming-Port // Port: /dev/cu.usbmodemFD121 // USB ID 2341:8053 // USB serial FB7B6060504B5952302E314AFF08191A // package main import ( "fmt" "log" "go.bug.st/serial/enumerator" ) func main() { ports, err := enumerator.GetDetailedPortsList() if err != nil { log.Fatal(err) } if len(ports) == 0 { return } for _, port := range ports { fmt.Printf("Port: %s\n", port.Name) if port.Product != "" { fmt.Printf(" Product Name: %s\n", port.Product) } if port.IsUSB { fmt.Printf(" USB ID : %s:%s\n", port.VID, port.PID) fmt.Printf(" USB serial : %s\n", port.SerialNumber) } } } go-serial-1.6.2/serial.go000066400000000000000000000153161456366146100152320ustar00rootroot00000000000000// // Copyright 2014-2023 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 "time" //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go // Port is the interface for a serial Port type Port interface { // SetMode sets all parameters of the serial port SetMode(mode *Mode) error // Stores data received from the serial port into the provided byte array // buffer. The function returns the number of bytes read. // // The Read function blocks until (at least) one byte is received from // the serial port or an error occurs. Read(p []byte) (n int, err error) // Send the content of the data byte array to the serial port. // Returns the number of bytes written. Write(p []byte) (n int, err error) // Wait until all data in the buffer are sent Drain() error // ResetInputBuffer Purges port read buffer ResetInputBuffer() error // ResetOutputBuffer Purges port write buffer ResetOutputBuffer() error // SetDTR sets the modem status bit DataTerminalReady SetDTR(dtr bool) error // SetRTS sets the modem status bit RequestToSend SetRTS(rts bool) error // GetModemStatusBits returns a ModemStatusBits structure containing the // modem status bits for the serial port (CTS, DSR, etc...) GetModemStatusBits() (*ModemStatusBits, error) // SetReadTimeout sets the timeout for the Read operation or use serial.NoTimeout // to disable read timeout. SetReadTimeout(t time.Duration) error // Close the serial port Close() error // Break sends a break for a determined time Break(time.Duration) error } // NoTimeout should be used as a parameter to SetReadTimeout to disable timeout. var NoTimeout time.Duration = -1 // ModemStatusBits contains all the modem input status bits for a serial port (CTS, DSR, etc...). // It can be retrieved with the Port.GetModemStatusBits() method. type ModemStatusBits struct { CTS bool // ClearToSend status DSR bool // DataSetReady status RI bool // RingIndicator status DCD bool // DataCarrierDetect status } // ModemOutputBits contains all the modem output bits for a serial port. // This is used in the Mode.InitialStatusBits struct to specify the initial status of the bits. // Note: Linux and MacOSX (and basically all unix-based systems) can not set the status bits // before opening the port, even if the initial state of the bit is set to false they will go // anyway to true for a few milliseconds, resulting in a small pulse. type ModemOutputBits struct { RTS bool // ReadyToSend status DTR bool // DataTerminalReady status } // Open opens the serial port using the specified modes func Open(portName string, mode *Mode) (Port, error) { port, err := nativeOpen(portName, mode) if err != nil { // Return a nil interface, for which var==nil is true (instead of // a nil pointer to a struct that satisfies the interface). return nil, err } return port, err } // GetPortsList retrieve the list of available serial ports func GetPortsList() ([]string, error) { return nativeGetPortsList() } // Mode describes a serial port configuration. type Mode struct { BaudRate int // The serial port bitrate (aka Baudrate) DataBits int // Size of the character (must be 5, 6, 7 or 8) Parity Parity // Parity (see Parity type for more info) StopBits StopBits // Stop bits (see StopBits type for more info) InitialStatusBits *ModemOutputBits // Initial output modem bits status (if nil defaults to DTR=true and RTS=true) } // Parity describes a serial port parity setting type Parity int const ( // NoParity disable parity control (default) NoParity Parity = iota // OddParity enable odd-parity check OddParity // EvenParity enable even-parity check EvenParity // MarkParity enable mark-parity (always 1) check MarkParity // SpaceParity enable space-parity (always 0) check SpaceParity ) // StopBits describe a serial port stop bits setting type StopBits int const ( // OneStopBit sets 1 stop bit (default) OneStopBit StopBits = iota // OnePointFiveStopBits sets 1.5 stop bits OnePointFiveStopBits // TwoStopBits sets 2 stop bits TwoStopBits ) // PortError is a platform independent error type for serial ports type PortError struct { code PortErrorCode causedBy error } // PortErrorCode is a code to easily identify the type of error type PortErrorCode int const ( // PortBusy the serial port is already in used by another process PortBusy PortErrorCode = iota // PortNotFound the requested port doesn't exist PortNotFound // InvalidSerialPort the requested port is not a serial port InvalidSerialPort // PermissionDenied the user doesn't have enough priviledges PermissionDenied // InvalidSpeed the requested speed is not valid or not supported InvalidSpeed // InvalidDataBits the number of data bits is not valid or not supported InvalidDataBits // InvalidParity the selected parity is not valid or not supported InvalidParity // InvalidStopBits the selected number of stop bits is not valid or not supported InvalidStopBits // InvalidTimeoutValue the timeout value is not valid or not supported InvalidTimeoutValue // ErrorEnumeratingPorts an error occurred while listing serial port 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 func (e PortError) EncodedErrorString() string { switch e.code { case PortBusy: return "Serial port busy" case PortNotFound: return "Serial port not found" case InvalidSerialPort: return "Invalid serial port" case PermissionDenied: return "Permission denied" case InvalidSpeed: return "Port speed invalid or not supported" case InvalidDataBits: return "Port data bits invalid or not supported" case InvalidParity: return "Port parity invalid or not supported" case InvalidStopBits: return "Port stop bits invalid or not supported" case InvalidTimeoutValue: return "Timeout value invalid or not supported" case ErrorEnumeratingPorts: return "Could not enumerate serial ports" case PortClosed: return "Port has been closed" case FunctionNotImplemented: return "Function not implemented" default: return "Other error" } } // Error returns the complete error code with details on the cause of the error func (e PortError) Error() string { if e.causedBy != nil { return e.EncodedErrorString() + ": " + e.causedBy.Error() } return e.EncodedErrorString() } // Code returns an identifier for the kind of error occurred func (e PortError) Code() PortErrorCode { return e.code } go-serial-1.6.2/serial_bsd.go000066400000000000000000000005751456366146100160630ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build darwin || dragonfly || freebsd || netbsd || openbsd package serial import "golang.org/x/sys/unix" func (port *unixPort) Drain() error { return unix.IoctlSetInt(port.handle, unix.TIOCDRAIN, 0) } go-serial-1.6.2/serial_darwin.go000066400000000000000000000021771456366146100165770ustar00rootroot00000000000000// // Copyright 2014-2023 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 "golang.org/x/sys/unix" const devFolder = "/dev" const regexFilter = "^(cu|tty)\\..*" const ioctlTcgetattr = unix.TIOCGETA const ioctlTcsetattr = unix.TIOCSETA const ioctlTcflsh = unix.TIOCFLUSH const ioctlTioccbrk = unix.TIOCCBRK const ioctlTiocsbrk = unix.TIOCSBRK func setTermSettingsBaudrate(speed int, settings *unix.Termios) (error, bool) { baudrate, ok := baudrateMap[speed] if !ok { return nil, true } settings.Ispeed = toTermiosSpeedType(baudrate) settings.Ospeed = toTermiosSpeedType(baudrate) return nil, false } func (port *unixPort) setSpecialBaudrate(speed uint32) error { const kIOSSIOSPEED = 0x80045402 return unix.IoctlSetPointerInt(port.handle, kIOSSIOSPEED, int(speed)) } func (port *unixPort) ResetInputBuffer() error { return unix.IoctlSetPointerInt(port.handle, ioctlTcflsh, unix.TCIFLUSH) } func (port *unixPort) ResetOutputBuffer() error { return unix.IoctlSetPointerInt(port.handle, ioctlTcflsh, unix.TCOFLUSH) } go-serial-1.6.2/serial_darwin_386.go000066400000000000000000000021301456366146100171640ustar00rootroot00000000000000// // Copyright 2014-2023 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 "golang.org/x/sys/unix" // termios manipulation functions var baudrateMap = map[int]uint32{ 0: unix.B9600, // Default to 9600 50: unix.B50, 75: unix.B75, 110: unix.B110, 134: unix.B134, 150: unix.B150, 200: unix.B200, 300: unix.B300, 600: unix.B600, 1200: unix.B1200, 1800: unix.B1800, 2400: unix.B2400, 4800: unix.B4800, 9600: unix.B9600, 19200: unix.B19200, 38400: unix.B38400, 57600: unix.B57600, 115200: unix.B115200, 230400: unix.B230400, } var databitsMap = map[int]uint32{ 0: unix.CS8, // Default to 8 bits 5: unix.CS5, 6: unix.CS6, 7: unix.CS7, 8: unix.CS8, } const tcCMSPAR uint32 = 0 // may be CMSPAR or PAREXT const tcIUCLC uint32 = 0 const tcCCTS_OFLOW uint32 = 0x00010000 const tcCRTS_IFLOW uint32 = 0x00020000 const tcCRTSCTS uint32 = (tcCCTS_OFLOW | tcCRTS_IFLOW) func toTermiosSpeedType(speed uint32) uint32 { return speed } go-serial-1.6.2/serial_darwin_64.go000066400000000000000000000021771456366146100171100ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build darwin && (amd64 || arm64) package serial import "golang.org/x/sys/unix" // termios manipulation functions var baudrateMap = map[int]uint64{ 0: unix.B9600, // Default to 9600 50: unix.B50, 75: unix.B75, 110: unix.B110, 134: unix.B134, 150: unix.B150, 200: unix.B200, 300: unix.B300, 600: unix.B600, 1200: unix.B1200, 1800: unix.B1800, 2400: unix.B2400, 4800: unix.B4800, 9600: unix.B9600, 19200: unix.B19200, 38400: unix.B38400, 57600: unix.B57600, 115200: unix.B115200, 230400: unix.B230400, } var databitsMap = map[int]uint64{ 0: unix.CS8, // Default to 8 bits 5: unix.CS5, 6: unix.CS6, 7: unix.CS7, 8: unix.CS8, } const tcCMSPAR uint64 = 0 // may be CMSPAR or PAREXT const tcIUCLC uint64 = 0 const tcCCTS_OFLOW uint64 = 0x00010000 const tcCRTS_IFLOW uint64 = 0x00020000 const tcCRTSCTS uint64 = (tcCCTS_OFLOW | tcCRTS_IFLOW) func toTermiosSpeedType(speed uint64) uint64 { return speed } go-serial-1.6.2/serial_freebsd.go000066400000000000000000000036401456366146100167210ustar00rootroot00000000000000// // Copyright 2014-2023 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 "golang.org/x/sys/unix" const devFolder = "/dev" const regexFilter = "^(cu|tty)\\..*" // termios manipulation functions var baudrateMap = map[int]uint32{ 0: unix.B9600, // Default to 9600 50: unix.B50, 75: unix.B75, 110: unix.B110, 134: unix.B134, 150: unix.B150, 200: unix.B200, 300: unix.B300, 600: unix.B600, 1200: unix.B1200, 1800: unix.B1800, 2400: unix.B2400, 4800: unix.B4800, 9600: unix.B9600, 19200: unix.B19200, 38400: unix.B38400, 57600: unix.B57600, 115200: unix.B115200, 230400: unix.B230400, 460800: unix.B460800, 921600: unix.B921600, } var databitsMap = map[int]uint32{ 0: unix.CS8, // Default to 8 bits 5: unix.CS5, 6: unix.CS6, 7: unix.CS7, 8: unix.CS8, } const tcCMSPAR uint32 = 0 // may be CMSPAR or PAREXT const tcIUCLC uint32 = 0 const tcCCTS_OFLOW uint32 = 0x00010000 const tcCRTS_IFLOW uint32 = 0x00020000 const tcCRTSCTS uint32 = tcCCTS_OFLOW const ioctlTcgetattr = unix.TIOCGETA const ioctlTcsetattr = unix.TIOCSETA const ioctlTcflsh = unix.TIOCFLUSH const ioctlTioccbrk = unix.TIOCCBRK const ioctlTiocsbrk = unix.TIOCSBRK func toTermiosSpeedType(speed uint32) uint32 { return speed } func setTermSettingsBaudrate(speed int, settings *unix.Termios) (error, bool) { baudrate, ok := baudrateMap[speed] if !ok { return nil, true } // XXX: Is Cflag really needed // revert old baudrate for _, rate := range baudrateMap { settings.Cflag &^= rate } // set new baudrate settings.Cflag |= baudrate settings.Ispeed = toTermiosSpeedType(baudrate) settings.Ospeed = toTermiosSpeedType(baudrate) return nil, false } func (port *unixPort) setSpecialBaudrate(speed uint32) error { // TODO: unimplemented return &PortError{code: InvalidSpeed} } go-serial-1.6.2/serial_linux.go000066400000000000000000000044201456366146100164430ustar00rootroot00000000000000// // Copyright 2014-2023 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 "golang.org/x/sys/unix" const devFolder = "/dev" const regexFilter = "(ttyS|ttyHS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO|ttymxc)[0-9]{1,3}" // termios manipulation functions var baudrateMap = map[int]uint32{ 0: unix.B9600, // Default to 9600 50: unix.B50, 75: unix.B75, 110: unix.B110, 134: unix.B134, 150: unix.B150, 200: unix.B200, 300: unix.B300, 600: unix.B600, 1200: unix.B1200, 1800: unix.B1800, 2400: unix.B2400, 4800: unix.B4800, 9600: unix.B9600, 19200: unix.B19200, 38400: unix.B38400, 57600: unix.B57600, 115200: unix.B115200, 230400: unix.B230400, 460800: unix.B460800, 500000: unix.B500000, 576000: unix.B576000, 921600: unix.B921600, 1000000: unix.B1000000, 1152000: unix.B1152000, 1500000: unix.B1500000, 2000000: unix.B2000000, 2500000: unix.B2500000, 3000000: unix.B3000000, 3500000: unix.B3500000, 4000000: unix.B4000000, } var databitsMap = map[int]uint32{ 0: unix.CS8, // Default to 8 bits 5: unix.CS5, 6: unix.CS6, 7: unix.CS7, 8: unix.CS8, } const tcCMSPAR = unix.CMSPAR const tcIUCLC = unix.IUCLC const tcCRTSCTS uint32 = unix.CRTSCTS const ioctlTcgetattr = unix.TCGETS const ioctlTcsetattr = unix.TCSETS const ioctlTcflsh = unix.TCFLSH const ioctlTioccbrk = unix.TIOCCBRK const ioctlTiocsbrk = unix.TIOCSBRK func toTermiosSpeedType(speed uint32) uint32 { return speed } func setTermSettingsBaudrate(speed int, settings *unix.Termios) (error, bool) { baudrate, ok := baudrateMap[speed] if !ok { return nil, true } // revert old baudrate for _, rate := range baudrateMap { settings.Cflag &^= rate } // set new baudrate settings.Cflag |= baudrate settings.Ispeed = toTermiosSpeedType(baudrate) settings.Ospeed = toTermiosSpeedType(baudrate) return nil, false } func (port *unixPort) Drain() error { // It's not super well documented, but this is the same as calling tcdrain: // - https://git.musl-libc.org/cgit/musl/tree/src/termios/tcdrain.c // - https://elixir.bootlin.com/linux/v6.2.8/source/drivers/tty/tty_io.c#L2673 return unix.IoctlSetInt(port.handle, unix.TCSBRK, 1) } go-serial-1.6.2/serial_linux_test.go000066400000000000000000000032321456366146100175020ustar00rootroot00000000000000// // Copyright 2014-2023 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. // // Testing code idea and fix thanks to @angri // https://github.com/bugst/go-serial/pull/42 package serial import ( "context" "os/exec" "testing" "time" "github.com/stretchr/testify/require" ) func startSocatAndWaitForPort(t *testing.T, ctx context.Context) *exec.Cmd { cmd := exec.CommandContext(ctx, "socat", "-D", "STDIO", "pty,link=/tmp/faketty") r, err := cmd.StderrPipe() require.NoError(t, err) require.NoError(t, cmd.Start()) // Let our fake serial port node appear. // socat will write to stderr before starting transfer phase; // we don't really care what, just that it did, because then it's ready. buf := make([]byte, 1024) _, err = r.Read(buf) require.NoError(t, err) return cmd } func TestSerialReadAndCloseConcurrency(t *testing.T) { // Run this test with race detector to actually test that // the correct multitasking behaviour is happening. ctx, cancel := context.WithCancel(context.Background()) defer cancel() cmd := startSocatAndWaitForPort(t, ctx) go cmd.Wait() port, err := Open("/tmp/faketty", &Mode{}) require.NoError(t, err) buf := make([]byte, 100) go port.Read(buf) // let port.Read to start time.Sleep(time.Millisecond * 1) port.Close() } func TestDoubleCloseIsNoop(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() cmd := startSocatAndWaitForPort(t, ctx) go cmd.Wait() port, err := Open("/tmp/faketty", &Mode{}) require.NoError(t, err) require.NoError(t, port.Close()) require.NoError(t, port.Close()) } go-serial-1.6.2/serial_openbsd.go000066400000000000000000000036521456366146100167440ustar00rootroot00000000000000// // Copyright 2014-2023 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 "golang.org/x/sys/unix" const devFolder = "/dev" const regexFilter = "^(cu|tty)\\..*" // termios manipulation functions var baudrateMap = map[int]uint32{ 0: unix.B9600, // Default to 9600 50: unix.B50, 75: unix.B75, 110: unix.B110, 134: unix.B134, 150: unix.B150, 200: unix.B200, 300: unix.B300, 600: unix.B600, 1200: unix.B1200, 1800: unix.B1800, 2400: unix.B2400, 4800: unix.B4800, 9600: unix.B9600, 19200: unix.B19200, 38400: unix.B38400, 57600: unix.B57600, 115200: unix.B115200, 230400: unix.B230400, //460800: unix.B460800, //921600: unix.B921600, } var databitsMap = map[int]uint32{ 0: unix.CS8, // Default to 8 bits 5: unix.CS5, 6: unix.CS6, 7: unix.CS7, 8: unix.CS8, } const tcCMSPAR uint32 = 0 // may be CMSPAR or PAREXT const tcIUCLC uint32 = 0 const tcCCTS_OFLOW uint32 = 0x00010000 const tcCRTS_IFLOW uint32 = 0x00020000 const tcCRTSCTS uint32 = tcCCTS_OFLOW const ioctlTcgetattr = unix.TIOCGETA const ioctlTcsetattr = unix.TIOCSETA const ioctlTcflsh = unix.TIOCFLUSH const ioctlTioccbrk = unix.TIOCCBRK const ioctlTiocsbrk = unix.TIOCSBRK func toTermiosSpeedType(speed uint32) int32 { return int32(speed) } func setTermSettingsBaudrate(speed int, settings *unix.Termios) (error, bool) { baudrate, ok := baudrateMap[speed] if !ok { return nil, true } // XXX: Is Cflag really needed // revert old baudrate for _, rate := range baudrateMap { settings.Cflag &^= rate } // set new baudrate settings.Cflag |= baudrate settings.Ispeed = toTermiosSpeedType(baudrate) settings.Ospeed = toTermiosSpeedType(baudrate) return nil, false } func (port *unixPort) setSpecialBaudrate(speed uint32) error { // TODO: unimplemented return &PortError{code: InvalidSpeed} } go-serial-1.6.2/serial_resetbuf_linux_bsd.go000066400000000000000000000007601456366146100211750ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build linux || freebsd || openbsd package serial import "golang.org/x/sys/unix" func (port *unixPort) ResetInputBuffer() error { return unix.IoctlSetInt(port.handle, ioctlTcflsh, unix.TCIFLUSH) } func (port *unixPort) ResetOutputBuffer() error { return unix.IoctlSetInt(port.handle, ioctlTcflsh, unix.TCOFLUSH) } go-serial-1.6.2/serial_specialbaudrate_linux.go000066400000000000000000000011201456366146100216450ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build linux && !ppc64le package serial import "golang.org/x/sys/unix" func (port *unixPort) setSpecialBaudrate(speed uint32) error { settings, err := unix.IoctlGetTermios(port.handle, unix.TCGETS2) if err != nil { return err } settings.Cflag &^= unix.CBAUD settings.Cflag |= unix.BOTHER settings.Ispeed = speed settings.Ospeed = speed return unix.IoctlSetTermios(port.handle, unix.TCSETS2, settings) } go-serial-1.6.2/serial_specialbaudrate_linux_ppc64le.go000066400000000000000000000004741456366146100232150ustar00rootroot00000000000000// // Copyright 2014-2023 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 func (port *unixPort) setSpecialBaudrate(speed uint32) error { // TODO: unimplemented return &PortError{code: InvalidSpeed} } go-serial-1.6.2/serial_unix.go000066400000000000000000000263071456366146100162770ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build linux || darwin || freebsd || openbsd package serial import ( "fmt" "io/ioutil" "regexp" "strings" "sync" "sync/atomic" "time" "go.bug.st/serial/unixutils" "golang.org/x/sys/unix" ) type unixPort struct { handle int readTimeout time.Duration closeLock sync.RWMutex closeSignal *unixutils.Pipe opened uint32 } func (port *unixPort) Close() error { if !atomic.CompareAndSwapUint32(&port.opened, 1, 0) { return nil } // Close port port.releaseExclusiveAccess() if err := unix.Close(port.handle); err != nil { return err } if port.closeSignal != nil { // Send close signal to all pending reads (if any) port.closeSignal.Write([]byte{0}) // Wait for all readers to complete port.closeLock.Lock() defer port.closeLock.Unlock() // Close signaling pipe if err := port.closeSignal.Close(); err != nil { return err } } return nil } func (port *unixPort) Read(p []byte) (int, error) { port.closeLock.RLock() defer port.closeLock.RUnlock() if atomic.LoadUint32(&port.opened) != 1 { return 0, &PortError{code: PortClosed} } var deadline time.Time if port.readTimeout != NoTimeout { deadline = time.Now().Add(port.readTimeout) } fds := unixutils.NewFDSet(port.handle, port.closeSignal.ReadFD()) for { timeout := time.Duration(-1) if port.readTimeout != NoTimeout { timeout = time.Until(deadline) if timeout < 0 { // a negative timeout means "no-timeout" in Select(...) timeout = 0 } } res, err := unixutils.Select(fds, nil, fds, timeout) if err == unix.EINTR { continue } if err != nil { return 0, err } if res.IsReadable(port.closeSignal.ReadFD()) { return 0, &PortError{code: PortClosed} } if !res.IsReadable(port.handle) { // Timeout happened return 0, nil } n, err := unix.Read(port.handle, p) if err == unix.EINTR { continue } // Linux: when the port is disconnected during a read operation // the port is left in a "readable with zero-length-data" state. // https://stackoverflow.com/a/34945814/1655275 if n == 0 && err == nil { return 0, &PortError{code: PortClosed} } if n < 0 { // Do not return -1 unix errors n = 0 } return n, err } } func (port *unixPort) Write(p []byte) (n int, err error) { n, err = unix.Write(port.handle, p) if n < 0 { // Do not return -1 unix errors n = 0 } return } func (port *unixPort) Break(t time.Duration) error { if err := unix.IoctlSetInt(port.handle, ioctlTiocsbrk, 0); err != nil { return err } time.Sleep(t) if err := unix.IoctlSetInt(port.handle, ioctlTioccbrk, 0); err != nil { return err } return nil } func (port *unixPort) SetMode(mode *Mode) error { settings, err := port.getTermSettings() if err != nil { return err } if err := setTermSettingsParity(mode.Parity, settings); err != nil { return err } if err := setTermSettingsDataBits(mode.DataBits, settings); err != nil { return err } if err := setTermSettingsStopBits(mode.StopBits, settings); err != nil { return err } requireSpecialBaudrate := false if err, special := setTermSettingsBaudrate(mode.BaudRate, settings); err != nil { return err } else if special { requireSpecialBaudrate = true } if err := port.setTermSettings(settings); err != nil { return err } if requireSpecialBaudrate { // MacOSX require this one to be the last operation otherwise an // 'Invalid serial port' error is produced. if err := port.setSpecialBaudrate(uint32(mode.BaudRate)); err != nil { return err } } return nil } func (port *unixPort) SetDTR(dtr bool) error { status, err := port.getModemBitsStatus() if err != nil { return err } if dtr { status |= unix.TIOCM_DTR } else { status &^= unix.TIOCM_DTR } return port.setModemBitsStatus(status) } func (port *unixPort) SetRTS(rts bool) error { status, err := port.getModemBitsStatus() if err != nil { return err } if rts { status |= unix.TIOCM_RTS } else { status &^= unix.TIOCM_RTS } return port.setModemBitsStatus(status) } func (port *unixPort) SetReadTimeout(timeout time.Duration) error { if timeout < 0 && timeout != NoTimeout { return &PortError{code: InvalidTimeoutValue} } port.readTimeout = timeout return nil } func (port *unixPort) GetModemStatusBits() (*ModemStatusBits, error) { status, err := port.getModemBitsStatus() if err != nil { return nil, err } return &ModemStatusBits{ CTS: (status & unix.TIOCM_CTS) != 0, DCD: (status & unix.TIOCM_CD) != 0, DSR: (status & unix.TIOCM_DSR) != 0, RI: (status & unix.TIOCM_RI) != 0, }, nil } func nativeOpen(portName string, mode *Mode) (*unixPort, error) { h, err := unix.Open(portName, unix.O_RDWR|unix.O_NOCTTY|unix.O_NDELAY, 0) if err != nil { switch err { case unix.EBUSY: return nil, &PortError{code: PortBusy} case unix.EACCES: return nil, &PortError{code: PermissionDenied} } return nil, err } port := &unixPort{ handle: h, opened: 1, readTimeout: NoTimeout, } // Setup serial port settings, err := port.getTermSettings() if err != nil { port.Close() return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error getting term settings: %w", err)} } // Set raw mode setRawMode(settings) // Explicitly disable RTS/CTS flow control setTermSettingsCtsRts(false, settings) if port.setTermSettings(settings) != nil { port.Close() return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error setting term settings: %w", err)} } if mode.InitialStatusBits != nil { status, err := port.getModemBitsStatus() if err != nil { port.Close() return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error getting modem bits status: %w", err)} } if mode.InitialStatusBits.DTR { status |= unix.TIOCM_DTR } else { status &^= unix.TIOCM_DTR } if mode.InitialStatusBits.RTS { status |= unix.TIOCM_RTS } else { status &^= unix.TIOCM_RTS } if err := port.setModemBitsStatus(status); err != nil { port.Close() return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error setting modem bits status: %w", err)} } } // MacOSX require that this operation is the last one otherwise an // 'Invalid serial port' error is returned... don't know why... if err := port.SetMode(mode); err != nil { port.Close() return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error configuring port: %w", err)} } unix.SetNonblock(h, false) port.acquireExclusiveAccess() // This pipe is used as a signal to cancel blocking Read pipe := &unixutils.Pipe{} if err := pipe.Open(); err != nil { port.Close() return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error opening signaling pipe: %w", err)} } port.closeSignal = pipe return port, nil } func nativeGetPortsList() ([]string, error) { files, err := ioutil.ReadDir(devFolder) if err != nil { return nil, err } ports := make([]string, 0, len(files)) regex, err := regexp.Compile(regexFilter) if err != nil { return nil, err } for _, f := range files { // Skip folders if f.IsDir() { continue } // Keep only devices with the correct name if !regex.MatchString(f.Name()) { continue } portName := devFolder + "/" + f.Name() // Check if serial port is real or is a placeholder serial port "ttySxx" or "ttyHSxx" if strings.HasPrefix(f.Name(), "ttyS") || strings.HasPrefix(f.Name(), "ttyHS") { port, err := nativeOpen(portName, &Mode{}) if err != nil { continue } else { port.Close() } } // Save serial port in the resulting list ports = append(ports, portName) } return ports, nil } // termios manipulation functions func setTermSettingsParity(parity Parity, settings *unix.Termios) error { switch parity { case NoParity: settings.Cflag &^= unix.PARENB settings.Cflag &^= unix.PARODD settings.Cflag &^= tcCMSPAR settings.Iflag &^= unix.INPCK case OddParity: settings.Cflag |= unix.PARENB settings.Cflag |= unix.PARODD settings.Cflag &^= tcCMSPAR settings.Iflag |= unix.INPCK case EvenParity: settings.Cflag |= unix.PARENB settings.Cflag &^= unix.PARODD settings.Cflag &^= tcCMSPAR settings.Iflag |= unix.INPCK case MarkParity: if tcCMSPAR == 0 { return &PortError{code: InvalidParity} } settings.Cflag |= unix.PARENB settings.Cflag |= unix.PARODD settings.Cflag |= tcCMSPAR settings.Iflag |= unix.INPCK case SpaceParity: if tcCMSPAR == 0 { return &PortError{code: InvalidParity} } settings.Cflag |= unix.PARENB settings.Cflag &^= unix.PARODD settings.Cflag |= tcCMSPAR settings.Iflag |= unix.INPCK default: return &PortError{code: InvalidParity} } return nil } func setTermSettingsDataBits(bits int, settings *unix.Termios) error { databits, ok := databitsMap[bits] if !ok { return &PortError{code: InvalidDataBits} } // Remove previous databits setting settings.Cflag &^= unix.CSIZE // Set requested databits settings.Cflag |= databits return nil } func setTermSettingsStopBits(bits StopBits, settings *unix.Termios) error { switch bits { case OneStopBit: settings.Cflag &^= unix.CSTOPB case OnePointFiveStopBits: return &PortError{code: InvalidStopBits} case TwoStopBits: settings.Cflag |= unix.CSTOPB default: return &PortError{code: InvalidStopBits} } return nil } func setTermSettingsCtsRts(enable bool, settings *unix.Termios) { if enable { settings.Cflag |= tcCRTSCTS } else { settings.Cflag &^= tcCRTSCTS } } func setRawMode(settings *unix.Termios) { // Set local mode settings.Cflag |= unix.CREAD settings.Cflag |= unix.CLOCAL // Set raw mode settings.Lflag &^= unix.ICANON settings.Lflag &^= unix.ECHO settings.Lflag &^= unix.ECHOE settings.Lflag &^= unix.ECHOK settings.Lflag &^= unix.ECHONL settings.Lflag &^= unix.ECHOCTL settings.Lflag &^= unix.ECHOPRT settings.Lflag &^= unix.ECHOKE settings.Lflag &^= unix.ISIG settings.Lflag &^= unix.IEXTEN settings.Iflag &^= unix.IXON settings.Iflag &^= unix.IXOFF settings.Iflag &^= unix.IXANY settings.Iflag &^= unix.INPCK settings.Iflag &^= unix.IGNPAR settings.Iflag &^= unix.PARMRK settings.Iflag &^= unix.ISTRIP settings.Iflag &^= unix.IGNBRK settings.Iflag &^= unix.BRKINT settings.Iflag &^= unix.INLCR settings.Iflag &^= unix.IGNCR settings.Iflag &^= unix.ICRNL settings.Iflag &^= tcIUCLC settings.Oflag &^= unix.OPOST // Block reads until at least one char is available (no timeout) settings.Cc[unix.VMIN] = 1 settings.Cc[unix.VTIME] = 0 } // native syscall wrapper functions func (port *unixPort) getTermSettings() (*unix.Termios, error) { return unix.IoctlGetTermios(port.handle, ioctlTcgetattr) } func (port *unixPort) setTermSettings(settings *unix.Termios) error { return unix.IoctlSetTermios(port.handle, ioctlTcsetattr, settings) } func (port *unixPort) getModemBitsStatus() (int, error) { return unix.IoctlGetInt(port.handle, unix.TIOCMGET) } func (port *unixPort) setModemBitsStatus(status int) error { return unix.IoctlSetPointerInt(port.handle, unix.TIOCMSET, status) } func (port *unixPort) acquireExclusiveAccess() error { return unix.IoctlSetInt(port.handle, unix.TIOCEXCL, 0) } func (port *unixPort) releaseExclusiveAccess() error { return unix.IoctlSetInt(port.handle, unix.TIOCNXCL, 0) } go-serial-1.6.2/serial_windows.go000066400000000000000000000273131456366146100170040ustar00rootroot00000000000000// // Copyright 2014-2023 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 /* // MSDN article on Serial Communications: // http://msdn.microsoft.com/en-us/library/ff802693.aspx // (alternative link) https://msdn.microsoft.com/en-us/library/ms810467.aspx // Arduino Playground article on serial communication with Windows API: // http://playground.arduino.cc/Interfacing/CPPWindows */ import ( "sync" "syscall" "time" ) type windowsPort struct { mu sync.Mutex handle syscall.Handle } func nativeGetPortsList() ([]string, error) { subKey, err := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM\\") if err != nil { return nil, &PortError{code: ErrorEnumeratingPorts} } var h syscall.Handle if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, subKey, 0, syscall.KEY_READ, &h); err != nil { if errno, isErrno := err.(syscall.Errno); isErrno && errno == syscall.ERROR_FILE_NOT_FOUND { return []string{}, nil } return nil, &PortError{code: ErrorEnumeratingPorts} } defer syscall.RegCloseKey(h) var valuesCount uint32 if syscall.RegQueryInfoKey(h, nil, nil, nil, nil, nil, nil, &valuesCount, nil, nil, nil, nil) != nil { return nil, &PortError{code: ErrorEnumeratingPorts} } list := make([]string, valuesCount) for i := range list { var data [1024]uint16 dataSize := uint32(len(data)) var name [1024]uint16 nameSize := uint32(len(name)) if regEnumValue(h, uint32(i), &name[0], &nameSize, nil, nil, &data[0], &dataSize) != nil { return nil, &PortError{code: ErrorEnumeratingPorts} } list[i] = syscall.UTF16ToString(data[:]) } return list, nil } func (port *windowsPort) Close() error { port.mu.Lock() defer func() { port.handle = 0 port.mu.Unlock() }() if port.handle == 0 { return nil } return syscall.CloseHandle(port.handle) } func (port *windowsPort) Read(p []byte) (int, error) { var readed uint32 ev, err := createOverlappedEvent() if err != nil { return 0, err } defer syscall.CloseHandle(ev.HEvent) err = syscall.ReadFile(port.handle, p, &readed, ev) if err == syscall.ERROR_IO_PENDING { err = getOverlappedResult(port.handle, ev, &readed, true) } switch err { case nil: // operation completed successfully case syscall.ERROR_OPERATION_ABORTED: // port may have been closed return int(readed), &PortError{code: PortClosed, causedBy: err} default: // error happened return int(readed), err } if readed > 0 { return int(readed), nil } // Timeout return 0, nil } func (port *windowsPort) Write(p []byte) (int, error) { var writed uint32 ev, err := createOverlappedEvent() if err != nil { return 0, err } defer syscall.CloseHandle(ev.HEvent) err = syscall.WriteFile(port.handle, p, &writed, ev) if err == syscall.ERROR_IO_PENDING { // wait for write to complete err = getOverlappedResult(port.handle, ev, &writed, true) } return int(writed), err } func (port *windowsPort) Drain() (err error) { return syscall.FlushFileBuffers(port.handle) } const ( purgeRxAbort uint32 = 0x0002 purgeRxClear = 0x0008 purgeTxAbort = 0x0001 purgeTxClear = 0x0004 ) func (port *windowsPort) ResetInputBuffer() error { return purgeComm(port.handle, purgeRxClear|purgeRxAbort) } func (port *windowsPort) ResetOutputBuffer() error { return purgeComm(port.handle, purgeTxClear|purgeTxAbort) } const ( dcbBinary uint32 = 0x00000001 dcbParity = 0x00000002 dcbOutXCTSFlow = 0x00000004 dcbOutXDSRFlow = 0x00000008 dcbDTRControlDisableMask = ^uint32(0x00000030) dcbDTRControlEnable = 0x00000010 dcbDTRControlHandshake = 0x00000020 dcbDSRSensitivity = 0x00000040 dcbTXContinueOnXOFF = 0x00000080 dcbOutX = 0x00000100 dcbInX = 0x00000200 dcbErrorChar = 0x00000400 dcbNull = 0x00000800 dcbRTSControlDisbaleMask = ^uint32(0x00003000) dcbRTSControlEnable = 0x00001000 dcbRTSControlHandshake = 0x00002000 dcbRTSControlToggle = 0x00003000 dcbAbortOnError = 0x00004000 ) type dcb struct { DCBlength uint32 BaudRate uint32 // Flags field is a bitfield // fBinary :1 // fParity :1 // fOutxCtsFlow :1 // fOutxDsrFlow :1 // fDtrControl :2 // fDsrSensitivity :1 // fTXContinueOnXoff :1 // fOutX :1 // fInX :1 // fErrorChar :1 // fNull :1 // fRtsControl :2 // fAbortOnError :1 // fDummy2 :17 Flags uint32 wReserved uint16 XonLim uint16 XoffLim uint16 ByteSize byte Parity byte StopBits byte XonChar byte XoffChar byte ErrorChar byte EOFChar byte EvtChar byte wReserved1 uint16 } type commTimeouts struct { ReadIntervalTimeout uint32 ReadTotalTimeoutMultiplier uint32 ReadTotalTimeoutConstant uint32 WriteTotalTimeoutMultiplier uint32 WriteTotalTimeoutConstant uint32 } const ( noParity = 0 oddParity = 1 evenParity = 2 markParity = 3 spaceParity = 4 ) var parityMap = map[Parity]byte{ NoParity: noParity, OddParity: oddParity, EvenParity: evenParity, MarkParity: markParity, SpaceParity: spaceParity, } const ( oneStopBit = 0 one5StopBits = 1 twoStopBits = 2 ) var stopBitsMap = map[StopBits]byte{ OneStopBit: oneStopBit, OnePointFiveStopBits: one5StopBits, TwoStopBits: twoStopBits, } const ( commFunctionSetXOFF = 1 commFunctionSetXON = 2 commFunctionSetRTS = 3 commFunctionClrRTS = 4 commFunctionSetDTR = 5 commFunctionClrDTR = 6 commFunctionSetBreak = 8 commFunctionClrBreak = 9 ) const ( msCTSOn = 0x0010 msDSROn = 0x0020 msRingOn = 0x0040 msRLSDOn = 0x0080 ) func (port *windowsPort) SetMode(mode *Mode) error { params := dcb{} if getCommState(port.handle, ¶ms) != nil { port.Close() return &PortError{code: InvalidSerialPort} } port.setModeParams(mode, ¶ms) if setCommState(port.handle, ¶ms) != nil { port.Close() return &PortError{code: InvalidSerialPort} } return nil } func (port *windowsPort) setModeParams(mode *Mode, params *dcb) { if mode.BaudRate == 0 { params.BaudRate = 9600 // Default to 9600 } else { params.BaudRate = uint32(mode.BaudRate) } if mode.DataBits == 0 { params.ByteSize = 8 // Default to 8 bits } else { params.ByteSize = byte(mode.DataBits) } params.StopBits = stopBitsMap[mode.StopBits] params.Parity = parityMap[mode.Parity] } func (port *windowsPort) SetDTR(dtr bool) error { // Like for RTS there are problems with the escapeCommFunction // observed behaviour was that DTR is set from false -> true // when setting RTS from true -> false // 1) Connect -> RTS = true (low) DTR = true (low) OKAY // 2) SetDTR(false) -> RTS = true (low) DTR = false (heigh) OKAY // 3) SetRTS(false) -> RTS = false (heigh) DTR = true (low) ERROR: DTR toggled // // In addition this way the CommState Flags are not updated /* var res bool if dtr { res = escapeCommFunction(port.handle, commFunctionSetDTR) } else { res = escapeCommFunction(port.handle, commFunctionClrDTR) } if !res { return &PortError{} } return nil */ // The following seems a more reliable way to do it params := &dcb{} if err := getCommState(port.handle, params); err != nil { return &PortError{causedBy: err} } params.Flags &= dcbDTRControlDisableMask if dtr { params.Flags |= dcbDTRControlEnable } if err := setCommState(port.handle, params); err != nil { return &PortError{causedBy: err} } return nil } func (port *windowsPort) SetRTS(rts bool) error { // It seems that there is a bug in the Windows VCP driver: // it doesn't send USB control message when the RTS bit is // changed, so the following code not always works with // USB-to-serial adapters. // // In addition this way the CommState Flags are not updated /* var res bool if rts { res = escapeCommFunction(port.handle, commFunctionSetRTS) } else { res = escapeCommFunction(port.handle, commFunctionClrRTS) } if !res { return &PortError{} } return nil */ // The following seems a more reliable way to do it params := &dcb{} if err := getCommState(port.handle, params); err != nil { return &PortError{causedBy: err} } params.Flags &= dcbRTSControlDisbaleMask if rts { params.Flags |= dcbRTSControlEnable } if err := setCommState(port.handle, params); err != nil { return &PortError{causedBy: err} } return nil } func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) { var bits uint32 if !getCommModemStatus(port.handle, &bits) { return nil, &PortError{} } return &ModemStatusBits{ CTS: (bits & msCTSOn) != 0, DCD: (bits & msRLSDOn) != 0, DSR: (bits & msDSROn) != 0, RI: (bits & msRingOn) != 0, }, nil } func (port *windowsPort) SetReadTimeout(timeout time.Duration) error { commTimeouts := &commTimeouts{ ReadIntervalTimeout: 0xFFFFFFFF, ReadTotalTimeoutMultiplier: 0xFFFFFFFF, ReadTotalTimeoutConstant: 0xFFFFFFFE, WriteTotalTimeoutConstant: 0, WriteTotalTimeoutMultiplier: 0, } if timeout != NoTimeout { ms := timeout.Milliseconds() if ms > 0xFFFFFFFE || ms < 0 { return &PortError{code: InvalidTimeoutValue} } commTimeouts.ReadTotalTimeoutConstant = uint32(ms) } if err := setCommTimeouts(port.handle, commTimeouts); err != nil { return &PortError{code: InvalidTimeoutValue, causedBy: err} } return nil } func (port *windowsPort) Break(d time.Duration) error { if err := setCommBreak(port.handle); err != nil { return &PortError{causedBy: err} } time.Sleep(d) if err := clearCommBreak(port.handle); err != nil { return &PortError{causedBy: err} } return nil } func createOverlappedEvent() (*syscall.Overlapped, error) { h, err := createEvent(nil, true, false, nil) return &syscall.Overlapped{HEvent: h}, err } func nativeOpen(portName string, mode *Mode) (*windowsPort, error) { portName = "\\\\.\\" + portName path, err := syscall.UTF16PtrFromString(portName) if err != nil { return nil, err } handle, err := syscall.CreateFile( path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED, 0) if err != nil { switch err { case syscall.ERROR_ACCESS_DENIED: return nil, &PortError{code: PortBusy} case syscall.ERROR_FILE_NOT_FOUND: return nil, &PortError{code: PortNotFound} } return nil, err } // Create the serial port port := &windowsPort{ handle: handle, } // Set port parameters params := &dcb{} if getCommState(port.handle, params) != nil { port.Close() return nil, &PortError{code: InvalidSerialPort} } port.setModeParams(mode, params) params.Flags &= dcbDTRControlDisableMask params.Flags &= dcbRTSControlDisbaleMask if mode.InitialStatusBits == nil { params.Flags |= dcbDTRControlEnable params.Flags |= dcbRTSControlEnable } else { if mode.InitialStatusBits.DTR { params.Flags |= dcbDTRControlEnable } if mode.InitialStatusBits.RTS { params.Flags |= dcbRTSControlEnable } } params.Flags &^= dcbOutXCTSFlow params.Flags &^= dcbOutXDSRFlow params.Flags &^= dcbDSRSensitivity params.Flags |= dcbTXContinueOnXOFF params.Flags &^= dcbInX params.Flags &^= dcbOutX params.Flags &^= dcbErrorChar params.Flags &^= dcbNull params.Flags &^= dcbAbortOnError params.XonLim = 2048 params.XoffLim = 512 params.XonChar = 17 // DC1 params.XoffChar = 19 // C3 if setCommState(port.handle, params) != nil { port.Close() return nil, &PortError{code: InvalidSerialPort} } if port.SetReadTimeout(NoTimeout) != nil { port.Close() return nil, &PortError{code: InvalidSerialPort} } return port, nil } go-serial-1.6.2/syscall_windows.go000066400000000000000000000025761456366146100172030ustar00rootroot00000000000000// // Copyright 2014-2023 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 //sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) = advapi32.RegEnumValueW //sys getCommState(handle syscall.Handle, dcb *dcb) (err error) = GetCommState //sys setCommState(handle syscall.Handle, dcb *dcb) (err error) = SetCommState //sys setCommTimeouts(handle syscall.Handle, timeouts *commTimeouts) (err error) = SetCommTimeouts //sys escapeCommFunction(handle syscall.Handle, function uint32) (res bool) = EscapeCommFunction //sys getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) = GetCommModemStatus //sys createEvent(eventAttributes *uint32, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) = CreateEventW //sys resetEvent(handle syscall.Handle) (err error) = ResetEvent //sys getOverlappedResult(handle syscall.Handle, overlapEvent *syscall.Overlapped, n *uint32, wait bool) (err error) = GetOverlappedResult //sys purgeComm(handle syscall.Handle, flags uint32) (err error) = PurgeComm //sys setCommBreak(handle syscall.Handle) (err error) = SetCommBreak //sys clearCommBreak(handle syscall.Handle) (err error) = ClearCommBreak go-serial-1.6.2/unixutils/000077500000000000000000000000001456366146100154625ustar00rootroot00000000000000go-serial-1.6.2/unixutils/pipe.go000066400000000000000000000030311456366146100167430ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build linux || darwin || freebsd || openbsd package unixutils import ( "fmt" "syscall" ) // Pipe represents a unix-pipe type Pipe struct { opened bool rd int wr int } // Open creates a new pipe func (p *Pipe) Open() error { fds := []int{0, 0} if err := syscall.Pipe(fds); err != nil { return err } p.rd = fds[0] p.wr = fds[1] p.opened = true return nil } // ReadFD returns the file handle for the read side of the pipe. func (p *Pipe) ReadFD() int { if !p.opened { return -1 } return p.rd } // WriteFD returns the flie handle for the write side of the pipe. func (p *Pipe) WriteFD() int { if !p.opened { return -1 } return p.wr } // Write to the pipe the content of data. Returns the numbre of bytes written. func (p *Pipe) Write(data []byte) (int, error) { if !p.opened { return 0, fmt.Errorf("Pipe not opened") } return syscall.Write(p.wr, data) } // Read from the pipe into the data array. Returns the number of bytes read. func (p *Pipe) Read(data []byte) (int, error) { if !p.opened { return 0, fmt.Errorf("Pipe not opened") } return syscall.Read(p.rd, data) } // Close the pipe func (p *Pipe) Close() error { if !p.opened { return fmt.Errorf("Pipe not opened") } err1 := syscall.Close(p.rd) err2 := syscall.Close(p.wr) p.opened = false if err1 != nil { return err1 } if err2 != nil { return err2 } return nil } go-serial-1.6.2/unixutils/select.go000066400000000000000000000050311456366146100172670ustar00rootroot00000000000000// // Copyright 2014-2023 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. // //go:build linux || darwin || freebsd || openbsd package unixutils import ( "time" "github.com/creack/goselect" ) // FDSet is a set of file descriptors suitable for a select call type FDSet struct { set goselect.FDSet max uintptr } // NewFDSet creates a set of file descriptors suitable for a Select call. func NewFDSet(fds ...int) *FDSet { s := &FDSet{} s.Add(fds...) return s } // Add adds the file descriptors passed as parameter to the FDSet. func (s *FDSet) Add(fds ...int) { for _, fd := range fds { f := uintptr(fd) s.set.Set(f) if f > s.max { s.max = f } } } // FDResultSets contains the result of a Select operation. type FDResultSets struct { readable *goselect.FDSet writeable *goselect.FDSet errors *goselect.FDSet } // IsReadable test if a file descriptor is ready to be read. func (r *FDResultSets) IsReadable(fd int) bool { return r.readable.IsSet(uintptr(fd)) } // IsWritable test if a file descriptor is ready to be written. func (r *FDResultSets) IsWritable(fd int) bool { return r.writeable.IsSet(uintptr(fd)) } // IsError test if a file descriptor is in error state. func (r *FDResultSets) IsError(fd int) bool { return r.errors.IsSet(uintptr(fd)) } // Select performs a select system call, // file descriptors in the rd set are tested for read-events, // file descriptors in the wd set are tested for write-events and // file descriptors in the er set are tested for error-events. // The function will block until an event happens or the timeout expires. // The function return an FDResultSets that contains all the file descriptor // that have a pending read/write/error event. func Select(rd, wr, er *FDSet, timeout time.Duration) (*FDResultSets, error) { max := uintptr(0) res := &FDResultSets{} if rd != nil { // fdsets are copied so the parameters are left untouched copyOfRd := rd.set res.readable = ©OfRd // Determine max fd. max = rd.max } if wr != nil { // fdsets are copied so the parameters are left untouched copyOfWr := wr.set res.writeable = ©OfWr // Determine max fd. if wr.max > max { max = wr.max } } if er != nil { // fdsets are copied so the parameters are left untouched copyOfEr := er.set res.errors = ©OfEr // Determine max fd. if er.max > max { max = er.max } } err := goselect.Select(int(max+1), res.readable, res.writeable, res.errors, timeout) return res, err } go-serial-1.6.2/zsyscall_windows.go000066400000000000000000000114311456366146100173630ustar00rootroot00000000000000// Code generated by 'go generate'; DO NOT EDIT. package serial import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } // TODO: add more here, after collecting data on the common // error values see on Windows. (perhaps when running // all.bat?) return e } var ( modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll") procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") procClearCommBreak = modkernel32.NewProc("ClearCommBreak") procCreateEventW = modkernel32.NewProc("CreateEventW") procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") procGetCommState = modkernel32.NewProc("GetCommState") procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult") procPurgeComm = modkernel32.NewProc("PurgeComm") procResetEvent = modkernel32.NewProc("ResetEvent") procSetCommBreak = modkernel32.NewProc("SetCommBreak") procSetCommState = modkernel32.NewProc("SetCommState") procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") ) func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) { r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(value)), uintptr(unsafe.Pointer(valueLen)), 0) if r0 != 0 { regerrno = syscall.Errno(r0) } return } func clearCommBreak(handle syscall.Handle) (err error) { r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0) if r1 == 0 { err = errnoErr(e1) } return } func createEvent(eventAttributes *uint32, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) { var _p0 uint32 if manualReset { _p0 = 1 } var _p1 uint32 if initialState { _p1 = 1 } r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(eventAttributes)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0) handle = syscall.Handle(r0) if handle == 0 { err = errnoErr(e1) } return } func escapeCommFunction(handle syscall.Handle, function uint32) (res bool) { r0, _, _ := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(function), 0) res = r0 != 0 return } func getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) { r0, _, _ := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(bits)), 0) res = r0 != 0 return } func getCommState(handle syscall.Handle, dcb *dcb) (err error) { r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0) if r1 == 0 { err = errnoErr(e1) } return } func getOverlappedResult(handle syscall.Handle, overlapEvent *syscall.Overlapped, n *uint32, wait bool) (err error) { var _p0 uint32 if wait { _p0 = 1 } r1, _, e1 := syscall.Syscall6(procGetOverlappedResult.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(overlapEvent)), uintptr(unsafe.Pointer(n)), uintptr(_p0), 0, 0) if r1 == 0 { err = errnoErr(e1) } return } func purgeComm(handle syscall.Handle, flags uint32) (err error) { r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(flags), 0) if r1 == 0 { err = errnoErr(e1) } return } func resetEvent(handle syscall.Handle) (err error) { r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(handle), 0, 0) if r1 == 0 { err = errnoErr(e1) } return } func setCommBreak(handle syscall.Handle) (err error) { r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0) if r1 == 0 { err = errnoErr(e1) } return } func setCommState(handle syscall.Handle, dcb *dcb) (err error) { r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0) if r1 == 0 { err = errnoErr(e1) } return } func setCommTimeouts(handle syscall.Handle, timeouts *commTimeouts) (err error) { r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) if r1 == 0 { err = errnoErr(e1) } return }