├── .editorconfig ├── .github └── workflows │ ├── codeql-analysis.yml │ └── golang.yml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── constants_darwin.go ├── constants_freebsd.go ├── constants_linux.go ├── constants_openbsd.go ├── doc.go ├── errors.go ├── example_getportlist_test.go ├── example_modem_bits_test.go ├── example_serialport_test.go ├── example_test.go ├── go.mod ├── go.sum ├── options.go ├── serial.go ├── serial_open_android.go ├── serial_open_unix.go ├── serial_termsettings_android.go ├── serial_termsettings_bsd.go ├── serial_termsettings_darwin.go ├── serial_termsettings_linux.go ├── serial_termsettings_linux_ppc64le.go ├── serial_test.go ├── serial_unix.go ├── serial_windows.go ├── syscall_windows.go ├── termios_baudrate_bsd.go ├── termios_baudrate_darwin.go ├── termios_baudrate_linux.go ├── termios_setting_darwin.go ├── termios_setting_unix.go ├── termios_unix.go ├── unixutils └── select_unix.go └── zsyscall_windows.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | 8 | [*.go] 9 | indent_style = tab 10 | indent_size = tab 11 | ij_go_add_parentheses_for_single_import = true 12 | ij_go_remove_redundant_import_aliases = true 13 | ij_go_import_sorting = goimports 14 | ij_go_move_all_imports_in_one_declaration = true 15 | ij_go_group_stdlib_imports = true 16 | ij_go_move_all_stdlib_imports_in_one_group = true 17 | ij_go_local_group_mode = project 18 | ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true 19 | 20 | [*.{yml,yaml}] 21 | indent_style = space 22 | indent_size = 2 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ v* ] 17 | pull_request: 18 | branches: [ v* ] 19 | schedule: 20 | - cron: '41 1 * * 3' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze 25 | runs-on: ubuntu-latest 26 | permissions: 27 | actions: read 28 | contents: read 29 | security-events: write 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | language: [ 'go' ] 35 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 36 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 37 | 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v2 41 | 42 | # Initializes the CodeQL tools for scanning. 43 | - name: Initialize CodeQL 44 | uses: github/codeql-action/init@v1 45 | with: 46 | languages: ${{ matrix.language }} 47 | # If you wish to specify custom queries, you can do so here or in a config file. 48 | # By default, queries listed here will override any specified in a config file. 49 | # Prefix the list here with "+" to use these queries and those in the config file. 50 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 51 | 52 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 53 | # If this step fails, then you should remove it and run the build manually (see below) 54 | - name: Autobuild 55 | uses: github/codeql-action/autobuild@v1 56 | 57 | # ℹ️ Command-line programs to run using the OS shell. 58 | # 📚 https://git.io/JvXDl 59 | 60 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 61 | # and modify them (or add more) to build your code if your project 62 | # uses a compiled language 63 | 64 | #- run: | 65 | # make bootstrap 66 | # make release 67 | 68 | - name: Perform CodeQL Analysis 69 | uses: github/codeql-action/analyze@v1 70 | -------------------------------------------------------------------------------- /.github/workflows/golang.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ v* ] 6 | pull_request: 7 | branches: [ v* ] 8 | 9 | jobs: 10 | # Test 11 | tests: 12 | name: Test go${{ matrix.go }} on ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | os: 17 | - 'ubuntu-latest' 18 | - 'macos-latest' 19 | - 'windows-latest' 20 | go: 21 | - '1.19' 22 | - '1.20' 23 | - '1.21' 24 | 25 | runs-on: ${{ matrix.os }} 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-go@v4 30 | with: 31 | go-version: ${{ matrix.go }} 32 | - run: go test -v -race ./... 33 | 34 | # Lint 35 | # lint: 36 | # name: Lint go${{ matrix.go }} 37 | # 38 | # strategy: 39 | # matrix: 40 | # go: 41 | # - 1.16 42 | # - 1.17 43 | # - 1.18 44 | # 45 | # runs-on: ubuntu-latest 46 | # 47 | # steps: 48 | # - uses: actions/checkout@v2 49 | # - uses: actions/setup-go@v2 50 | # with: 51 | # go-version: ${{ matrix.go }} 52 | # - uses: golangci/golangci-lint-action@v2 53 | # with: 54 | # version: v1.45 55 | 56 | # Build 57 | build: 58 | name: Build ${{ matrix.goos }}/${{ matrix.goarch }} on ${{ matrix.os }} 59 | 60 | strategy: 61 | matrix: 62 | # `go tool dist list` with many exclusions 63 | include: 64 | - { os: ubuntu-latest, goos: linux, goarch: amd64, cgo: '0' } 65 | - { os: ubuntu-latest, goos: linux, goarch: arm64, cgo: '0' } 66 | - { os: ubuntu-latest, goos: linux, goarch: ppc64le, cgo: '0' } 67 | 68 | - { os: ubuntu-latest, goos: android, goarch: amd64, cgo: '0' } 69 | - { os: ubuntu-latest, goos: android, goarch: arm64, cgo: '0' } 70 | 71 | - { os: ubuntu-latest, goos: freebsd, goarch: amd64, cgo: '0' } 72 | - { os: ubuntu-latest, goos: freebsd, goarch: arm64, cgo: '0' } 73 | 74 | - { os: ubuntu-latest, goos: openbsd, goarch: amd64, cgo: '0' } 75 | - { os: ubuntu-latest, goos: openbsd, goarch: arm64, cgo: '0' } 76 | 77 | # MacOS IOKit required, so no cross-build available 78 | # - { os: ubuntu-latest, goos: darwin, goarch: amd64 } 79 | # - { os: ubuntu-latest, goos: darwin, goarch: arm64 } 80 | 81 | - { os: macos-latest, goos: darwin, goarch: amd64, cgo: '1' } 82 | - { os: macos-latest, goos: darwin, goarch: arm64, cgo: '1' } 83 | 84 | - { os: windows-latest, goos: windows, goarch: amd64, cgo: '0' } 85 | - { os: windows-latest, goos: windows, goarch: arm64, cgo: '0' } 86 | 87 | runs-on: ${{ matrix.os }} 88 | 89 | env: 90 | GOOS: ${{ matrix.goos }} 91 | GOARCH: ${{ matrix.goarch }} 92 | CGO_ENABLED: ${{ matrix.cgo }} 93 | 94 | steps: 95 | - uses: actions/checkout@v4 96 | - uses: actions/setup-go@v4 97 | with: 98 | go-version: '1.21' 99 | - run: uname -a && go build 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 3m 3 | issues-exit-code: 1 4 | concurrency: 4 5 | allow-parallel-runners: true 6 | tests: true 7 | 8 | output: 9 | format: colored-line-number 10 | print-issued-lines: true 11 | print-linter-name: true 12 | 13 | linters-settings: 14 | cyclop: 15 | max-complexity: 20 16 | package-average: 0.0 # the maximal average package complexity. If it's higher than 0.0 (float) the check is enabled (default 0.0) 17 | skip-tests: true 18 | funlen: 19 | lines: 60 20 | statements: 40 21 | golint: 22 | min-confidence: 0.9 23 | 24 | gci: 25 | no-inline-comments: true 26 | no-prefix-comments: true 27 | sections: 28 | - standard 29 | - default 30 | - prefix(github.com/albenik/go-serial) 31 | section-separators: 32 | - newLine 33 | 34 | lll: 35 | line-length: 140 36 | 37 | linters: 38 | enable-all: true 39 | # Please keep in order 40 | disable: 41 | - deadcode # The linter 'deadcode' is deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused. 42 | - exhaustruct # Unnecessary annoying! 43 | - exhaustivestruct # Unnecessary annoying! 44 | - goerr113 # Useful but not in this package! 45 | - gochecknoglobals # Too paranoid! 46 | - goimports # Latest gci with sections is good enough 47 | - golint # The linter 'golint' is deprecated (since v1.41.0) due to: The repository of the linter has been archived by the owner. Replaced by revive. 48 | - ifshort # The linter 'ifshort' is deprecated (since v1.48.0) due to: The repository of the linter has been deprecated by the owner. 49 | - interfacer # The linter 'interfacer' is deprecated (since v1.38.0) due to: The repository of the linter has been archived by the owner. 50 | - ireturn # Yes! Some functions returns interfaces. 51 | - maligned # The linter 'maligned' is deprecated (since v1.38.0) due to: The repository of the linter has been archived by the owner. Replaced by govet 'fieldalignment'. 52 | - nlreturn # Personally I hate mandatory blank lines before returns. 53 | - nosnakecase # The linter 'nosnakecase' is deprecated (since v1.48.1) due to: The repository of the linter has been deprecated by the owner. Replaced by revive(var-naming). 54 | - paralleltest 55 | - rowserrcheck # rowserrcheck is disabled because of generics. You can track the evolution of the generics support by following the https://github.com/golangci/golangci-lint/issues/2649. 56 | - scopelint # The linter 'scopelint' is deprecated (since v1.39.0) due to: The repository of the linter has been deprecated by the owner. Replaced by exportloopref. 57 | - structcheck # structcheck is disabled because of generics. You can track the evolution of the generics support by following the https://github.com/golangci/golangci-lint/issues/2649. 58 | - testableexamples # New linter, project is not adapted for this linter yet. 59 | - varcheck # The linter 'varcheck' is deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused. 60 | - varnamelen # Annoying! 61 | - unparam # Panics with GOOS=linux 62 | - wastedassign # wastedassign is disabled because of generics. You can track the evolution of the generics support by following the https://github.com/golangci/golangci-lint/issues/2649. 63 | - wrapcheck # Not needed! 64 | - wsl # Unnecessary annoying! 65 | 66 | issues: 67 | exclude-rules: 68 | - path: _test\.go 69 | linters: 70 | - gochecknoglobals 71 | - funlen 72 | - lll 73 | - wrapcheck 74 | 75 | - path: enumerator/ 76 | linters: 77 | - gci 78 | - gofumpt 79 | - revive 80 | 81 | - text: "Line contains TODO/BUG/FIXME" 82 | linters: 83 | - godox 84 | 85 | - source: "//go:generate" 86 | linters: 87 | - lll 88 | 89 | - source: "C\\." 90 | linters: 91 | - unconvert 92 | 93 | - source: "(?:dataBits:\\s+\\d+|baudRate:\\s+\\d+)" 94 | linters: 95 | - gomnd 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.7.0 4 | 5 | - CI: Supported go versions now are `go1.19`, `go1.20`, `go1.21` 6 | - Applied `go mod tidy -go 1.19` 7 | - Package `github.com/albenik/go-serial/enumerator` was removed as of broken build with `go1.21`, 8 | please use `github.com/bugst/go-serial/enumerator` — the original well maintained source of the removed package. 9 | - Minor code fixes (typo, linter recommendations, etc...) 10 | - Dependencies updated 11 | 12 | ## 2.6.1 13 | 14 | - BUGFIX: Linux, "bad address" while setting DTR (#41) 15 | 16 | ## 2.6.0 17 | 18 | - `go mod tudy -go 1.18`. 19 | - CI Tests: `go1.18`, `go1.19`, `go1.20`. 20 | - CI Cross-build: cleanup. 21 | - `golangci-lint` added & code cleaned. 22 | - obsolete `darwin/386` code removed. 23 | 24 | ## 2.5.1 25 | 26 | - `ppc64le` build supported [#33](https://github.com/albenik/go-serial/pull/33). 27 | 28 | ## 2.5.0 29 | 30 | - `GOOS=android` build supported [#29](https://github.com/albenik/go-serial/issues/29). 31 | - Unused second argument for unix build in method `Port.SetTimeoutEx()` was made optional in backward compatibility 32 | manner. 33 | - `go 1.13` errors supported: `PortError.Unwrap()` method added, `PortError.Cause()` method marked as deprecated. 34 | 35 | ## 2.4.0 36 | 37 | - `GOOS=darwin GOARCH=arm64` build supported [#25](https://github.com/albenik/go-serial/pull/25). 38 | - Fixed regression in `GOOS=darwin` build was introduced in `v2.3.0` 39 | 40 | ## 2.3.0 41 | 42 | - Some fixes backported from https://github.com/bugst/go-serial [#22](https://github.com/albenik/go-serial/pull/22). 43 | 44 | ## 2.2.0 45 | 46 | - `PortError.Cause()` method added 47 | 48 | ## 2.1.0 49 | 50 | - MacOS extended baudrate support added [#14](https://github.com/albenik/go-serial/pull/14). 51 | - MacOS wrong generated syscall fixed [#15](https://github.com/albenik/go-serial/issues/15). 52 | 53 | ## 2.0.0 54 | 55 | - New Go Module import path `github.com/albenik/go-serial/v2` 56 | - `serial.Port` interface discarded in favor of `serial.Port` structure (similar to `os.File`) 57 | - `serial.Mode` discarded and replaced with `serial.Option` 58 | - `serial.Open()` method changed to use `serila.Option`) 59 | - `port.SetMode(mode *Mode)` replaced with `port.Reconfigure(opts ...Option)` 60 | - `Disable HUPCL by default` [#7](https://github.com/albenik/go-serial/pull/7) 61 | - `WithHUPCL(bool)` option introduced 62 | - Minor bugfix & refactoring 63 | 64 | ## 1.x.x 65 | 66 | - Forked from https://github.com/bugst/go-serial 67 | - Minor but incompatible interface & logic changes implemented 68 | - Import path altered 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019-2022, Veniamin Albaev. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github.com/albenik/go-serial/v2 2 | 3 | ![Go](https://github.com/albenik/go-serial/workflows/Go/badge.svg) 4 | 5 | A cross-platform serial library for Go. Forked from [github.com/bugst/go-serial](https://github.com/bugst/go-serial) and 6 | now developing independently. 7 | 8 | Many ideas are bein taken from [github.com/bugst/go-serial](https://github.com/bugst/go-serial) 9 | and [github.com/pyserial/pyserial](https://github.com/pyserial/pyserial). 10 | 11 | Any PR-s are welcome. 12 | 13 | ## INSTALL 14 | 15 | Not work in GOPATH mode!!! 16 | 17 | ``` 18 | go get -u github.com/albenik/go-serial/v2 19 | ``` 20 | 21 | ## MacOS build note 22 | 23 | * Since version **v2.1.0** the macos build requires `IOKit` as dependency and is only possible on Mac with cgo enabled. 24 | * Apple M1 (darwin/arm64) is supported. _(Thanks to [martinhpedersen](https://github.com/albenik/go-serial/pull/25))_ 25 | 26 | ## Documentation and examples 27 | 28 | See the godoc here: https://pkg.go.dev/github.com/albenik/go-serial/v2 29 | 30 | ## License 31 | 32 | The software is release under a BSD 3-clause license 33 | -------------------------------------------------------------------------------- /constants_darwin.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial 8 | 9 | import "golang.org/x/sys/unix" 10 | 11 | const ( 12 | devicesBasePath = "/dev" 13 | regexFilter = "^(cu|tty)\\..*" 14 | 15 | ioctlTcflsh = unix.TIOCFLUSH 16 | 17 | tcCMSPAR uint64 = 0 // may be CMSPAR or PAREXT 18 | tcIUCLC uint64 = 0 19 | 20 | tcCCTS_OFLOW uint64 = 0x00010000 //nolint:revive,stylecheck 21 | tcCRTS_IFLOW uint64 = 0x00020000 //nolint:revive,stylecheck 22 | tcCRTSCTS = tcCCTS_OFLOW | tcCRTS_IFLOW 23 | ) 24 | 25 | var databitsMap = map[int]uint64{ 26 | 0: unix.CS8, // Default to 8 bits 27 | 5: unix.CS5, 28 | 6: unix.CS6, 29 | 7: unix.CS7, 30 | 8: unix.CS8, 31 | } 32 | -------------------------------------------------------------------------------- /constants_freebsd.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial 8 | 9 | import "golang.org/x/sys/unix" 10 | 11 | const ( 12 | devicesBasePath = "/dev" 13 | regexFilter = "^(cu|tty)\\..*" 14 | 15 | tcCMSPAR uint32 = 0 // may be CMSPAR or PAREXT 16 | tcIUCLC uint32 = 0 17 | 18 | tcCCTS_OFLOW uint32 = 0x00010000 //nolint:revive,stylecheck 19 | // tcCRTS_IFLOW uint32 = 0x00020000 //nolint:revive,stylecheck 20 | tcCRTSCTS = tcCCTS_OFLOW 21 | 22 | ioctlTcflsh = unix.TIOCFLUSH 23 | ) 24 | 25 | var ( 26 | baudrateMap = map[int]uint32{ 27 | 0: unix.B9600, // Default to 9600 28 | 50: unix.B50, 29 | 75: unix.B75, 30 | 110: unix.B110, 31 | 134: unix.B134, 32 | 150: unix.B150, 33 | 200: unix.B200, 34 | 300: unix.B300, 35 | 600: unix.B600, 36 | 1200: unix.B1200, 37 | 1800: unix.B1800, 38 | 2400: unix.B2400, 39 | 4800: unix.B4800, 40 | 9600: unix.B9600, 41 | 19200: unix.B19200, 42 | 38400: unix.B38400, 43 | 57600: unix.B57600, 44 | 115200: unix.B115200, 45 | 230400: unix.B230400, 46 | 460800: unix.B460800, 47 | 921600: unix.B921600, 48 | } 49 | 50 | databitsMap = map[int]uint32{ 51 | 0: unix.CS8, // Default to 8 bits 52 | 5: unix.CS5, 53 | 6: unix.CS6, 54 | 7: unix.CS7, 55 | 8: unix.CS8, 56 | } 57 | ) 58 | 59 | func toTermiosSpeedType(speed uint32) uint32 { 60 | return speed 61 | } 62 | -------------------------------------------------------------------------------- /constants_linux.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial 8 | 9 | import "golang.org/x/sys/unix" 10 | 11 | const ( 12 | devicesBasePath = "/dev" 13 | regexFilter = "(ttyS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO)[0-9]{1,3}" 14 | 15 | tcCMSPAR = unix.CMSPAR 16 | tcIUCLC = unix.IUCLC 17 | tcCRTSCTS uint32 = unix.CRTSCTS 18 | 19 | ioctlTcflsh = unix.TCFLSH 20 | ) 21 | 22 | var ( 23 | baudrateMap = map[int]uint32{ 24 | 0: unix.B9600, // Default to 9600 25 | 50: unix.B50, 26 | 75: unix.B75, 27 | 110: unix.B110, 28 | 134: unix.B134, 29 | 150: unix.B150, 30 | 200: unix.B200, 31 | 300: unix.B300, 32 | 600: unix.B600, 33 | 1200: unix.B1200, 34 | 1800: unix.B1800, 35 | 2400: unix.B2400, 36 | 4800: unix.B4800, 37 | 9600: unix.B9600, 38 | 19200: unix.B19200, 39 | 38400: unix.B38400, 40 | 57600: unix.B57600, 41 | 115200: unix.B115200, 42 | 230400: unix.B230400, 43 | 460800: unix.B460800, 44 | 500000: unix.B500000, 45 | 576000: unix.B576000, 46 | 921600: unix.B921600, 47 | 1000000: unix.B1000000, 48 | 1152000: unix.B1152000, 49 | 1500000: unix.B1500000, 50 | 2000000: unix.B2000000, 51 | 2500000: unix.B2500000, 52 | 3000000: unix.B3000000, 53 | 3500000: unix.B3500000, 54 | 4000000: unix.B4000000, 55 | } 56 | 57 | databitsMap = map[int]uint32{ 58 | 0: unix.CS8, // Default to 8 bits 59 | 5: unix.CS5, 60 | 6: unix.CS6, 61 | 7: unix.CS7, 62 | 8: unix.CS8, 63 | } 64 | ) 65 | -------------------------------------------------------------------------------- /constants_openbsd.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial 8 | 9 | import "golang.org/x/sys/unix" 10 | 11 | const ( 12 | devicesBasePath = "/dev" 13 | regexFilter = "^(cu|tty)\\..*" 14 | 15 | tcCMSPAR uint32 = 0 // may be CMSPAR or PAREXT 16 | tcIUCLC uint32 = 0 17 | 18 | tcCCTS_OFLOW uint32 = 0x00010000 //nolint:revive,stylecheck 19 | // tcCRTS_IFLOW uint32 = 0x00020000 //nolint:revive,stylecheck 20 | tcCRTSCTS = tcCCTS_OFLOW 21 | 22 | ioctlTcflsh = unix.TIOCFLUSH 23 | ) 24 | 25 | var baudrateMap = map[int]uint32{ 26 | 0: unix.B9600, // Default to 9600 27 | 50: unix.B50, 28 | 75: unix.B75, 29 | 110: unix.B110, 30 | 134: unix.B134, 31 | 150: unix.B150, 32 | 200: unix.B200, 33 | 300: unix.B300, 34 | 600: unix.B600, 35 | 1200: unix.B1200, 36 | 1800: unix.B1800, 37 | 2400: unix.B2400, 38 | 4800: unix.B4800, 39 | 9600: unix.B9600, 40 | 19200: unix.B19200, 41 | 38400: unix.B38400, 42 | 57600: unix.B57600, 43 | 115200: unix.B115200, 44 | 230400: unix.B230400, 45 | // 460800: unix.B460800, 46 | // 921600: unix.B921600, 47 | } 48 | 49 | var databitsMap = map[int]uint32{ 50 | 0: unix.CS8, // Default to 8 bits 51 | 5: unix.CS5, 52 | 6: unix.CS6, 53 | 7: unix.CS7, 54 | 8: unix.CS8, 55 | } 56 | 57 | func toTermiosSpeedType(speed uint32) int32 { 58 | return int32(speed) 59 | } 60 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | /* 8 | 9 | Package go-serial is a cross-platform serial library for the go language. 10 | 11 | import github.com/albenik/go-serial/v2 12 | 13 | It is possible to get the list of available serial ports with the 14 | GetPortsList function: 15 | 16 | ports, err := serial.GetPortsList() 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | if len(ports) == 0 { 21 | log.Fatal("No serial ports found!") 22 | } 23 | for _, port := range ports { 24 | fmt.Printf("Found port: %v\n", port) 25 | } 26 | 27 | The serial port can be opened with the Open function: 28 | 29 | port, err := serial.Open("/dev/ttyUSB0", serial.WithBaudrate(115200)) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | The Open function can accept options that specifies the configuration 35 | options for the serial port. If not specified the default options are 9600_N81, 36 | in the example above only the speed is changed so the port is opened using 115200_N81. 37 | The following snippets shows how to declare a configuration for 57600_E71: 38 | 39 | serial.Open("/dev/ttyUSB0", 40 | serial.WithBaudrate(57600), 41 | serial.WithParity(serial.EvenParity), 42 | serial.WithDataBits(7), 43 | serial.WithStopBits(serial.OneStopBit), 44 | ) 45 | 46 | The configuration can be changed at any time with the Reconfigure() function: 47 | 48 | if err := port.Reconfigure( 49 | serial.WithBaudrate(57600), 50 | serial.WithParity(serial.EvenParity), 51 | ); err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | The port object implements the io.ReadWriteCloser interface, so we can use 56 | the usual Read, Write and Close functions to send and receive data from the 57 | serial port: 58 | 59 | n, err := port.Write([]byte("10,20,30\n\r")) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | fmt.Printf("Sent %v bytes\n", n) 64 | 65 | buff := make([]byte, 100) 66 | for { 67 | n, err := port.Read(buff) 68 | if err != nil { 69 | log.Fatal(err) 70 | break 71 | } 72 | if n == 0 { 73 | fmt.Println("\nEOF") 74 | break 75 | } 76 | fmt.Printf("%v", string(buff[:n])) 77 | } 78 | 79 | If a port is a virtual USB-CDC serial port (for example an USB-to-RS232 80 | cable or a microcontroller development board) is possible to retrieve 81 | the USB metadata, like VID/PID or USB Serial Number, with the 82 | GetDetailedPortsList function in the enumerator package: 83 | 84 | import "github.com/albenik/go-serial/v2/enumerator" 85 | 86 | ports, err := enumerator.GetDetailedPortsList() 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | if len(ports) == 0 { 91 | fmt.Println("No serial ports found!") 92 | return 93 | } 94 | for _, port := range ports { 95 | fmt.Printf("Found port: %s\n", port.Name) 96 | if port.IsUSB { 97 | fmt.Printf(" USB ID %s:%s\n", port.VID, port.PID) 98 | fmt.Printf(" USB serial %s\n", port.SerialNumber) 99 | } 100 | } 101 | 102 | for details on USB port enumeration see the documentation of the specific package. 103 | 104 | This library tries to avoid the use of the "C" package (and consequently the need 105 | of cgo) to simplify cross compiling. 106 | Unfortunately the USB enumeration package for darwin (MacOSX) requires cgo 107 | to access the IOKit framework. This means that if you need USB enumeration 108 | on darwin you're forced to use cgo. 109 | */ 110 | 111 | package serial 112 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | // PortErrorCode is a code to easily identify the type of error. 4 | type PortErrorCode int 5 | 6 | const ( 7 | PortErrorUnknown PortErrorCode = iota 8 | // PortBusy the serial port is already in used by another process. 9 | PortBusy 10 | // PortNotFound the requested port doesn't exist. 11 | PortNotFound 12 | // InvalidSerialPort the requested port is not a serial port. 13 | InvalidSerialPort 14 | // PermissionDenied the user doesn't have enough privileges. 15 | PermissionDenied 16 | // InvalidSpeed the requested speed is not valid or not supported. 17 | InvalidSpeed 18 | // InvalidDataBits the number of data bits is not valid or not supported. 19 | InvalidDataBits 20 | // InvalidParity the selected parity is not valid or not supported. 21 | InvalidParity 22 | // InvalidStopBits the selected number of stop bits is not valid or not supported. 23 | InvalidStopBits 24 | // InvalidTimeoutValue Invalid timeout value passed. 25 | InvalidTimeoutValue 26 | // ErrorEnumeratingPorts an error occurred while listing serial port. 27 | ErrorEnumeratingPorts 28 | // PortClosed the port has been closed while the operation is in progress. 29 | PortClosed 30 | // FunctionNotImplemented the requested function is not implemented. 31 | FunctionNotImplemented 32 | // OsError Operating system function error. 33 | OsError 34 | // WriteFailed Port write failed. 35 | WriteFailed 36 | // ReadFailed Port read failed. 37 | ReadFailed 38 | ) 39 | 40 | // PortError is a platform independent error type for serial ports. 41 | type PortError struct { 42 | code PortErrorCode 43 | wrapped error 44 | } 45 | 46 | // EncodedErrorString returns a string explaining the error code. 47 | func (e PortError) EncodedErrorString() string { 48 | switch e.code { 49 | case PortErrorUnknown: 50 | return "error code not set" 51 | case PortBusy: 52 | return "serial port busy" 53 | case PortNotFound: 54 | return "serial port not found" 55 | case InvalidSerialPort: 56 | return "invalid serial port" 57 | case PermissionDenied: 58 | return "permission denied" 59 | case InvalidSpeed: 60 | return "port speed invalid or not supported" 61 | case InvalidDataBits: 62 | return "port data bits invalid or not supported" 63 | case InvalidParity: 64 | return "port parity invalid or not supported" 65 | case InvalidStopBits: 66 | return "port stop bits invalid or not supported" 67 | case InvalidTimeoutValue: 68 | return "timeout value invalid or not supported" 69 | case ErrorEnumeratingPorts: 70 | return "could not enumerate serial ports" 71 | case PortClosed: 72 | return "port has been closed" 73 | case FunctionNotImplemented: 74 | return "function not implemented" 75 | case OsError: 76 | return "operating system error" 77 | case ReadFailed: 78 | return "read filed" 79 | case WriteFailed: 80 | return "write failed" 81 | default: 82 | return "other error" 83 | } 84 | } 85 | 86 | // Error returns the complete error code with details on the cause of the error. 87 | func (e PortError) Error() string { 88 | if e.wrapped != nil { 89 | return e.EncodedErrorString() + ": " + e.wrapped.Error() 90 | } 91 | return e.EncodedErrorString() 92 | } 93 | 94 | func (e PortError) Unwrap() error { 95 | return e.wrapped 96 | } 97 | 98 | // Code returns an identifier for the kind of error occurred. 99 | func (e PortError) Code() PortErrorCode { 100 | return e.code 101 | } 102 | 103 | // Cause returns the cause for the error 104 | // Deprecated: Use go1.13 error iterface Unwrap() instead. 105 | func (e PortError) Cause() error { 106 | return e.Unwrap() 107 | } 108 | 109 | func newPortOSError(err error) *PortError { 110 | return &PortError{code: OsError, wrapped: err} 111 | } 112 | -------------------------------------------------------------------------------- /example_getportlist_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial_test 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | 13 | "github.com/albenik/go-serial/v2" 14 | ) 15 | 16 | func ExampleGetPortsList() { 17 | ports, err := serial.GetPortsList() 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | if len(ports) == 0 { 22 | fmt.Println("No serial ports found!") 23 | } else { 24 | for _, port := range ports { 25 | fmt.Printf("Found port: %v\n", port) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example_modem_bits_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial_test 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "time" 13 | 14 | "github.com/albenik/go-serial/v2" 15 | ) 16 | 17 | func ExamplePort_GetModemStatusBits() { 18 | // Open the first serial port detected at 9600bps N81 19 | port, err := serial.Open("/dev/ttyACM1", 20 | serial.WithBaudrate(9600), 21 | serial.WithDataBits(8), 22 | serial.WithParity(serial.NoParity), 23 | serial.WithStopBits(serial.OneStopBit), 24 | serial.WithReadTimeout(1000), 25 | serial.WithWriteTimeout(1000), 26 | ) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | defer port.Close() 31 | 32 | count := 0 33 | for count < 25 { 34 | status, err := port.GetModemStatusBits() 35 | if err != nil { 36 | log.Println(err) // DO NOT USER log.Fatal or `port.Close()` deferred call will never happened! 37 | return 38 | } 39 | fmt.Printf("Status: %+v\n", status) 40 | 41 | time.Sleep(time.Second) 42 | count++ 43 | if count == 5 { 44 | if err = port.SetDTR(false); err != nil { 45 | log.Println(err) 46 | return 47 | } 48 | fmt.Println("Set DTR OFF") 49 | } 50 | if count == 10 { 51 | if err = port.SetDTR(true); err != nil { 52 | log.Println(err) 53 | return 54 | } 55 | fmt.Println("Set DTR ON") 56 | } 57 | if count == 15 { 58 | if err = port.SetRTS(false); err != nil { 59 | log.Println(err) 60 | return 61 | } 62 | fmt.Println("Set RTS OFF") 63 | } 64 | if count == 20 { 65 | if err = port.SetRTS(true); err != nil { 66 | log.Println(err) 67 | return 68 | } 69 | fmt.Println("Set RTS ON") 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /example_serialport_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial_test 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | 13 | "github.com/albenik/go-serial/v2" 14 | ) 15 | 16 | func ExamplePort_Reconfigure() { 17 | port, err := serial.Open("/dev/ttyACM0") 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | if err := port.Reconfigure( 22 | serial.WithBaudrate(9600), 23 | serial.WithDataBits(8), 24 | serial.WithParity(serial.NoParity), 25 | serial.WithStopBits(serial.OneStopBit), 26 | serial.WithReadTimeout(1000), 27 | serial.WithWriteTimeout(1000), 28 | ); err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Println("Port set to 9600 N81") 32 | } 33 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial_test 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | 13 | "github.com/albenik/go-serial/v2" 14 | ) 15 | 16 | // This example prints the list of serial ports and use the first one 17 | // to send a string "10,20,30" and prints the response on the screen. 18 | func Example_sendAndReceive() { 19 | // Retrieve the port list 20 | ports, err := serial.GetPortsList() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | if len(ports) == 0 { 25 | log.Fatal("No serial ports found!") 26 | } 27 | 28 | // Print the list of detected ports 29 | for _, port := range ports { 30 | fmt.Printf("Found port: %v\n", port) 31 | } 32 | 33 | // Open the first serial port detected at 9600bps N81 34 | port, err := serial.Open(ports[0], 35 | serial.WithBaudrate(9600), 36 | serial.WithDataBits(8), 37 | serial.WithParity(serial.NoParity), 38 | serial.WithStopBits(serial.OneStopBit), 39 | serial.WithReadTimeout(1000), 40 | serial.WithWriteTimeout(1000), 41 | serial.WithHUPCL(false), 42 | ) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | // Send the string "ABCDEF" to the serial port 48 | n, err := fmt.Fprint(port, "ABCDEF") 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | fmt.Printf("Sent %v bytes\n", n) 53 | 54 | // Read and print the response 55 | buff := make([]byte, 100) 56 | for { 57 | // Reads up to 100 bytes 58 | n, err := port.Read(buff) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | if n == 0 { 63 | fmt.Println("\nEOF") 64 | break 65 | } 66 | fmt.Printf("%v", string(buff[:n])) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/albenik/go-serial/v2 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/creack/goselect v0.1.2 7 | github.com/stretchr/testify v1.8.4 8 | go.uber.org/multierr v1.11.0 9 | golang.org/x/sys v0.16.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/kr/text v0.2.0 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 17 | gopkg.in/yaml.v3 v3.0.1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= 2 | github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 7 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 10 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 11 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 15 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 16 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 17 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 18 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 19 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 22 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 23 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 24 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2022 Veniamin Albaev . 3 | // All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | 8 | package serial 9 | 10 | type Option func(p *Port) 11 | 12 | func WithBaudrate(o int) Option { 13 | return func(p *Port) { 14 | p.baudRate = o 15 | } 16 | } 17 | 18 | func WithDataBits(o int) Option { 19 | return func(p *Port) { 20 | p.dataBits = o 21 | } 22 | } 23 | 24 | func WithParity(o Parity) Option { 25 | return func(p *Port) { 26 | p.parity = o 27 | } 28 | } 29 | 30 | func WithStopBits(o StopBits) Option { 31 | return func(p *Port) { 32 | p.stopBits = o 33 | } 34 | } 35 | 36 | func WithReadTimeout(o int) Option { 37 | return func(p *Port) { 38 | p.setReadTimeoutValues(o) 39 | } 40 | } 41 | 42 | func WithWriteTimeout(o int) Option { 43 | return func(p *Port) { 44 | p.setWriteTimeoutValues(o) 45 | } 46 | } 47 | 48 | func WithHUPCL(o bool) Option { 49 | return func(p *Port) { 50 | p.hupcl = o 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /serial.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2018 Cristian Maglie. 3 | // Copyright 2019-2022 Veniamin Albaev . 4 | // All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | // 8 | 9 | package serial 10 | 11 | import ( 12 | "os" 13 | ) 14 | 15 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 16 | 17 | const ( 18 | // NoParity disable parity control (default). 19 | NoParity Parity = iota 20 | // OddParity enable odd-parity check. 21 | OddParity 22 | // EvenParity enable even-parity check. 23 | EvenParity 24 | // MarkParity enable mark-parity (always 1) check. 25 | MarkParity 26 | // SpaceParity enable space-parity (always 0) check. 27 | SpaceParity 28 | ) 29 | 30 | const ( 31 | // OneStopBit sets 1 stop bit (default). 32 | OneStopBit StopBits = iota 33 | // OnePointFiveStopBits sets 1.5 stop bits. 34 | OnePointFiveStopBits 35 | // TwoStopBits sets 2 stop bits. 36 | TwoStopBits 37 | ) 38 | 39 | // StopBits describe a serial port stop bits setting. 40 | type StopBits int 41 | 42 | // Parity describes a serial port parity setting. 43 | type Parity int 44 | 45 | // ModemStatusBits contains all the modem status bits for a serial port (CTS, DSR, etc...). 46 | // It can be retrieved with the Port.GetModemStatusBits() method. 47 | type ModemStatusBits struct { 48 | CTS bool // ClearToSend status 49 | DSR bool // DataSetReady status 50 | RI bool // RingIndicator status 51 | DCD bool // DataCarrierDetect status 52 | } 53 | 54 | // Port is the interface for a serial Port. 55 | type Port struct { 56 | name string 57 | opened bool 58 | baudRate int // The serial port bitrate (aka Baudrate) 59 | dataBits int // Size of the character (must be 5, 6, 7 or 8) 60 | parity Parity // Parity (see Parity type for more info) 61 | stopBits StopBits // Stop bits (see StopBits type for more info) 62 | hupcl bool // Lower DTR line on close (hang up) 63 | 64 | internal *port // os specific (implementation like os.File) 65 | } 66 | 67 | func (p *Port) String() string { 68 | if p == nil { 69 | return "Error: port instance" 70 | } 71 | return p.name 72 | } 73 | 74 | func (p *Port) checkValid() error { 75 | if p == nil || p.internal == nil || !isHandleValid(p.internal.handle) { 76 | return &PortError{code: PortClosed, wrapped: os.ErrInvalid} 77 | } 78 | if !p.opened { 79 | return &PortError{code: PortClosed} 80 | } 81 | return nil 82 | } 83 | 84 | func newWithDefaults(n string, p *port) *Port { 85 | return &Port{ 86 | name: n, 87 | opened: true, 88 | baudRate: 9600, 89 | dataBits: 8, 90 | parity: NoParity, 91 | stopBits: OneStopBit, 92 | hupcl: false, 93 | internal: p, 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /serial_open_android.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2018 Cristian Maglie. All rights reserved. 3 | // Copyright 2019-2022 Veniamin Albaev .. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | 8 | //go:build android 9 | 10 | package serial 11 | 12 | import ( 13 | "go.uber.org/multierr" 14 | "golang.org/x/sys/unix" 15 | ) 16 | 17 | func accquireExclusiveAccess(_ int) error { 18 | return nil 19 | } 20 | 21 | func (p *Port) closeAndReturnError(code PortErrorCode, err error) *PortError { 22 | return &PortError{ 23 | code: code, 24 | wrapped: multierr.Combine( 25 | err, 26 | unix.Close(p.internal.handle), 27 | ), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /serial_open_unix.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2018 Cristian Maglie. All rights reserved. 3 | // Copyright 2019-2022 Veniamin Albaev . 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | 8 | //go:build (linux && !android) || darwin || freebsd || openbsd 9 | 10 | package serial 11 | 12 | import ( 13 | "go.uber.org/multierr" 14 | "golang.org/x/sys/unix" 15 | ) 16 | 17 | func accquireExclusiveAccess(h int) error { 18 | return unix.IoctlSetInt(h, unix.TIOCEXCL, 0) 19 | } 20 | 21 | func (p *Port) closeAndReturnError(code PortErrorCode, err error) *PortError { 22 | return &PortError{ 23 | code: code, 24 | wrapped: multierr.Combine( 25 | err, 26 | unix.IoctlSetInt(p.internal.handle, unix.TIOCNXCL, 0), 27 | unix.Close(p.internal.handle), 28 | ), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /serial_termsettings_android.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build android 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func (p *Port) retrieveTermSettings() (s *settings, err error) { 16 | s = &settings{ 17 | termios: new(unix.Termios), 18 | } 19 | 20 | if s.termios, err = unix.IoctlGetTermios(p.internal.handle, unix.TCGETS); err != nil { 21 | return nil, newPortOSError(err) 22 | } 23 | 24 | return s, nil 25 | } 26 | 27 | func (p *Port) applyTermSettings(s *settings) error { 28 | if err := unix.IoctlSetTermios(p.internal.handle, unix.TCSETS, s.termios); err != nil { 29 | return newPortOSError(err) 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /serial_termsettings_bsd.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build freebsd || openbsd 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func (p *Port) retrieveTermSettings() (*settings, error) { 16 | var err error 17 | s := &settings{termios: new(unix.Termios)} 18 | 19 | if s.termios, err = unix.IoctlGetTermios(p.internal.handle, unix.TIOCGETA); err != nil { 20 | return nil, newPortOSError(err) 21 | } 22 | 23 | return s, nil 24 | } 25 | 26 | func (p *Port) applyTermSettings(s *settings) error { 27 | if err := unix.IoctlSetTermios(p.internal.handle, unix.TIOCSETA, s.termios); err != nil { 28 | return newPortOSError(err) 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /serial_termsettings_darwin.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build darwin 8 | 9 | package serial 10 | 11 | // #include 12 | // #include 13 | import "C" 14 | 15 | import ( 16 | "unsafe" 17 | 18 | "golang.org/x/sys/unix" 19 | ) 20 | 21 | func (p *Port) retrieveTermSettings() (*settings, error) { 22 | s := &settings{termios: new(unix.Termios), specificBaudrate: 0} 23 | 24 | var err error 25 | if s.termios, err = unix.IoctlGetTermios(p.internal.handle, unix.TIOCGETA); err != nil { 26 | return nil, newPortOSError(err) 27 | } 28 | 29 | speed := C.cfgetispeed((*C.struct_termios)(unsafe.Pointer(s.termios))) 30 | s.specificBaudrate = int(speed) 31 | 32 | return s, nil 33 | } 34 | 35 | func (p *Port) applyTermSettings(s *settings) error { 36 | if err := unix.IoctlSetTermios(p.internal.handle, unix.TIOCSETA, s.termios); err != nil { 37 | return newPortOSError(err) 38 | } 39 | 40 | speed := s.specificBaudrate 41 | if err := unix.IoctlSetPointerInt(p.internal.handle, C.IOSSIOSPEED, speed); err != nil { 42 | return newPortOSError(err) 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /serial_termsettings_linux.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build linux && !android && !ppc64le 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func (p *Port) retrieveTermSettings() (s *settings, err error) { 16 | s = &settings{ 17 | termios: new(unix.Termios), 18 | } 19 | 20 | if s.termios, err = unix.IoctlGetTermios(p.internal.handle, unix.TCGETS); err != nil { 21 | return nil, newPortOSError(err) 22 | } 23 | 24 | if s.termios.Cflag&unix.BOTHER == unix.BOTHER { 25 | if s.termios, err = unix.IoctlGetTermios(p.internal.handle, unix.TCGETS2); err != nil { 26 | return nil, newPortOSError(err) 27 | } 28 | } 29 | 30 | return s, nil 31 | } 32 | 33 | func (p *Port) applyTermSettings(s *settings) error { 34 | req := uint(unix.TCSETS) 35 | if s.termios.Cflag&unix.BOTHER == unix.BOTHER { 36 | req = unix.TCSETS2 37 | } 38 | 39 | if err := unix.IoctlSetTermios(p.internal.handle, req, s.termios); err != nil { 40 | return newPortOSError(err) 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /serial_termsettings_linux_ppc64le.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | // linux/ppc64le specific implementation, that does not use TCGETS2 or TCSETS2, 7 | // as they are not supported by golang v1.18 for that platform circa 2022-03. 8 | // Code is identical to android implementation. 9 | 10 | //go:build linux && !android && ppc64le 11 | 12 | package serial 13 | 14 | import ( 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | func (p *Port) retrieveTermSettings() (s *settings, err error) { 19 | s = &settings{ 20 | termios: new(unix.Termios), 21 | } 22 | 23 | if s.termios, err = unix.IoctlGetTermios(p.internal.handle, unix.TCGETS); err != nil { 24 | return nil, newPortOSError(err) 25 | } 26 | 27 | return s, nil 28 | } 29 | 30 | func (p *Port) applyTermSettings(s *settings) error { 31 | if err := unix.IoctlSetTermios(p.internal.handle, unix.TCSETS, s.termios); err != nil { 32 | return newPortOSError(err) 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /serial_test.go: -------------------------------------------------------------------------------- 1 | package serial_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/albenik/go-serial/v2" 11 | ) 12 | 13 | func TestPortNilReceiver_Error(t *testing.T) { 14 | checkError := func(err error) { 15 | var portErr *serial.PortError 16 | require.ErrorAs(t, err, &portErr) 17 | assert.Equal(t, serial.PortClosed, portErr.Code()) 18 | assert.ErrorIs(t, err, os.ErrInvalid) 19 | } 20 | 21 | t.Run("Close", func(t *testing.T) { 22 | checkError((*serial.Port)(nil).Close()) 23 | }) 24 | 25 | t.Run("Reconfigure", func(t *testing.T) { 26 | checkError((*serial.Port)(nil).Reconfigure()) 27 | }) 28 | 29 | t.Run("ReadyToRead", func(t *testing.T) { 30 | _, err := (*serial.Port)(nil).ReadyToRead() 31 | checkError(err) 32 | }) 33 | 34 | t.Run("Read", func(t *testing.T) { 35 | _, err := (*serial.Port)(nil).Read(make([]byte, 16)) 36 | checkError(err) 37 | }) 38 | 39 | t.Run("Write", func(t *testing.T) { 40 | _, err := (*serial.Port)(nil).Write([]byte{1, 2, 3, 4, 5, 6, 7, 8}) 41 | checkError(err) 42 | }) 43 | 44 | t.Run("ResetInputBuffer", func(t *testing.T) { 45 | checkError((*serial.Port)(nil).ResetInputBuffer()) 46 | }) 47 | 48 | t.Run("ResetOutputBuffer", func(t *testing.T) { 49 | checkError((*serial.Port)(nil).ResetOutputBuffer()) 50 | }) 51 | 52 | t.Run("SetDTR", func(t *testing.T) { 53 | checkError((*serial.Port)(nil).SetDTR(false)) 54 | }) 55 | 56 | t.Run("SetRTS", func(t *testing.T) { 57 | checkError((*serial.Port)(nil).SetRTS(false)) 58 | }) 59 | 60 | t.Run("SetReadTimeout", func(t *testing.T) { 61 | checkError((*serial.Port)(nil).SetReadTimeout(1)) 62 | }) 63 | 64 | t.Run("SetReadTimeoutEx", func(t *testing.T) { 65 | checkError((*serial.Port)(nil).SetReadTimeoutEx(1, 0)) 66 | }) 67 | 68 | t.Run("SetFirstByteReadTimeout", func(t *testing.T) { 69 | checkError((*serial.Port)(nil).SetFirstByteReadTimeout(1)) 70 | }) 71 | 72 | t.Run("SetWriteTimeout", func(t *testing.T) { 73 | checkError((*serial.Port)(nil).SetWriteTimeout(1)) 74 | }) 75 | 76 | t.Run("GetModemStatusBits", func(t *testing.T) { 77 | _, err := (*serial.Port)(nil).GetModemStatusBits() 78 | checkError(err) 79 | }) 80 | } 81 | 82 | func TestPortTestPortNilReceiver_String(t *testing.T) { 83 | var p *serial.Port 84 | assert.Equal(t, "Error: port instance", p.String()) 85 | } 86 | 87 | /* 88 | func TestSerial_Operations(t *testing.T) { 89 | ports, err := serial.GetPortsList() 90 | require.NoError(t, err) 91 | 92 | if len(ports) == 0 { 93 | t.Log("No serial ports found") 94 | } 95 | 96 | for _, name := range ports { 97 | t.Logf("Found port: %q", name) 98 | port, err := serial.Open(name, 99 | serial.WithBaudrate(9600), 100 | serial.WithDataBits(8), 101 | serial.WithParity(serial.NoParity), 102 | serial.WithStopBits(serial.OneStopBit), 103 | serial.WithReadTimeout(1000), 104 | serial.WithWriteTimeout(1000), 105 | serial.WithHUPCL(false), 106 | ) 107 | if err == nil { 108 | t.Logf("Port %q opened", name) 109 | if assert.NoError(t, port.Close()) { 110 | t.Logf("Port %q closed without errors", name) 111 | } 112 | continue 113 | } 114 | 115 | var portErr serial.PortError 116 | expected := errors.As(err, &portErr) && !in(portErr.Code(), serial.OsError, serial.InvalidSerialPort) 117 | if !expected { 118 | assert.NoError(t, err) 119 | } 120 | } 121 | } 122 | 123 | func in(code serial.PortErrorCode, list ...serial.PortErrorCode) bool { 124 | for _, c := range list { 125 | if code == c { 126 | return true 127 | } 128 | } 129 | return false 130 | } 131 | */ 132 | -------------------------------------------------------------------------------- /serial_unix.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2018 Cristian Maglie. All rights reserved. 3 | // Copyright 2019-2022 Veniamin Albaev . 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // 7 | 8 | //go:build linux || darwin || freebsd || openbsd 9 | 10 | package serial 11 | 12 | import ( 13 | "errors" 14 | "os" 15 | "path" 16 | "regexp" 17 | "strings" 18 | "syscall" 19 | "time" 20 | 21 | "go.uber.org/multierr" 22 | "golang.org/x/sys/unix" 23 | 24 | "github.com/albenik/go-serial/v2/unixutils" 25 | ) 26 | 27 | const FIONREAD = 0x541B 28 | 29 | var ( 30 | zeroByte = []byte{0} 31 | portNameRx = regexp.MustCompile(regexFilter) 32 | ) 33 | 34 | type port struct { 35 | handle int 36 | 37 | firstByteTimeout bool 38 | readTimeout int 39 | writeTimeout int 40 | 41 | closePipeR int 42 | closePipeW int 43 | } 44 | 45 | func Open(name string, opts ...Option) (*Port, error) { 46 | h, err := unix.Open(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NDELAY, 0) 47 | if err != nil { 48 | switch { 49 | case errors.Is(err, unix.EBUSY): 50 | return nil, &PortError{code: PortBusy} 51 | case errors.Is(err, unix.EACCES): 52 | return nil, &PortError{code: PermissionDenied} 53 | default: 54 | return nil, err 55 | } 56 | } 57 | 58 | // does nothing in build for android 59 | if err = accquireExclusiveAccess(h); err != nil { 60 | return nil, newPortOSError(multierr.Append(err, unix.Close(h))) 61 | } 62 | 63 | p := newWithDefaults(name, &port{ 64 | handle: h, 65 | firstByteTimeout: true, 66 | readTimeout: 0, 67 | writeTimeout: 0, 68 | }) 69 | 70 | // Setup serial port 71 | if err := p.Reconfigure(opts...); err != nil { 72 | return nil, p.closeAndReturnError(InvalidSerialPort, err) 73 | } 74 | 75 | if err = unix.SetNonblock(h, false); err != nil { 76 | return nil, p.closeAndReturnError(OsError, err) 77 | } 78 | 79 | fds := []int{0, 0} 80 | if err := syscall.Pipe(fds); err != nil { 81 | _ = p.Close() 82 | return nil, p.closeAndReturnError(OsError, err) 83 | } 84 | p.internal.closePipeR = fds[0] 85 | p.internal.closePipeW = fds[1] 86 | 87 | return p, nil 88 | } 89 | 90 | func (p *Port) Close() error { 91 | // NOT thread safe 92 | if err := p.checkValid(); err != nil { 93 | return err 94 | } 95 | 96 | p.opened = false 97 | 98 | // Send close signal to all pending reads (if any) and close signaling pipe 99 | _, err := unix.Write(p.internal.closePipeW, zeroByte) 100 | err = multierr.Combine( 101 | err, 102 | unix.Close(p.internal.closePipeW), 103 | unix.Close(p.internal.closePipeR), 104 | unix.IoctlSetInt(p.internal.handle, unix.TIOCNXCL, 0), 105 | unix.Close(p.internal.handle), 106 | ) 107 | 108 | if err != nil { 109 | return newPortOSError(err) 110 | } 111 | return nil 112 | } 113 | 114 | func (p *Port) Reconfigure(opts ...Option) error { 115 | for _, o := range opts { 116 | o(p) 117 | } 118 | return p.reconfigure() 119 | } 120 | 121 | func (p *Port) ReadyToRead() (uint32, error) { 122 | if err := p.checkValid(); err != nil { 123 | return 0, err 124 | } 125 | 126 | n, err := unix.IoctlGetInt(p.internal.handle, FIONREAD) 127 | if err != nil { 128 | return 0, newPortOSError(err) 129 | } 130 | return uint32(n), nil 131 | } 132 | 133 | func (p *Port) Read(b []byte) (int, error) { 134 | if err := p.checkValid(); err != nil { 135 | return 0, err 136 | } 137 | 138 | size, read := len(b), 0 139 | fds := unixutils.NewFDSet(p.internal.handle, p.internal.closePipeR) 140 | buf := make([]byte, size) 141 | 142 | now := time.Now() 143 | deadline := now.Add(time.Duration(p.internal.readTimeout) * time.Millisecond) 144 | 145 | for read < size { 146 | res, err := unixutils.Select(fds, nil, fds, deadline.Sub(now)) 147 | if err != nil { 148 | if errors.Is(err, unix.EINTR) { 149 | continue 150 | } 151 | return read, newPortOSError(err) 152 | } 153 | 154 | if res.IsReadable(p.internal.closePipeR) { 155 | return read, &PortError{code: PortClosed} 156 | } 157 | if !res.IsReadable(p.internal.handle) { 158 | return read, nil 159 | } 160 | 161 | n, err := unix.Read(p.internal.handle, buf[read:]) 162 | if err != nil { 163 | if errors.Is(err, unix.EINTR) { 164 | continue 165 | } 166 | return read, newPortOSError(err) 167 | } 168 | 169 | // read should always return some data as select reported, it was ready to read when we got to this point. 170 | if n == 0 { 171 | return read, &PortError{code: ReadFailed} 172 | } 173 | 174 | copy(b[read:], buf[read:read+n]) 175 | read += n 176 | 177 | now = time.Now() 178 | if !now.Before(deadline) || p.internal.firstByteTimeout { 179 | return read, nil 180 | } 181 | } 182 | return read, nil 183 | } 184 | 185 | func (p *Port) Write(b []byte) (int, error) { 186 | if err := p.checkValid(); err != nil { 187 | return 0, err 188 | } 189 | 190 | size, written := len(b), 0 191 | fds := unixutils.NewFDSet(p.internal.handle) 192 | clFds := unixutils.NewFDSet(p.internal.closePipeR) 193 | 194 | deadline := time.Now().Add(time.Duration(p.internal.writeTimeout) * time.Millisecond) 195 | 196 | for written < size { 197 | n, err := unix.Write(p.internal.handle, b[written:]) 198 | if err != nil { 199 | return written, newPortOSError(err) 200 | } 201 | 202 | if p.internal.writeTimeout == 0 { 203 | return n, nil 204 | } 205 | 206 | written += n 207 | now := time.Now() 208 | if p.internal.writeTimeout > 0 && !now.Before(deadline) { 209 | return written, nil 210 | } 211 | 212 | res, err := unixutils.Select(clFds, fds, fds, deadline.Sub(now)) 213 | if err != nil { 214 | return written, newPortOSError(err) 215 | } 216 | 217 | if res.IsReadable(p.internal.closePipeR) { 218 | return written, &PortError{code: PortClosed} 219 | } 220 | 221 | if !res.IsWritable(p.internal.handle) { 222 | return written, &PortError{code: WriteFailed} 223 | } 224 | } 225 | return written, nil 226 | } 227 | 228 | func (p *Port) ResetInputBuffer() error { 229 | if err := p.checkValid(); err != nil { 230 | return err 231 | } 232 | 233 | if err := unix.IoctlSetInt(p.internal.handle, ioctlTcflsh, unix.TCIFLUSH); err != nil { 234 | return newPortOSError(err) 235 | } 236 | return nil 237 | } 238 | 239 | func (p *Port) ResetOutputBuffer() error { 240 | if err := p.checkValid(); err != nil { 241 | return err 242 | } 243 | 244 | if err := unix.IoctlSetInt(p.internal.handle, ioctlTcflsh, unix.TCOFLUSH); err != nil { 245 | return newPortOSError(err) 246 | } 247 | return nil 248 | } 249 | 250 | func (p *Port) SetDTR(dtr bool) error { 251 | if err := p.checkValid(); err != nil { 252 | return err 253 | } 254 | 255 | status, err := p.retrieveModemBitsStatus() 256 | if err != nil { 257 | return err // port.retrieveModemBitsStatus already returned PortError 258 | } 259 | if dtr { 260 | status |= unix.TIOCM_DTR 261 | } else { 262 | status &^= unix.TIOCM_DTR 263 | } 264 | return p.applyModemBitsStatus(status) // already returned PortError 265 | } 266 | 267 | func (p *Port) SetRTS(rts bool) error { 268 | if err := p.checkValid(); err != nil { 269 | return err 270 | } 271 | 272 | status, err := p.retrieveModemBitsStatus() 273 | if err != nil { 274 | return err // port.retrieveModemBitsStatus() already returned PortError 275 | } 276 | if rts { 277 | status |= unix.TIOCM_RTS 278 | } else { 279 | status &^= unix.TIOCM_RTS 280 | } 281 | return p.applyModemBitsStatus(status) // already returned PortError 282 | } 283 | 284 | func (p *Port) SetReadTimeout(t int) error { 285 | if err := p.checkValid(); err != nil { 286 | return err 287 | } 288 | 289 | p.setReadTimeoutValues(t) 290 | return nil // timeout is done via select 291 | } 292 | 293 | // SetReadTimeoutEx Sets advanced timeouts. 294 | // Second argument was forget here due refactoring and keeping now for backward compatibility. 295 | // TODO Remove second argument in version v3. 296 | func (p *Port) SetReadTimeoutEx(t uint32, _ ...uint32) error { 297 | if err := p.checkValid(); err != nil { 298 | return err 299 | } 300 | 301 | s, err := p.retrieveTermSettings() 302 | if err != nil { 303 | return err // port.retrieveTermSettings() already returned PortError 304 | } 305 | 306 | //nolint:gomnd 307 | vtime := t / 100 // VTIME tenths of a second elapses between bytes 308 | if vtime > 255 || vtime*100 != t { 309 | return &PortError{code: InvalidTimeoutValue} 310 | } 311 | if vtime > 0 { 312 | s.termios.Cc[unix.VMIN] = 1 313 | s.termios.Cc[unix.VTIME] = uint8(t) 314 | } else { 315 | s.termios.Cc[unix.VMIN] = 0 316 | s.termios.Cc[unix.VTIME] = 0 317 | } 318 | 319 | if err = p.applyTermSettings(s); err != nil { 320 | return err // port.applyTermSettings() already returned PortError 321 | } 322 | 323 | p.internal.firstByteTimeout = false 324 | p.internal.readTimeout = int(t) 325 | return nil 326 | } 327 | 328 | func (p *Port) SetFirstByteReadTimeout(t uint32) error { 329 | if err := p.checkValid(); err != nil { 330 | return err 331 | } 332 | 333 | if t > 0 && t < 0xFFFFFFFF { 334 | p.internal.firstByteTimeout = true 335 | p.internal.readTimeout = int(t) 336 | return nil 337 | } 338 | return &PortError{code: InvalidTimeoutValue} 339 | } 340 | 341 | func (p *Port) SetWriteTimeout(t int) error { 342 | if err := p.checkValid(); err != nil { 343 | return err 344 | } 345 | 346 | p.setWriteTimeoutValues(t) 347 | return nil // timeout is done via select 348 | } 349 | 350 | func (p *Port) GetModemStatusBits() (*ModemStatusBits, error) { 351 | if err := p.checkValid(); err != nil { 352 | return nil, err 353 | } 354 | 355 | status, err := p.retrieveModemBitsStatus() 356 | if err != nil { 357 | return nil, err // port.retrieveModemBitsStatus() already returned PortError 358 | } 359 | return &ModemStatusBits{ 360 | CTS: (status & unix.TIOCM_CTS) != 0, 361 | DCD: (status & unix.TIOCM_CD) != 0, 362 | DSR: (status & unix.TIOCM_DSR) != 0, 363 | RI: (status & unix.TIOCM_RI) != 0, 364 | }, nil 365 | } 366 | 367 | func (p *Port) setReadTimeoutValues(t int) { 368 | p.internal.firstByteTimeout = false 369 | p.internal.readTimeout = t 370 | } 371 | 372 | func (p *Port) setWriteTimeoutValues(t int) { 373 | p.internal.writeTimeout = t 374 | } 375 | 376 | func (p *Port) retrieveModemBitsStatus() (int, error) { 377 | s, err := unix.IoctlGetInt(p.internal.handle, unix.TIOCMGET) 378 | if err != nil { 379 | return 0, newPortOSError(err) 380 | } 381 | return s, nil 382 | } 383 | 384 | func (p *Port) applyModemBitsStatus(status int) error { 385 | if err := unix.IoctlSetPointerInt(p.internal.handle, unix.TIOCMSET, status); err != nil { 386 | return newPortOSError(err) 387 | } 388 | return nil 389 | } 390 | 391 | func (p *Port) reconfigure() error { 392 | if err := p.checkValid(); err != nil { 393 | return err 394 | } 395 | 396 | s, err := p.retrieveTermSettings() 397 | if err != nil { 398 | return err // port.retrieveTermSettings() already returned PortError 399 | } 400 | 401 | if err := s.setBaudrate(p.baudRate); err != nil { 402 | return err 403 | } 404 | if err := s.setParity(p.parity); err != nil { 405 | return err 406 | } 407 | if err := s.setDataBits(p.dataBits); err != nil { 408 | return err 409 | } 410 | if err := s.setStopBits(p.stopBits); err != nil { 411 | return err 412 | } 413 | s.setRawMode(p.hupcl) 414 | // Explicitly disable RTS/CTS flow control 415 | s.setCtsRts(false) 416 | 417 | return p.applyTermSettings(s) // already returned PortError 418 | } 419 | 420 | func GetPortsList() ([]string, error) { 421 | files, err := os.ReadDir(devicesBasePath) 422 | if err != nil { 423 | return nil, err 424 | } 425 | 426 | ports := make([]string, 0, len(files)) 427 | for _, f := range files { 428 | // Skip folders 429 | if f.IsDir() { 430 | continue 431 | } 432 | 433 | // Keep only devices with the correct name 434 | if !portNameRx.MatchString(f.Name()) { 435 | continue 436 | } 437 | 438 | name := path.Join(devicesBasePath, f.Name()) 439 | 440 | // Check if serial port is real or is a placeholder serial port "ttySxx" 441 | if strings.HasPrefix(f.Name(), "ttyS") { 442 | if port, err := Open(name); err != nil { 443 | var portErr *PortError 444 | if errors.As(err, &portErr) && portErr.Code() == InvalidSerialPort { 445 | continue 446 | } 447 | } else { 448 | _ = port.Close() 449 | } 450 | } 451 | 452 | // Save serial port in the resulting list 453 | ports = append(ports, name) 454 | } 455 | 456 | return ports, nil 457 | } 458 | 459 | func isHandleValid(h int) bool { 460 | return h != 0 461 | } 462 | -------------------------------------------------------------------------------- /serial_windows.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2018 Cristian Maglie. 3 | // Copyright 2019-2022 Veniamin Albaev . 4 | // All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | // 8 | 9 | package serial 10 | 11 | // Useful links: 12 | // https://msdn.microsoft.com/en-us/library/ff802693.aspx 13 | // https://msdn.microsoft.com/en-us/library/ms810467.aspx 14 | // https://github.com/pyserial/pyserial 15 | // https://pythonhosted.org/pyserial/ 16 | // https://playground.arduino.cc/Interfacing/CPPWindows 17 | // https://www.tldp.org/HOWTO/Serial-HOWTO-19.html 18 | 19 | import "syscall" 20 | 21 | var parityMap = map[Parity]byte{ 22 | NoParity: 0, 23 | OddParity: 1, 24 | EvenParity: 2, 25 | MarkParity: 3, 26 | SpaceParity: 4, 27 | } 28 | 29 | var stopBitsMap = map[StopBits]byte{ 30 | OneStopBit: 0, 31 | OnePointFiveStopBits: 1, 32 | TwoStopBits: 2, 33 | } 34 | 35 | type port struct { 36 | handle syscall.Handle 37 | timeouts *commTimeouts 38 | } 39 | 40 | func Open(name string, opts ...Option) (*Port, error) { 41 | path, err := syscall.UTF16PtrFromString("\\\\.\\" + name) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | handle, err := syscall.CreateFile( 47 | path, 48 | syscall.GENERIC_READ|syscall.GENERIC_WRITE, 49 | 0, // exclusive access 50 | nil, // no security 51 | syscall.OPEN_EXISTING, 52 | syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED, 53 | 0) 54 | if err != nil { 55 | switch err { 56 | case syscall.ERROR_ACCESS_DENIED: 57 | return nil, &PortError{code: PortBusy} 58 | case syscall.ERROR_FILE_NOT_FOUND: 59 | return nil, &PortError{code: PortNotFound} 60 | } 61 | return nil, err 62 | } 63 | 64 | port := newWithDefaults(name, &port{ 65 | handle: handle, 66 | timeouts: &commTimeouts{ 67 | // Read blocks until done. 68 | ReadIntervalTimeout: 0, 69 | ReadTotalTimeoutMultiplier: 0, 70 | ReadTotalTimeoutConstant: 0, 71 | // Write blocks until done. 72 | WriteTotalTimeoutMultiplier: 0, 73 | WriteTotalTimeoutConstant: 0, 74 | }, 75 | }) 76 | if err = port.Reconfigure(opts...); err != nil { 77 | port.Close() 78 | return nil, err 79 | } 80 | 81 | return port, nil 82 | } 83 | 84 | func (p *Port) Close() error { 85 | if err := p.checkValid(); err != nil { 86 | return err 87 | } 88 | 89 | if p.internal.handle == syscall.InvalidHandle { 90 | return nil 91 | } 92 | err := syscall.CloseHandle(p.internal.handle) 93 | p.internal.handle = syscall.InvalidHandle 94 | if err != nil { 95 | return &PortError{code: OsError, wrapped: err} 96 | } 97 | return nil 98 | } 99 | 100 | func (p *Port) Reconfigure(opts ...Option) error { 101 | if err := p.checkValid(); err != nil { 102 | return err 103 | } 104 | 105 | for _, o := range opts { 106 | o(p) 107 | } 108 | return p.reconfigure() 109 | } 110 | 111 | func (p *Port) ReadyToRead() (uint32, error) { 112 | if err := p.checkValid(); err != nil { 113 | return 0, err 114 | } 115 | 116 | var errs uint32 117 | var stat comstat 118 | if err := clearCommError(p.internal.handle, &errs, &stat); err != nil { 119 | return 0, &PortError{code: OsError, wrapped: err} 120 | } 121 | return stat.inque, nil 122 | } 123 | 124 | func (p *Port) Read(b []byte) (int, error) { 125 | if err := p.checkValid(); err != nil { 126 | return 0, err 127 | } 128 | 129 | if p.internal.handle == syscall.InvalidHandle { 130 | return 0, &PortError{code: PortClosed, wrapped: nil} 131 | } 132 | handle := p.internal.handle 133 | 134 | errs := new(uint32) 135 | stat := new(comstat) 136 | if err := clearCommError(handle, errs, stat); err != nil { 137 | return 0, &PortError{code: InvalidSerialPort, wrapped: err} 138 | } 139 | 140 | size := uint32(len(b)) 141 | var readSize uint32 142 | if p.internal.timeouts.ReadTotalTimeoutConstant == 0 && p.internal.timeouts.ReadTotalTimeoutMultiplier == 0 { 143 | if stat.inque < size { 144 | readSize = stat.inque 145 | } else { 146 | readSize = size 147 | } 148 | } else { 149 | readSize = size 150 | } 151 | 152 | if readSize > 0 { 153 | var read uint32 154 | overlapped, err := createOverlappedStruct() 155 | if err != nil { 156 | return 0, &PortError{code: OsError, wrapped: err} 157 | } 158 | defer syscall.CloseHandle(overlapped.HEvent) 159 | err = syscall.ReadFile(handle, b[:readSize], &read, overlapped) 160 | if err != nil && err != syscall.ERROR_IO_PENDING { 161 | return 0, &PortError{code: OsError, wrapped: err} 162 | } 163 | err = getOverlappedResult(handle, overlapped, &read, true) 164 | if err != nil && err != syscall.ERROR_OPERATION_ABORTED { 165 | return 0, &PortError{code: OsError, wrapped: err} 166 | } 167 | return int(read), nil 168 | } else { 169 | return 0, nil 170 | } 171 | } 172 | 173 | func (p *Port) Write(b []byte) (int, error) { 174 | if err := p.checkValid(); err != nil { 175 | return 0, err 176 | } 177 | 178 | h := p.internal.handle 179 | errs := new(uint32) 180 | stat := new(comstat) 181 | if err := clearCommError(h, errs, stat); err != nil { 182 | return 0, &PortError{code: InvalidSerialPort, wrapped: err} 183 | } 184 | 185 | overlapped, err := createOverlappedStruct() 186 | if err != nil { 187 | return 0, err 188 | } 189 | defer syscall.CloseHandle(overlapped.HEvent) 190 | var written uint32 191 | err = syscall.WriteFile(h, b, &written, overlapped) 192 | if err == nil || err == syscall.ERROR_IO_PENDING || err == syscall.ERROR_OPERATION_ABORTED { 193 | err = getOverlappedResult(h, overlapped, &written, true) 194 | if err == nil || err == syscall.ERROR_OPERATION_ABORTED { 195 | return int(written), nil 196 | } 197 | } 198 | return int(written), err 199 | } 200 | 201 | func (p *Port) ResetInputBuffer() error { 202 | if err := p.checkValid(); err != nil { 203 | return err 204 | } 205 | 206 | return purgeComm(p.internal.handle, purgeRxClear|purgeRxAbort) 207 | } 208 | 209 | func (p *Port) ResetOutputBuffer() error { 210 | if err := p.checkValid(); err != nil { 211 | return err 212 | } 213 | 214 | return purgeComm(p.internal.handle, purgeTxClear|purgeTxAbort) 215 | } 216 | 217 | func (p *Port) SetDTR(dtr bool) error { 218 | if err := p.checkValid(); err != nil { 219 | return err 220 | } 221 | 222 | // Like for RTS there are problems with the escapeCommFunction 223 | // observed behaviour was that DTR is set from false -> true 224 | // when setting RTS from true -> false 225 | // 1) Connect -> RTS = true (low) DTR = true (low) OKAY 226 | // 2) SetDTR(false) -> RTS = true (low) DTR = false (heigh) OKAY 227 | // 3) SetRTS(false) -> RTS = false (heigh) DTR = true (low) ERROR: DTR toggled 228 | // 229 | // In addition this way the CommState Flags are not updated 230 | /* 231 | var res bool 232 | if dtr { 233 | res = escapeCommFunction(port.handle, commFunctionSetDTR) 234 | } else { 235 | res = escapeCommFunction(port.handle, commFunctionClrDTR) 236 | } 237 | if !res { 238 | return &PortError{} 239 | } 240 | return nil 241 | */ 242 | 243 | // The following seems a more reliable way to do it 244 | 245 | p.hupcl = dtr 246 | 247 | params := &dcb{} 248 | if err := getCommState(p.internal.handle, params); err != nil { 249 | return &PortError{wrapped: err} 250 | } 251 | 252 | params.Flags &= dcbDTRControlDisableMask 253 | if dtr { 254 | params.Flags |= dcbDTRControlEnable 255 | } 256 | 257 | if err := setCommState(p.internal.handle, params); err != nil { 258 | return &PortError{wrapped: err} 259 | } 260 | 261 | return nil 262 | } 263 | 264 | func (p *Port) SetRTS(rts bool) error { 265 | if err := p.checkValid(); err != nil { 266 | return err 267 | } 268 | 269 | // It seems that there is a bug in the Windows VCP driver: 270 | // it doesn't send USB control message when the RTS bit is 271 | // changed, so the following code not always works with 272 | // USB-to-serial adapters. 273 | // 274 | // In addition this way the CommState Flags are not updated 275 | 276 | /* 277 | var res bool 278 | if rts { 279 | res = escapeCommFunction(port.handle, commFunctionSetRTS) 280 | } else { 281 | res = escapeCommFunction(port.handle, commFunctionClrRTS) 282 | } 283 | if !res { 284 | return &PortError{} 285 | } 286 | return nil 287 | */ 288 | 289 | // The following seems a more reliable way to do it 290 | 291 | params := &dcb{} 292 | if err := getCommState(p.internal.handle, params); err != nil { 293 | return &PortError{wrapped: err} 294 | } 295 | params.Flags &= dcbRTSControlDisableMask 296 | if rts { 297 | params.Flags |= dcbRTSControlEnable 298 | } 299 | if err := setCommState(p.internal.handle, params); err != nil { 300 | return &PortError{wrapped: err} 301 | } 302 | return nil 303 | } 304 | 305 | func (p *Port) SetReadTimeout(t int) error { 306 | if err := p.checkValid(); err != nil { 307 | return err 308 | } 309 | 310 | p.setReadTimeoutValues(t) 311 | return p.reconfigure() 312 | } 313 | 314 | func (p *Port) SetReadTimeoutEx(t, i uint32) error { 315 | if err := p.checkValid(); err != nil { 316 | return err 317 | } 318 | 319 | p.internal.timeouts.ReadIntervalTimeout = i 320 | p.internal.timeouts.ReadTotalTimeoutMultiplier = 0 321 | p.internal.timeouts.ReadTotalTimeoutConstant = t 322 | return p.reconfigure() 323 | } 324 | 325 | func (p *Port) SetFirstByteReadTimeout(t uint32) error { 326 | if err := p.checkValid(); err != nil { 327 | return err 328 | } 329 | 330 | if t > 0 && t < 0xFFFFFFFF { 331 | p.internal.timeouts.ReadIntervalTimeout = 0xFFFFFFFF 332 | p.internal.timeouts.ReadTotalTimeoutMultiplier = 0xFFFFFFFF 333 | p.internal.timeouts.ReadTotalTimeoutConstant = t 334 | return p.reconfigure() 335 | } else { 336 | return &PortError{code: InvalidTimeoutValue} 337 | } 338 | } 339 | 340 | func (p *Port) SetWriteTimeout(t int) error { 341 | if err := p.checkValid(); err != nil { 342 | return err 343 | } 344 | 345 | p.setWriteTimeoutValues(t) 346 | return p.reconfigure() 347 | } 348 | 349 | func (p *Port) GetModemStatusBits() (*ModemStatusBits, error) { 350 | if err := p.checkValid(); err != nil { 351 | return nil, err 352 | } 353 | 354 | var bits uint32 355 | if !getCommModemStatus(p.internal.handle, &bits) { 356 | return nil, &PortError{} 357 | } 358 | return &ModemStatusBits{ 359 | CTS: (bits & msCTSOn) != 0, 360 | DCD: (bits & msRLSDOn) != 0, 361 | DSR: (bits & msDSROn) != 0, 362 | RI: (bits & msRingOn) != 0, 363 | }, nil 364 | } 365 | 366 | func (p *Port) setReadTimeoutValues(t int) { 367 | switch { 368 | case t < 0: // Block until the buffer is full. 369 | p.internal.timeouts.ReadIntervalTimeout = 0 370 | p.internal.timeouts.ReadTotalTimeoutMultiplier = 0 371 | p.internal.timeouts.ReadTotalTimeoutConstant = 0 372 | case t == 0: // Return immediately with or without data. 373 | p.internal.timeouts.ReadIntervalTimeout = 0xFFFFFFFF 374 | p.internal.timeouts.ReadTotalTimeoutMultiplier = 0 375 | p.internal.timeouts.ReadTotalTimeoutConstant = 0 376 | case t > 0: // Block until the buffer is full or timeout occurs. 377 | p.internal.timeouts.ReadIntervalTimeout = 0 378 | p.internal.timeouts.ReadTotalTimeoutMultiplier = 0 379 | p.internal.timeouts.ReadTotalTimeoutConstant = uint32(t) 380 | } 381 | } 382 | 383 | func (p *Port) setWriteTimeoutValues(t int) { 384 | switch { 385 | case t < 0: 386 | p.internal.timeouts.WriteTotalTimeoutMultiplier = 0 387 | p.internal.timeouts.WriteTotalTimeoutConstant = 0 388 | case t == 0: 389 | p.internal.timeouts.WriteTotalTimeoutMultiplier = 0 390 | p.internal.timeouts.WriteTotalTimeoutConstant = 0xFFFFFFFF 391 | case t > 0: 392 | p.internal.timeouts.WriteTotalTimeoutMultiplier = 0 393 | p.internal.timeouts.WriteTotalTimeoutConstant = uint32(t) 394 | } 395 | } 396 | 397 | func (p *Port) reconfigure() error { 398 | if err := setCommTimeouts(p.internal.handle, p.internal.timeouts); err != nil { 399 | p.Close() 400 | return &PortError{code: InvalidSerialPort, wrapped: err} 401 | } 402 | if err := setCommMask(p.internal.handle, evErr); err != nil { 403 | p.Close() 404 | return &PortError{code: InvalidSerialPort, wrapped: err} 405 | } 406 | params := &dcb{} 407 | if err := getCommState(p.internal.handle, params); err != nil { 408 | p.Close() 409 | return &PortError{code: InvalidSerialPort, wrapped: err} 410 | } 411 | params.Flags &= dcbRTSControlDisableMask 412 | params.Flags |= dcbRTSControlEnable 413 | params.Flags &= dcbDTRControlDisableMask 414 | if p.hupcl { 415 | params.Flags |= dcbDTRControlEnable 416 | } 417 | params.Flags &^= dcbOutXCTSFlow 418 | params.Flags &^= dcbOutXDSRFlow 419 | params.Flags &^= dcbDSRSensitivity 420 | params.Flags |= dcbTXContinueOnXOFF 421 | params.Flags &^= dcbInX 422 | params.Flags &^= dcbOutX 423 | params.Flags &^= dcbErrorChar 424 | params.Flags &^= dcbNull 425 | params.Flags &^= dcbAbortOnError 426 | params.XonLim = 2048 427 | params.XoffLim = 512 428 | params.XonChar = 17 // DC1 429 | params.XoffChar = 19 // C3 430 | 431 | params.BaudRate = uint32(p.baudRate) 432 | params.ByteSize = byte(p.dataBits) 433 | params.Parity = parityMap[p.parity] 434 | params.StopBits = stopBitsMap[p.stopBits] 435 | 436 | if err := setCommState(p.internal.handle, params); err != nil { 437 | p.Close() 438 | return &PortError{code: InvalidSerialPort, wrapped: err} 439 | } 440 | return nil 441 | } 442 | 443 | func GetPortsList() ([]string, error) { 444 | subKey, err := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM\\") 445 | if err != nil { 446 | return nil, &PortError{code: ErrorEnumeratingPorts} 447 | } 448 | 449 | var h syscall.Handle 450 | if syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, subKey, 0, syscall.KEY_READ, &h) != nil { 451 | return nil, &PortError{code: ErrorEnumeratingPorts} 452 | } 453 | defer syscall.RegCloseKey(h) 454 | 455 | var valuesCount uint32 456 | if syscall.RegQueryInfoKey(h, nil, nil, nil, nil, nil, nil, &valuesCount, nil, nil, nil, nil) != nil { 457 | return nil, &PortError{code: ErrorEnumeratingPorts} 458 | } 459 | 460 | list := make([]string, valuesCount) 461 | for i := range list { 462 | var data [1024]uint16 463 | dataSize := uint32(len(data)) 464 | var name [1024]uint16 465 | nameSize := uint32(len(name)) 466 | if regEnumValue(h, uint32(i), &name[0], &nameSize, nil, nil, &data[0], &dataSize) != nil { 467 | return nil, &PortError{code: ErrorEnumeratingPorts} 468 | } 469 | list[i] = syscall.UTF16ToString(data[:]) 470 | } 471 | return list, nil 472 | } 473 | 474 | func isHandleValid(h syscall.Handle) bool { 475 | return h != syscall.InvalidHandle 476 | } 477 | -------------------------------------------------------------------------------- /syscall_windows.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | package serial 8 | 9 | import ( 10 | "syscall" 11 | ) 12 | 13 | //sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) = advapi32.RegEnumValueW 14 | 15 | const ( 16 | ceBreak uint32 = 0x0010 17 | ceFrame = 0x0008 18 | ceOverrun = 0x0002 19 | ceRxover = 0x0001 20 | ceRxparity = 0x0004 21 | ) 22 | 23 | type comstat struct { 24 | /* typedef struct _COMSTAT { 25 | DWORD fCtsHold :1; 26 | DWORD fDsrHold :1; 27 | DWORD fRlsdHold :1; 28 | DWORD fXoffHold :1; 29 | DWORD fXoffSent :1; 30 | DWORD fEof :1; 31 | DWORD fTxim :1; 32 | DWORD fReserved :25; 33 | DWORD cbInQue; 34 | DWORD cbOutQue; 35 | } COMSTAT, *LPCOMSTAT; */ 36 | flags uint32 37 | inque uint32 38 | outque uint32 39 | } 40 | 41 | //sys clearCommError(handle syscall.Handle, lpErrors *uint32, lpStat *comstat) (err error) = ClearCommError 42 | 43 | const ( 44 | dcbBinary uint32 = 0x00000001 45 | dcbParity = 0x00000002 46 | dcbOutXCTSFlow = 0x00000004 47 | dcbOutXDSRFlow = 0x00000008 48 | dcbDTRControlDisableMask = ^uint32(0x00000030) 49 | dcbDTRControlEnable = 0x00000010 50 | dcbDTRControlHandshake = 0x00000020 51 | dcbDSRSensitivity = 0x00000040 52 | dcbTXContinueOnXOFF = 0x00000080 53 | dcbOutX = 0x00000100 54 | dcbInX = 0x00000200 55 | dcbErrorChar = 0x00000400 56 | dcbNull = 0x00000800 57 | dcbRTSControlDisableMask = ^uint32(0x00003000) 58 | dcbRTSControlEnable = 0x00001000 59 | dcbRTSControlHandshake = 0x00002000 60 | dcbRTSControlToggle = 0x00003000 61 | dcbAbortOnError = 0x00004000 62 | ) 63 | 64 | type dcb struct { 65 | DCBlength uint32 66 | BaudRate uint32 67 | 68 | // Flags field is a bitfield 69 | // fBinary :1 70 | // fParity :1 71 | // fOutxCtsFlow :1 72 | // fOutxDsrFlow :1 73 | // fDtrControl :2 74 | // fDsrSensitivity :1 75 | // fTXContinueOnXoff :1 76 | // fOutX :1 77 | // fInX :1 78 | // fErrorChar :1 79 | // fNull :1 80 | // fRtsControl :2 81 | // fAbortOnError :1 82 | // fDummy2 :17 83 | Flags uint32 84 | 85 | wReserved uint16 86 | XonLim uint16 87 | XoffLim uint16 88 | ByteSize byte 89 | Parity byte 90 | StopBits byte 91 | XonChar byte 92 | XoffChar byte 93 | ErrorChar byte 94 | EOFChar byte 95 | EvtChar byte 96 | wReserved1 uint16 97 | } 98 | 99 | //sys getCommState(handle syscall.Handle, dcb *dcb) (err error) = GetCommState 100 | 101 | //sys setCommState(handle syscall.Handle, dcb *dcb) (err error) = SetCommState 102 | 103 | type commTimeouts struct { 104 | ReadIntervalTimeout uint32 105 | ReadTotalTimeoutMultiplier uint32 106 | ReadTotalTimeoutConstant uint32 107 | WriteTotalTimeoutMultiplier uint32 108 | WriteTotalTimeoutConstant uint32 109 | } 110 | 111 | //sys setCommTimeouts(handle syscall.Handle, timeouts *commTimeouts) (err error) = SetCommTimeouts 112 | 113 | const ( 114 | commFunctionSetXOFF = 1 115 | commFunctionSetXON = 2 116 | commFunctionSetRTS = 3 117 | commFunctionClrRTS = 4 118 | commFunctionSetDTR = 5 119 | commFunctionClrDTR = 6 120 | commFunctionSetBreak = 8 121 | commFunctionClrBreak = 9 122 | ) 123 | 124 | //sys escapeCommFunction(handle syscall.Handle, function uint32) (res bool) = EscapeCommFunction 125 | 126 | const ( 127 | msCTSOn = 0x0010 128 | msDSROn = 0x0020 129 | msRingOn = 0x0040 130 | msRLSDOn = 0x0080 131 | ) 132 | 133 | //sys getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) = GetCommModemStatus 134 | 135 | const ( 136 | evBreak uint32 = 0x0040 // A break was detected on input. 137 | evCts = 0x0008 // The CTS (clear-to-send) signal changed state. 138 | evDsr = 0x0010 // The DSR (data-set-ready) signal changed state. 139 | evErr = 0x0080 // A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY. 140 | evRing = 0x0100 // A ring indicator was detected. 141 | evRlsd = 0x0020 // The RLSD (receive-line-signal-detect) signal changed state. 142 | evRxChar = 0x0001 // A character was received and placed in the input buffer. 143 | evRxFlag = 0x0002 // The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function. 144 | evTxEmpty = 0x0004 // The last character in the output buffer was sent. 145 | ) 146 | 147 | //sys setCommMask(handle syscall.Handle, mask uint32) (err error) = SetCommMask 148 | 149 | //sys createEvent(eventAttributes *uint32, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) = CreateEventW 150 | 151 | //sys resetEvent(handle syscall.Handle) (err error) = ResetEvent 152 | 153 | //sys getOverlappedResult(handle syscall.Handle, overlapEvent *syscall.Overlapped, n *uint32, wait bool) (err error) = GetOverlappedResult 154 | 155 | const ( 156 | purgeRxAbort uint32 = 0x0002 157 | purgeRxClear = 0x0008 158 | purgeTxAbort = 0x0001 159 | purgeTxClear = 0x0004 160 | ) 161 | 162 | //sys purgeComm(handle syscall.Handle, flags uint32) (err error) = PurgeComm 163 | 164 | func createOverlappedStruct() (*syscall.Overlapped, error) { 165 | if h, err := createEvent(nil, true, false, nil); err == nil { 166 | return &syscall.Overlapped{HEvent: h}, nil 167 | } else { 168 | return nil, err 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /termios_baudrate_bsd.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build freebsd || openbsd 8 | 9 | package serial 10 | 11 | func (s *settings) setBaudrate(speed int) error { 12 | baudrate, ok := baudrateMap[speed] 13 | if !ok { 14 | return &PortError{code: InvalidSpeed} 15 | } 16 | // revert old baudrate 17 | for _, rate := range baudrateMap { 18 | s.termios.Cflag &^= rate 19 | } 20 | // set new baudrate 21 | s.termios.Cflag |= baudrate 22 | s.termios.Ispeed = toTermiosSpeedType(baudrate) 23 | s.termios.Ospeed = toTermiosSpeedType(baudrate) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /termios_baudrate_darwin.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build darwin 8 | 9 | package serial 10 | 11 | func (s *settings) setBaudrate(speed int) error { 12 | s.specificBaudrate = speed 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /termios_baudrate_linux.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build linux 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func (s *settings) setBaudrate(r int) error { 16 | if rate, ok := baudrateMap[r]; ok { 17 | // clear all standard baudrate bits 18 | for _, b := range baudrateMap { 19 | s.termios.Cflag &^= b 20 | } 21 | // set selected baudrate bit 22 | s.termios.Cflag |= rate 23 | s.termios.Ispeed = rate 24 | s.termios.Ospeed = rate 25 | } else { 26 | s.termios.Cflag &^= unix.CBAUD 27 | s.termios.Cflag |= unix.BOTHER 28 | s.termios.Ispeed = uint32(r) 29 | s.termios.Ospeed = uint32(r) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /termios_setting_darwin.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build darwin 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | type settings struct { 16 | termios *unix.Termios 17 | specificBaudrate int 18 | } 19 | -------------------------------------------------------------------------------- /termios_setting_unix.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build linux || freebsd || openbsd 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | type settings struct { 16 | termios *unix.Termios 17 | } 18 | -------------------------------------------------------------------------------- /termios_unix.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build linux || darwin || freebsd || openbsd 8 | 9 | package serial 10 | 11 | import ( 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func (s *settings) setParity(parity Parity) error { 16 | switch parity { 17 | case NoParity: 18 | s.termios.Cflag &^= unix.PARENB 19 | s.termios.Cflag &^= unix.PARODD 20 | s.termios.Cflag &^= tcCMSPAR 21 | s.termios.Iflag &^= unix.INPCK 22 | case OddParity: 23 | s.termios.Cflag |= unix.PARENB 24 | s.termios.Cflag |= unix.PARODD 25 | s.termios.Cflag &^= tcCMSPAR 26 | s.termios.Iflag |= unix.INPCK 27 | case EvenParity: 28 | s.termios.Cflag |= unix.PARENB 29 | s.termios.Cflag &^= unix.PARODD 30 | s.termios.Cflag &^= tcCMSPAR 31 | s.termios.Iflag |= unix.INPCK 32 | case MarkParity: 33 | if tcCMSPAR == 0 { 34 | return &PortError{code: InvalidParity} 35 | } 36 | s.termios.Cflag |= unix.PARENB 37 | s.termios.Cflag |= unix.PARODD 38 | s.termios.Cflag |= tcCMSPAR 39 | s.termios.Iflag |= unix.INPCK 40 | case SpaceParity: 41 | if tcCMSPAR == 0 { 42 | return &PortError{code: InvalidParity} 43 | } 44 | s.termios.Cflag |= unix.PARENB 45 | s.termios.Cflag &^= unix.PARODD 46 | s.termios.Cflag |= tcCMSPAR 47 | s.termios.Iflag |= unix.INPCK 48 | default: 49 | return &PortError{code: InvalidParity} 50 | } 51 | return nil 52 | } 53 | 54 | func (s *settings) setDataBits(bits int) error { 55 | databits, ok := databitsMap[bits] 56 | if !ok { 57 | return &PortError{code: InvalidDataBits} 58 | } 59 | // Remove previous databits setting 60 | s.termios.Cflag &^= unix.CSIZE 61 | // Set requested databits 62 | s.termios.Cflag |= databits 63 | return nil 64 | } 65 | 66 | func (s *settings) setStopBits(bits StopBits) error { 67 | switch bits { 68 | case OneStopBit: 69 | s.termios.Cflag &^= unix.CSTOPB 70 | case OnePointFiveStopBits: 71 | return &PortError{code: InvalidStopBits} 72 | case TwoStopBits: 73 | s.termios.Cflag |= unix.CSTOPB 74 | default: 75 | return &PortError{code: InvalidStopBits} 76 | } 77 | return nil 78 | } 79 | 80 | func (s *settings) setCtsRts(enable bool) { 81 | if enable { 82 | s.termios.Cflag |= tcCRTSCTS 83 | } else { 84 | s.termios.Cflag &^= tcCRTSCTS 85 | } 86 | } 87 | 88 | func (s *settings) setRawMode(hupcl bool) { 89 | // Set local mode 90 | s.termios.Cflag |= unix.CREAD 91 | s.termios.Cflag |= unix.CLOCAL 92 | if hupcl { 93 | s.termios.Cflag |= unix.HUPCL 94 | } else { 95 | s.termios.Cflag &^= unix.HUPCL 96 | } 97 | 98 | // Set raw mode 99 | s.termios.Lflag &^= unix.ICANON 100 | s.termios.Lflag &^= unix.ECHO 101 | s.termios.Lflag &^= unix.ECHOE 102 | s.termios.Lflag &^= unix.ECHOK 103 | s.termios.Lflag &^= unix.ECHONL 104 | s.termios.Lflag &^= unix.ECHOCTL 105 | s.termios.Lflag &^= unix.ECHOPRT 106 | s.termios.Lflag &^= unix.ECHOKE 107 | s.termios.Lflag &^= unix.ISIG 108 | s.termios.Lflag &^= unix.IEXTEN 109 | 110 | s.termios.Iflag &^= unix.IXON 111 | s.termios.Iflag &^= unix.IXOFF 112 | s.termios.Iflag &^= unix.IXANY 113 | s.termios.Iflag &^= unix.INPCK 114 | s.termios.Iflag &^= unix.IGNPAR 115 | s.termios.Iflag &^= unix.PARMRK 116 | s.termios.Iflag &^= unix.ISTRIP 117 | s.termios.Iflag &^= unix.IGNBRK 118 | s.termios.Iflag &^= unix.BRKINT 119 | s.termios.Iflag &^= unix.INLCR 120 | s.termios.Iflag &^= unix.IGNCR 121 | s.termios.Iflag &^= unix.ICRNL 122 | s.termios.Iflag &^= tcIUCLC 123 | 124 | s.termios.Oflag &^= unix.OPOST 125 | 126 | // Block reads until at least one char is available (no timeout) 127 | s.termios.Cc[unix.VMIN] = 1 128 | s.termios.Cc[unix.VTIME] = 0 129 | } 130 | -------------------------------------------------------------------------------- /unixutils/select_unix.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2014-2017 Cristian Maglie. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | // 6 | 7 | //go:build linux || darwin || freebsd || openbsd 8 | 9 | package unixutils 10 | 11 | import ( 12 | "time" 13 | 14 | "github.com/creack/goselect" 15 | ) 16 | 17 | // FDSet is a set of file descriptors suitable for a select call. 18 | type FDSet struct { 19 | set goselect.FDSet 20 | max uintptr 21 | } 22 | 23 | // NewFDSet creates a set of file descriptors suitable for a Select call. 24 | func NewFDSet(fds ...int) *FDSet { 25 | s := &FDSet{} 26 | s.Add(fds...) 27 | return s 28 | } 29 | 30 | // Add adds the file descriptors passed as parameter to the FDSet. 31 | func (s *FDSet) Add(fds ...int) { 32 | for _, fd := range fds { 33 | f := uintptr(fd) 34 | s.set.Set(f) 35 | if f > s.max { 36 | s.max = f 37 | } 38 | } 39 | } 40 | 41 | // FDResultSets contains the result of a Select operation. 42 | type FDResultSets struct { 43 | readable *goselect.FDSet 44 | writeable *goselect.FDSet 45 | errors *goselect.FDSet 46 | } 47 | 48 | // IsReadable test if a file descriptor is ready to be read. 49 | func (r *FDResultSets) IsReadable(fd int) bool { 50 | return r.readable.IsSet(uintptr(fd)) 51 | } 52 | 53 | // IsWritable test if a file descriptor is ready to be written. 54 | func (r *FDResultSets) IsWritable(fd int) bool { 55 | return r.writeable.IsSet(uintptr(fd)) 56 | } 57 | 58 | // IsError test if a file descriptor is in error state. 59 | func (r *FDResultSets) IsError(fd int) bool { 60 | return r.errors.IsSet(uintptr(fd)) 61 | } 62 | 63 | // Select performs a select system call, 64 | // file descriptors in the rd set are tested for read-events, 65 | // file descriptors in the wd set are tested for write-events and 66 | // file descriptors in the er set are tested for error-events. 67 | // The function will block until an event happens or the timeout expires. 68 | // The function return an FDResultSets that contains all the file descriptor 69 | // that have a pending read/write/error event. 70 | func Select(rd, wr, er *FDSet, timeout time.Duration) (*FDResultSets, error) { 71 | maxval := uintptr(0) 72 | res := &FDResultSets{} 73 | if rd != nil { 74 | // fdsets are copied so the parameters are left untouched 75 | copyOfRd := rd.set 76 | res.readable = ©OfRd 77 | // Determine max fd. 78 | maxval = rd.max 79 | } 80 | if wr != nil { 81 | // fdsets are copied so the parameters are left untouched 82 | copyOfWr := wr.set 83 | res.writeable = ©OfWr 84 | // Determine max fd. 85 | if wr.max > maxval { 86 | maxval = wr.max 87 | } 88 | } 89 | if er != nil { 90 | // fdsets are copied so the parameters are left untouched 91 | copyOfEr := er.set 92 | res.errors = ©OfEr 93 | // Determine max fd. 94 | if er.max > maxval { 95 | maxval = er.max 96 | } 97 | } 98 | 99 | err := goselect.Select(int(maxval+1), res.readable, res.writeable, res.errors, timeout) 100 | return res, err 101 | } 102 | -------------------------------------------------------------------------------- /zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package serial 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") 42 | modkernel32 = windows.NewLazySystemDLL("kernel32.dll") 43 | 44 | procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") 45 | procClearCommError = modkernel32.NewProc("ClearCommError") 46 | procCreateEventW = modkernel32.NewProc("CreateEventW") 47 | procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") 48 | procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") 49 | procGetCommState = modkernel32.NewProc("GetCommState") 50 | procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult") 51 | procPurgeComm = modkernel32.NewProc("PurgeComm") 52 | procResetEvent = modkernel32.NewProc("ResetEvent") 53 | procSetCommMask = modkernel32.NewProc("SetCommMask") 54 | procSetCommState = modkernel32.NewProc("SetCommState") 55 | procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") 56 | ) 57 | 58 | func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) { 59 | 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) 60 | if r0 != 0 { 61 | regerrno = syscall.Errno(r0) 62 | } 63 | return 64 | } 65 | 66 | func clearCommError(handle syscall.Handle, lpErrors *uint32, lpStat *comstat) (err error) { 67 | r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat))) 68 | if r1 == 0 { 69 | err = errnoErr(e1) 70 | } 71 | return 72 | } 73 | 74 | func createEvent(eventAttributes *uint32, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) { 75 | var _p0 uint32 76 | if manualReset { 77 | _p0 = 1 78 | } 79 | var _p1 uint32 80 | if initialState { 81 | _p1 = 1 82 | } 83 | r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(eventAttributes)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0) 84 | handle = syscall.Handle(r0) 85 | if handle == 0 { 86 | err = errnoErr(e1) 87 | } 88 | return 89 | } 90 | 91 | func escapeCommFunction(handle syscall.Handle, function uint32) (res bool) { 92 | r0, _, _ := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(function), 0) 93 | res = r0 != 0 94 | return 95 | } 96 | 97 | func getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) { 98 | r0, _, _ := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(bits)), 0) 99 | res = r0 != 0 100 | return 101 | } 102 | 103 | func getCommState(handle syscall.Handle, dcb *dcb) (err error) { 104 | r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0) 105 | if r1 == 0 { 106 | err = errnoErr(e1) 107 | } 108 | return 109 | } 110 | 111 | func getOverlappedResult(handle syscall.Handle, overlapEvent *syscall.Overlapped, n *uint32, wait bool) (err error) { 112 | var _p0 uint32 113 | if wait { 114 | _p0 = 1 115 | } 116 | r1, _, e1 := syscall.Syscall6(procGetOverlappedResult.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(overlapEvent)), uintptr(unsafe.Pointer(n)), uintptr(_p0), 0, 0) 117 | if r1 == 0 { 118 | err = errnoErr(e1) 119 | } 120 | return 121 | } 122 | 123 | func purgeComm(handle syscall.Handle, flags uint32) (err error) { 124 | r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(flags), 0) 125 | if r1 == 0 { 126 | err = errnoErr(e1) 127 | } 128 | return 129 | } 130 | 131 | func resetEvent(handle syscall.Handle) (err error) { 132 | r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(handle), 0, 0) 133 | if r1 == 0 { 134 | err = errnoErr(e1) 135 | } 136 | return 137 | } 138 | 139 | func setCommMask(handle syscall.Handle, mask uint32) (err error) { 140 | r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(mask), 0) 141 | if r1 == 0 { 142 | err = errnoErr(e1) 143 | } 144 | return 145 | } 146 | 147 | func setCommState(handle syscall.Handle, dcb *dcb) (err error) { 148 | r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0) 149 | if r1 == 0 { 150 | err = errnoErr(e1) 151 | } 152 | return 153 | } 154 | 155 | func setCommTimeouts(handle syscall.Handle, timeouts *commTimeouts) (err error) { 156 | r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) 157 | if r1 == 0 { 158 | err = errnoErr(e1) 159 | } 160 | return 161 | } 162 | --------------------------------------------------------------------------------