diff options
author | xengineering <me@xengineering.eu> | 2024-05-30 16:00:32 +0200 |
---|---|---|
committer | xengineering <me@xengineering.eu> | 2024-05-30 16:00:32 +0200 |
commit | b8ef4d11fe0d00ce0884ccf982675845b20c3ce9 (patch) | |
tree | 3beb57ff849ed74569ce325225bc819791c25a6a /software/vendor/go.bug.st/serial/serial_windows.go | |
parent | eab833271eeaa8d54991c11eccec9445f662a191 (diff) | |
download | iot-core-b8ef4d11fe0d00ce0884ccf982675845b20c3ce9.tar iot-core-b8ef4d11fe0d00ce0884ccf982675845b20c3ce9.tar.zst iot-core-b8ef4d11fe0d00ce0884ccf982675845b20c3ce9.zip |
software: Implement serial port detection
Diffstat (limited to 'software/vendor/go.bug.st/serial/serial_windows.go')
-rw-r--r-- | software/vendor/go.bug.st/serial/serial_windows.go | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/software/vendor/go.bug.st/serial/serial_windows.go b/software/vendor/go.bug.st/serial/serial_windows.go new file mode 100644 index 0000000..35bb0c9 --- /dev/null +++ b/software/vendor/go.bug.st/serial/serial_windows.go @@ -0,0 +1,481 @@ +// +// 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 +} |