Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f10848f47f | |||
| b3711b62ad | |||
| ff4afcdddb | |||
| 27af1097f9 | |||
| 80d2794531 | |||
| 070b7685d5 | |||
|
|
f12391c01f | ||
|
|
8f447ebc07 | ||
|
|
20a47944cf | ||
|
|
9710c814f6 | ||
|
|
f0e4a45720 | ||
|
|
bcb0408701 | ||
|
|
5069d66aa2 | ||
|
|
3449d2e7f6 | ||
|
|
1ff9b6fa9a | ||
|
|
7dc6297645 | ||
|
|
fb4b111d50 | ||
|
|
b7483e31a7 | ||
|
|
0996f840dd | ||
|
|
f5a4685ea0 | ||
|
|
56ac2d4e76 | ||
|
|
1c72447e64 | ||
|
|
45e996e1b0 | ||
|
|
0b7848559a | ||
|
|
c18d387887 | ||
|
|
c768d77847 | ||
|
|
1282f62c6e | ||
|
|
4f7d935be3 | ||
|
|
03c961bc8a | ||
|
|
671075c6ac | ||
|
|
bac809c5a1 | ||
|
|
0925f99089 | ||
|
|
42bc112d18 | ||
|
|
259bdeb6c7 | ||
|
|
572f392ca9 | ||
|
|
14e5ea68ce | ||
|
|
bcd8695df4 | ||
|
|
92703ecb02 | ||
|
|
674fbae95a | ||
|
|
2aa105e32e | ||
|
|
7f490f208a |
78
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
78
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Source: https://github.com/arduino/tooling-project-assets/blob/main/issue-templates/forms/platform-dependent/bug-report.yml
|
||||||
|
# See: https://docs.github.com/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms
|
||||||
|
|
||||||
|
name: Bug report
|
||||||
|
description: Report a problem with the code or documentation in this repository.
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the problem
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce
|
||||||
|
attributes:
|
||||||
|
label: To reproduce
|
||||||
|
description: |
|
||||||
|
Provide the specific set of steps we can follow to reproduce the
|
||||||
|
problem in particular the exact golang source code you used.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist-reproduce
|
||||||
|
attributes:
|
||||||
|
label: |
|
||||||
|
Please double-check that you have reported each of the following
|
||||||
|
before submitting the issue.
|
||||||
|
options:
|
||||||
|
- label: I've provided the FULL source code that causes the problem
|
||||||
|
required: true
|
||||||
|
- label: I've provided all the actions required to reproduce the problem
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: |
|
||||||
|
What would you expect to happen after following those instructions?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating system and version
|
||||||
|
description: |
|
||||||
|
Which operating system(s) version are you using on your computer?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: boards
|
||||||
|
attributes:
|
||||||
|
label: Please describe your hardware setup
|
||||||
|
description: |
|
||||||
|
Arduino boards, USB dongles, hubs or embedded devices you are using and how they
|
||||||
|
are connected together.
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: |
|
||||||
|
Add here any additional information that you think might be relevant to
|
||||||
|
the problem.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: Issue checklist
|
||||||
|
description: |
|
||||||
|
Please double-check that you have done each of the following things before
|
||||||
|
submitting the issue.
|
||||||
|
options:
|
||||||
|
- label: I searched for previous requests in [the issue tracker](https://github.com/bugst/go-serial/issues)
|
||||||
|
required: true
|
||||||
|
- label: My request contains all necessary details
|
||||||
|
required: true
|
||||||
29
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
29
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Source: https://github.com/arduino/tooling-project-assets/blob/main/issue-templates/forms/platform-dependent/feature-request.yml
|
||||||
|
# See: https://docs.github.com/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms
|
||||||
|
|
||||||
|
name: Feature request
|
||||||
|
description: Suggest an enhancement to this project.
|
||||||
|
labels:
|
||||||
|
- "type: enhancement"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the new feature or change suggestion
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any additional information about the feature request here.
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: Issue checklist
|
||||||
|
description: Please double-check that you have done each of the following things before submitting the issue.
|
||||||
|
options:
|
||||||
|
- label: I searched for previous requests in [the issue tracker](https://github.com/bugst/go-serial/issues)
|
||||||
|
required: true
|
||||||
|
- label: My request contains all necessary details
|
||||||
|
required: true
|
||||||
23
.github/workflows/test.yaml
vendored
23
.github/workflows/test.yaml
vendored
@@ -15,13 +15,22 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v1
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.17"
|
go-version: "1.22"
|
||||||
- name: Build native
|
- name: Setup CGO Environment
|
||||||
|
run: |
|
||||||
|
if [ ${{ matrix.os }} == 'macOS-latest' ] ; then
|
||||||
|
echo "CGO_ENABLED=1" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
- name: Build AMD64
|
||||||
run: GOARCH=amd64 go build -v ./...
|
run: GOARCH=amd64 go build -v ./...
|
||||||
shell: bash
|
shell: bash
|
||||||
|
- name: Build ARM64
|
||||||
|
run: GOARCH=arm64 go build -v ./...
|
||||||
|
shell: bash
|
||||||
- name: Install socat
|
- name: Install socat
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: sudo apt-get install socat
|
run: sudo apt-get install socat
|
||||||
@@ -51,10 +60,10 @@ jobs:
|
|||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v1
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.17"
|
go-version: "1.22"
|
||||||
- name: Cross-build
|
- name: Cross-build
|
||||||
run: |
|
run: |
|
||||||
set ${{ matrix.go-os-pairs }}
|
set ${{ matrix.go-os-pairs }}
|
||||||
|
|||||||
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
# IDE directories and files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Test directory
|
||||||
|
testprogram/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
*.err
|
||||||
|
*.out.log
|
||||||
|
|
||||||
|
# Configuration files with sensitive information
|
||||||
|
*.local
|
||||||
|
*.secret
|
||||||
|
*.env
|
||||||
|
.claude/settings.local.json
|
||||||
169
CLAUDE.md
Normal file
169
CLAUDE.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## 开发者约束要求
|
||||||
|
|
||||||
|
### 语言与地区
|
||||||
|
- **主要交互语言**: 中文(简体)
|
||||||
|
- **开发者所在地**: 中国大陆
|
||||||
|
- **时区**: GMT+8(北京时间)
|
||||||
|
|
||||||
|
### AI Agent交互规范
|
||||||
|
- 使用中文进行所有AI Agent交互和代码解释
|
||||||
|
- 文档和注释可以使用英文(保持与现有代码库风格一致)
|
||||||
|
- 技术术语遵循行业标准翻译
|
||||||
|
|
||||||
|
### Git提交规范
|
||||||
|
- **提交信息必须包含中文说明**:确保每个commit都有清晰的中文描述
|
||||||
|
- 提交信息格式:`<类型>: <主题>`,主题使用中文
|
||||||
|
- 提交信息应简洁明了,准确描述变更内容
|
||||||
|
- 类型可以是:feat(新功能)、fix(修复)、docs(文档)、style(样式)、refactor(重构)、test(测试)、chore(构建过程或辅助工具的变动)
|
||||||
|
|
||||||
|
### 代码库使用注意事项
|
||||||
|
- 本项目是一个开源Go库,主要用于跨平台串口通信
|
||||||
|
- 遵守BSD 3-clause开源许可证
|
||||||
|
- 保持代码的跨平台兼容性和简洁性
|
||||||
|
- 所有修改需要通过GitHub PR流程进行
|
||||||
|
|
||||||
|
### 代码库使用注意事项
|
||||||
|
- 本项目是一个开源Go库,主要用于跨平台串口通信
|
||||||
|
- 遵守BSD 3-clause开源许可证
|
||||||
|
- 保持代码的跨平台兼容性和简洁性
|
||||||
|
- 所有修改需要通过GitHub PR流程进行
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**go-serial** is a cross-platform serial port communication library for Go. It provides a simple and consistent API for accessing serial ports on Windows, macOS, Linux, and other Unix-like systems.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- Cross-platform support (Windows, macOS, Linux, FreeBSD, OpenBSD)
|
||||||
|
- Simple API for opening and configuring serial ports
|
||||||
|
- Support for modem control lines (RTS, DTR, CTS, DSR, etc.)
|
||||||
|
- USB device enumeration with VID/PID and serial number detection
|
||||||
|
- Read/write timeout support
|
||||||
|
- Parity, data bits, stop bits configuration
|
||||||
|
- Break signal support
|
||||||
|
|
||||||
|
## Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
go-serial/
|
||||||
|
├── serial.go # Core API definition (Port interface, Mode, errors)
|
||||||
|
├── serial_*.go # Platform-specific implementations (windows, darwin, linux, bsd, wasm)
|
||||||
|
├── enumerator/ # USB serial port enumeration with detailed info
|
||||||
|
│ ├── enumerator.go # Core enumerator API
|
||||||
|
│ └── usb_*.go # Platform-specific USB enumeration
|
||||||
|
├── portlist/ # Simple port listing implementation
|
||||||
|
├── unixutils/ # Unix-specific utility functions
|
||||||
|
└── *.test.go # Example and test files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core API
|
||||||
|
|
||||||
|
### Opening a Serial Port
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "go.bug.st/serial"
|
||||||
|
|
||||||
|
mode := &serial.Mode{
|
||||||
|
BaudRate: 115200,
|
||||||
|
Parity: serial.EvenParity,
|
||||||
|
DataBits: 7,
|
||||||
|
StopBits: serial.OneStopBit,
|
||||||
|
}
|
||||||
|
port, err := serial.Open("/dev/ttyUSB0", mode)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer port.Close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reading and Writing
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Write data
|
||||||
|
n, err := port.Write([]byte("Hello"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data with timeout
|
||||||
|
buff := make([]byte, 100)
|
||||||
|
port.SetReadTimeout(5 * time.Second)
|
||||||
|
n, err := port.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getting Port List
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Simple port list
|
||||||
|
ports, err := serial.GetPortsList()
|
||||||
|
|
||||||
|
// Detailed port list with USB info
|
||||||
|
import "go.bug.st/serial/enumerator"
|
||||||
|
ports, err := enumerator.GetDetailedPortsList()
|
||||||
|
for _, port := range ports {
|
||||||
|
if port.IsUSB {
|
||||||
|
fmt.Printf("USB Port: %s (VID:%s PID:%s Serial:%s)\n",
|
||||||
|
port.Name, port.VID, port.PID, port.SerialNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test -v ./... # Run all tests
|
||||||
|
go test -v ./enumerator # Run enumerator tests specifically
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go doc go.bug.st/serial # Show package documentation
|
||||||
|
go doc go.bug.st/serial.Port # Show Port interface documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generating Windows Syscall Bindings
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd go-serial
|
||||||
|
go generate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform-Specific Notes
|
||||||
|
|
||||||
|
- **Windows**: Uses Windows API for serial communication, supports most USB-to-serial adapters
|
||||||
|
- **macOS**: Uses IOKit framework (requires cgo) for USB enumeration
|
||||||
|
- **Linux**: Supports standard serial ports and USB-to-serial devices via `/dev/tty*`
|
||||||
|
- **Unix-like systems**: Use termios API for configuration
|
||||||
|
- **Wasm**: Limited support (enumeration not implemented)
|
||||||
|
|
||||||
|
## Recent Changes
|
||||||
|
|
||||||
|
The repository has recent commits related to:
|
||||||
|
- Fixing serial port getter methods
|
||||||
|
- Making Windows internal constants private
|
||||||
|
- Fixing incorrect masks for modem status bits on Windows
|
||||||
|
- Renaming variables to match documentation
|
||||||
|
- Windows port enumeration improvements
|
||||||
|
- **Linux系统下串口Close方法深度优化**:
|
||||||
|
- 优化了资源释放顺序,先释放独占访问权,再关闭实际的端口句柄
|
||||||
|
- 添加了句柄有效性检查,避免对无效句柄进行操作
|
||||||
|
- 确保所有资源都能正确清理,包括句柄和信号管道
|
||||||
|
- 改进了错误处理机制,确保只返回第一个错误
|
||||||
|
- 为所有方法添加了端口状态检查,确保在端口关闭后立即返回PortClosed错误
|
||||||
|
- 统一了错误处理格式,使用PortError类型包装系统错误
|
||||||
|
- 优化了Read方法的错误处理和超时逻辑
|
||||||
|
- 为Write、Break、SetMode、SetDTR、SetRTS和GetModemStatusBits方法添加了端口状态检查
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD 3-clause license - see LICENSE file for details.
|
||||||
2
LICENSE
2
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
Copyright (c) 2014-2023, Cristian Maglie.
|
Copyright (c) 2014-2024, Cristian Maglie.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
# go.bug.st/serial
|
# go.bug.st/serial
|
||||||
|
|
||||||
A cross-platform serial library for go-lang.
|
A cross-platform serial port library for Go.
|
||||||
|
|
||||||
## Documentation and examples
|
## Documentation and examples
|
||||||
|
|
||||||
See the godoc here: https://godoc.org/go.bug.st/serial
|
See the package documentation here: https://pkg.go.dev/go.bug.st/serial
|
||||||
|
|
||||||
## go.mod transition
|
## go.mod transition
|
||||||
|
|
||||||
This library now support `go.mod` with the import `go.bug.st/serial`.
|
This library supports `go.mod` with the import `go.bug.st/serial`.
|
||||||
|
|
||||||
If you came from the pre-`go.mod` era please update your import paths from `go.bug.st/serial.v1` to `go.bug.st/serial` to receive new updates. Anyway, the latest `v1` release should still be avaiable using the old import.
|
If you came from the pre-`go.mod` era please update your import paths from `go.bug.st/serial.v1` to `go.bug.st/serial` to receive updates. The latest `v1` release is still available using the old import path.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ If you came from the pre-`go.mod` era please update your import paths from `go.b
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The software is release under a [BSD 3-clause license]
|
This software is released under the [BSD 3-clause license].
|
||||||
|
|
||||||
[contributors]: https://github.com/bugst/go-serial/graphs/contributors
|
[contributors]: https://github.com/bugst/go-serial/graphs/contributors
|
||||||
[BSD 3-clause license]: https://github.com/bugst/go-serial/blob/master/LICENSE
|
[BSD 3-clause license]: https://github.com/bugst/go-serial/blob/master/LICENSE
|
||||||
|
|||||||
2
doc.go
2
doc.go
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
11
enumerator/usb_wasm.go
Normal file
11
enumerator/usb_wasm.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
|
||||||
|
package enumerator
|
||||||
|
|
||||||
|
func nativeGetDetailedPortsList() ([]*PortDetails, error) {
|
||||||
|
return nil, &PortEnumerationError{}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -98,7 +98,7 @@ const (
|
|||||||
spdrpUpperFilters = 0x00000011 // UpperFilters = R/W
|
spdrpUpperFilters = 0x00000011 // UpperFilters = R/W
|
||||||
spdrpLowerFilters = 0x00000012 // LowerFilters = R/W
|
spdrpLowerFilters = 0x00000012 // LowerFilters = R/W
|
||||||
spdrpBusTypeGUID = 0x00000013 // BusTypeGUID = R
|
spdrpBusTypeGUID = 0x00000013 // BusTypeGUID = R
|
||||||
spdrpLegactBusType = 0x00000014 // LegacyBusType = R
|
spdrpLegacyBusType = 0x00000014 // LegacyBusType = R
|
||||||
spdrpBusNumber = 0x00000015 // BusNumber = R
|
spdrpBusNumber = 0x00000015 // BusNumber = R
|
||||||
spdrpEnumeratorName = 0x00000016 // Enumerator Name = R
|
spdrpEnumeratorName = 0x00000016 // Enumerator Name = R
|
||||||
spdrpSecurity = 0x00000017 // Security = R/W, binary form
|
spdrpSecurity = 0x00000017 // Security = R/W, binary form
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
15
enumerator_wasm.go
Normal file
15
enumerator_wasm.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
|
||||||
|
package serial
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nativeGetPortsList() ([]string, error) {
|
||||||
|
return nil, errors.New("nativeGetPortsList is not supported on wasm")
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleGetSetModemBits() {
|
func ExamplePort_GetModemStatusBits() {
|
||||||
// Open the first serial port detected at 9600bps N81
|
// Open the first serial port detected at 9600bps N81
|
||||||
mode := &serial.Mode{
|
mode := &serial.Mode{
|
||||||
BaudRate: 9600,
|
BaudRate: 9600,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleSerialPort_SetMode() {
|
func ExamplePort_SetMode() {
|
||||||
port, err := serial.Open("/dev/ttyACM0", &serial.Mode{})
|
port, err := serial.Open("/dev/ttyACM0", &serial.Mode{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -4,12 +4,12 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/creack/goselect v0.1.2
|
github.com/creack/goselect v0.1.2
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
|
golang.org/x/sys v0.19.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
20
go.sum
20
go.sum
@@ -1,17 +1,21 @@
|
|||||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// Port: /dev/cu.usbmodemFD121
|
// Port: /dev/cu.usbmodemFD121
|
||||||
// USB ID 2341:8053
|
// USB ID 2341:8053
|
||||||
// USB serial FB7B6060504B5952302E314AFF08191A
|
// USB serial FB7B6060504B5952302E314AFF08191A
|
||||||
//
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -147,7 +147,7 @@ const (
|
|||||||
PortNotFound
|
PortNotFound
|
||||||
// InvalidSerialPort the requested port is not a serial port
|
// InvalidSerialPort the requested port is not a serial port
|
||||||
InvalidSerialPort
|
InvalidSerialPort
|
||||||
// PermissionDenied the user doesn't have enough priviledges
|
// PermissionDenied the user doesn't have enough privileges
|
||||||
PermissionDenied
|
PermissionDenied
|
||||||
// InvalidSpeed the requested speed is not valid or not supported
|
// InvalidSpeed the requested speed is not valid or not supported
|
||||||
InvalidSpeed
|
InvalidSpeed
|
||||||
@@ -163,6 +163,8 @@ const (
|
|||||||
ErrorEnumeratingPorts
|
ErrorEnumeratingPorts
|
||||||
// PortClosed the port has been closed while the operation is in progress
|
// PortClosed the port has been closed while the operation is in progress
|
||||||
PortClosed
|
PortClosed
|
||||||
|
// Timeout the read operation timed out
|
||||||
|
Timeout
|
||||||
// FunctionNotImplemented the requested function is not implemented
|
// FunctionNotImplemented the requested function is not implemented
|
||||||
FunctionNotImplemented
|
FunctionNotImplemented
|
||||||
)
|
)
|
||||||
@@ -192,6 +194,8 @@ func (e PortError) EncodedErrorString() string {
|
|||||||
return "Could not enumerate serial ports"
|
return "Could not enumerate serial ports"
|
||||||
case PortClosed:
|
case PortClosed:
|
||||||
return "Port has been closed"
|
return "Port has been closed"
|
||||||
|
case Timeout:
|
||||||
|
return "Read timeout"
|
||||||
case FunctionNotImplemented:
|
case FunctionNotImplemented:
|
||||||
return "Function not implemented"
|
return "Function not implemented"
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
||||||
package serial
|
package serial
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
const devFolder = "/dev"
|
const devFolder = "/dev"
|
||||||
const regexFilter = "^(cu|tty)\\..*"
|
|
||||||
|
var osPortFilter = regexp.MustCompile("^(cu|tty)\\..*")
|
||||||
|
|
||||||
const ioctlTcgetattr = unix.TIOCGETA
|
const ioctlTcgetattr = unix.TIOCGETA
|
||||||
const ioctlTcsetattr = unix.TIOCSETA
|
const ioctlTcsetattr = unix.TIOCSETA
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
||||||
package serial
|
package serial
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
const devFolder = "/dev"
|
const devFolder = "/dev"
|
||||||
const regexFilter = "^(cu|tty)\\..*"
|
|
||||||
|
var osPortFilter = regexp.MustCompile("^(cu|tty)\\..*")
|
||||||
|
|
||||||
// termios manipulation functions
|
// termios manipulation functions
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
||||||
package serial
|
package serial
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
const devFolder = "/dev"
|
const devFolder = "/dev"
|
||||||
const regexFilter = "(ttyS|ttyHS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO|ttymxc)[0-9]{1,3}"
|
|
||||||
|
var osPortFilter = regexp.MustCompile("(ttyS|ttyHS|ttyUSB|ttyACM|ttyAMA|rfcomm|ttyO|ttymxc)[0-9]{1,3}")
|
||||||
|
|
||||||
// termios manipulation functions
|
// termios manipulation functions
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
||||||
package serial
|
package serial
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
const devFolder = "/dev"
|
const devFolder = "/dev"
|
||||||
const regexFilter = "^(cu|tty)\\..*"
|
|
||||||
|
var osPortFilter = regexp.MustCompile("^(cu|tty)\\..*")
|
||||||
|
|
||||||
// termios manipulation functions
|
// termios manipulation functions
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
188
serial_unix.go
188
serial_unix.go
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -10,8 +10,7 @@ package serial
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -31,35 +30,48 @@ type unixPort struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) Close() error {
|
func (port *unixPort) Close() error {
|
||||||
|
// 原子操作检查并设置端口关闭状态
|
||||||
if !atomic.CompareAndSwapUint32(&port.opened, 1, 0) {
|
if !atomic.CompareAndSwapUint32(&port.opened, 1, 0) {
|
||||||
return nil
|
return nil // 端口已经关闭,直接返回
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close port
|
var firstErr error
|
||||||
port.releaseExclusiveAccess()
|
|
||||||
if err := unix.Close(port.handle); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 发送关闭信号以取消所有待处理的读操作
|
||||||
if port.closeSignal != nil {
|
if port.closeSignal != nil {
|
||||||
// Send close signal to all pending reads (if any)
|
if _, err := port.closeSignal.Write([]byte{0}); err != nil && firstErr == nil {
|
||||||
port.closeSignal.Write([]byte{0})
|
firstErr = err
|
||||||
|
|
||||||
// Wait for all readers to complete
|
|
||||||
port.closeLock.Lock()
|
|
||||||
defer port.closeLock.Unlock()
|
|
||||||
|
|
||||||
// Close signaling pipe
|
|
||||||
if err := port.closeSignal.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// 检查句柄是否有效,然后执行关闭操作
|
||||||
|
if port.handle != -1 {
|
||||||
|
// 释放独占访问权 - 应在关闭handle之前完成
|
||||||
|
if err := port.releaseExclusiveAccess(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭实际的端口句柄 - 这将解除任何挂起的读写操作
|
||||||
|
if err := unix.Close(port.handle); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
port.handle = -1 // 标记句柄无效
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭信号管道
|
||||||
|
if port.closeSignal != nil {
|
||||||
|
if err := port.closeSignal.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
port.closeSignal = nil // 清除指针
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) Read(p []byte) (int, error) {
|
func (port *unixPort) Read(p []byte) (int, error) {
|
||||||
port.closeLock.RLock()
|
// 首先检查端口是否已经关闭
|
||||||
defer port.closeLock.RUnlock()
|
|
||||||
if atomic.LoadUint32(&port.opened) != 1 {
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
return 0, &PortError{code: PortClosed}
|
return 0, &PortError{code: PortClosed}
|
||||||
}
|
}
|
||||||
@@ -71,104 +83,158 @@ func (port *unixPort) Read(p []byte) (int, error) {
|
|||||||
|
|
||||||
fds := unixutils.NewFDSet(port.handle, port.closeSignal.ReadFD())
|
fds := unixutils.NewFDSet(port.handle, port.closeSignal.ReadFD())
|
||||||
for {
|
for {
|
||||||
|
// 在每次select之前再次检查端口状态(非阻塞检查)
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return 0, &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
timeout := time.Duration(-1)
|
timeout := time.Duration(-1)
|
||||||
if port.readTimeout != NoTimeout {
|
if port.readTimeout != NoTimeout {
|
||||||
timeout = time.Until(deadline)
|
timeout = time.Until(deadline)
|
||||||
if timeout < 0 {
|
if timeout < 0 {
|
||||||
// a negative timeout means "no-timeout" in Select(...)
|
// 负超时值在Select(...)中表示"无超时"
|
||||||
timeout = 0
|
timeout = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := unixutils.Select(fds, nil, fds, timeout)
|
res, err := unixutils.Select(fds, nil, fds, timeout)
|
||||||
if err == unix.EINTR {
|
if err == unix.EINTR {
|
||||||
continue
|
continue // 系统调用被中断,重试
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
// 如果在端口关闭后遇到错误,返回PortClosed错误
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return 0, &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
return 0, &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.IsReadable(port.closeSignal.ReadFD()) {
|
if res.IsReadable(port.closeSignal.ReadFD()) {
|
||||||
|
// 收到关闭信号
|
||||||
return 0, &PortError{code: PortClosed}
|
return 0, &PortError{code: PortClosed}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res.IsReadable(port.handle) {
|
if !res.IsReadable(port.handle) {
|
||||||
// Timeout happened
|
// 超时
|
||||||
return 0, nil
|
if port.readTimeout == NoTimeout {
|
||||||
|
continue // 无超时设置,继续等待
|
||||||
|
}
|
||||||
|
// 超时应返回 Timeout 错误,而不是 (0, nil)
|
||||||
|
return 0, &PortError{code: Timeout, causedBy: unix.ETIMEDOUT}
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := unix.Read(port.handle, p)
|
n, err := unix.Read(port.handle, p)
|
||||||
if err == unix.EINTR {
|
if err == unix.EINTR {
|
||||||
continue
|
continue // 系统调用被中断,重试
|
||||||
}
|
}
|
||||||
// Linux: when the port is disconnected during a read operation
|
// Linux系统特性:当读取操作期间端口断开连接时,
|
||||||
// the port is left in a "readable with zero-length-data" state.
|
// 端口会进入"可读但返回零长度数据"的状态。
|
||||||
// https://stackoverflow.com/a/34945814/1655275
|
// https://stackoverflow.com/a/34945814/1655275
|
||||||
if n == 0 && err == nil {
|
if n == 0 && err == nil {
|
||||||
return 0, &PortError{code: PortClosed}
|
return 0, &PortError{code: PortClosed}
|
||||||
}
|
}
|
||||||
if n < 0 { // Do not return -1 unix errors
|
if n < 0 { // 确保不返回负数
|
||||||
n = 0
|
n = 0
|
||||||
}
|
}
|
||||||
return n, err
|
// 检查读取操作期间端口是否被关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return 0, &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return n, &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) Write(p []byte) (n int, err error) {
|
func (port *unixPort) Write(p []byte) (n int, err error) {
|
||||||
|
// 首先检查端口是否已经关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return 0, &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
n, err = unix.Write(port.handle, p)
|
n, err = unix.Write(port.handle, p)
|
||||||
if n < 0 { // Do not return -1 unix errors
|
if n < 0 { // 确保不返回负数
|
||||||
n = 0
|
n = 0
|
||||||
}
|
}
|
||||||
return
|
if err != nil {
|
||||||
|
return n, &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) Break(t time.Duration) error {
|
func (port *unixPort) Break(t time.Duration) error {
|
||||||
|
// 检查端口是否已经关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
if err := unix.IoctlSetInt(port.handle, ioctlTiocsbrk, 0); err != nil {
|
if err := unix.IoctlSetInt(port.handle, ioctlTiocsbrk, 0); err != nil {
|
||||||
return err
|
return &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(t)
|
time.Sleep(t)
|
||||||
|
|
||||||
if err := unix.IoctlSetInt(port.handle, ioctlTioccbrk, 0); err != nil {
|
if err := unix.IoctlSetInt(port.handle, ioctlTioccbrk, 0); err != nil {
|
||||||
return err
|
return &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) SetMode(mode *Mode) error {
|
func (port *unixPort) SetMode(mode *Mode) error {
|
||||||
|
// 检查端口是否已经关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
settings, err := port.getTermSettings()
|
settings, err := port.getTermSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &PortError{code: InvalidSerialPort, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setTermSettingsParity(mode.Parity, settings); err != nil {
|
if err := setTermSettingsParity(mode.Parity, settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setTermSettingsDataBits(mode.DataBits, settings); err != nil {
|
if err := setTermSettingsDataBits(mode.DataBits, settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setTermSettingsStopBits(mode.StopBits, settings); err != nil {
|
if err := setTermSettingsStopBits(mode.StopBits, settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
requireSpecialBaudrate := false
|
requireSpecialBaudrate := false
|
||||||
if err, special := setTermSettingsBaudrate(mode.BaudRate, settings); err != nil {
|
if err, special := setTermSettingsBaudrate(mode.BaudRate, settings); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if special {
|
} else if special {
|
||||||
requireSpecialBaudrate = true
|
requireSpecialBaudrate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := port.setTermSettings(settings); err != nil {
|
if err := port.setTermSettings(settings); err != nil {
|
||||||
return err
|
return &PortError{code: InvalidSerialPort, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if requireSpecialBaudrate {
|
if requireSpecialBaudrate {
|
||||||
// MacOSX require this one to be the last operation otherwise an
|
// MacOSX要求这是最后一个操作,否则会产生'Invalid serial port'错误
|
||||||
// 'Invalid serial port' error is produced.
|
|
||||||
if err := port.setSpecialBaudrate(uint32(mode.BaudRate)); err != nil {
|
if err := port.setSpecialBaudrate(uint32(mode.BaudRate)); err != nil {
|
||||||
return err
|
return &PortError{code: InvalidSpeed, causedBy: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) SetDTR(dtr bool) error {
|
func (port *unixPort) SetDTR(dtr bool) error {
|
||||||
|
// 检查端口是否已经关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
status, err := port.getModemBitsStatus()
|
status, err := port.getModemBitsStatus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
}
|
}
|
||||||
if dtr {
|
if dtr {
|
||||||
status |= unix.TIOCM_DTR
|
status |= unix.TIOCM_DTR
|
||||||
@@ -179,9 +245,14 @@ func (port *unixPort) SetDTR(dtr bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) SetRTS(rts bool) error {
|
func (port *unixPort) SetRTS(rts bool) error {
|
||||||
|
// 检查端口是否已经关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
status, err := port.getModemBitsStatus()
|
status, err := port.getModemBitsStatus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
}
|
}
|
||||||
if rts {
|
if rts {
|
||||||
status |= unix.TIOCM_RTS
|
status |= unix.TIOCM_RTS
|
||||||
@@ -200,10 +271,16 @@ func (port *unixPort) SetReadTimeout(timeout time.Duration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (port *unixPort) GetModemStatusBits() (*ModemStatusBits, error) {
|
func (port *unixPort) GetModemStatusBits() (*ModemStatusBits, error) {
|
||||||
|
// 检查端口是否已经关闭
|
||||||
|
if atomic.LoadUint32(&port.opened) != 1 {
|
||||||
|
return nil, &PortError{code: PortClosed}
|
||||||
|
}
|
||||||
|
|
||||||
status, err := port.getModemBitsStatus()
|
status, err := port.getModemBitsStatus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &PortError{code: FunctionNotImplemented, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ModemStatusBits{
|
return &ModemStatusBits{
|
||||||
CTS: (status & unix.TIOCM_CTS) != 0,
|
CTS: (status & unix.TIOCM_CTS) != 0,
|
||||||
DCD: (status & unix.TIOCM_CD) != 0,
|
DCD: (status & unix.TIOCM_CD) != 0,
|
||||||
@@ -242,7 +319,7 @@ func nativeOpen(portName string, mode *Mode) (*unixPort, error) {
|
|||||||
// Explicitly disable RTS/CTS flow control
|
// Explicitly disable RTS/CTS flow control
|
||||||
setTermSettingsCtsRts(false, settings)
|
setTermSettingsCtsRts(false, settings)
|
||||||
|
|
||||||
if port.setTermSettings(settings) != nil {
|
if err = port.setTermSettings(settings); err != nil {
|
||||||
port.Close()
|
port.Close()
|
||||||
return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error setting term settings: %w", err)}
|
return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error setting term settings: %w", err)}
|
||||||
}
|
}
|
||||||
@@ -276,7 +353,9 @@ func nativeOpen(portName string, mode *Mode) (*unixPort, error) {
|
|||||||
return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error configuring port: %w", err)}
|
return nil, &PortError{code: InvalidSerialPort, causedBy: fmt.Errorf("error configuring port: %w", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
unix.SetNonblock(h, false)
|
// Set non-blocking mode to ensure read() doesn't block after select() returns
|
||||||
|
// The select() call provides the timeout mechanism, not the read() call
|
||||||
|
unix.SetNonblock(h, true)
|
||||||
|
|
||||||
port.acquireExclusiveAccess()
|
port.acquireExclusiveAccess()
|
||||||
|
|
||||||
@@ -292,16 +371,12 @@ func nativeOpen(portName string, mode *Mode) (*unixPort, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nativeGetPortsList() ([]string, error) {
|
func nativeGetPortsList() ([]string, error) {
|
||||||
files, err := ioutil.ReadDir(devFolder)
|
files, err := os.ReadDir(devFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ports := make([]string, 0, len(files))
|
ports := make([]string, 0, len(files))
|
||||||
regex, err := regexp.Compile(regexFilter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
// Skip folders
|
// Skip folders
|
||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
@@ -309,7 +384,7 @@ func nativeGetPortsList() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keep only devices with the correct name
|
// Keep only devices with the correct name
|
||||||
if !regex.MatchString(f.Name()) {
|
if !osPortFilter.MatchString(f.Name()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +516,14 @@ func setRawMode(settings *unix.Termios) {
|
|||||||
settings.Oflag &^= unix.OPOST
|
settings.Oflag &^= unix.OPOST
|
||||||
|
|
||||||
// Block reads until at least one char is available (no timeout)
|
// Block reads until at least one char is available (no timeout)
|
||||||
settings.Cc[unix.VMIN] = 1
|
// VMIN=1: Block until at least 1 byte is available
|
||||||
|
// VTIME=0: No inter-character timeout (not used with VMIN=1)
|
||||||
|
//
|
||||||
|
// NOTE: When readTimeout is set, the select() system call provides the timeout
|
||||||
|
// and VMIN/VTIME settings are not used for timeout purposes.
|
||||||
|
// However, VMIN=1 can cause issues with select() on some platforms/
|
||||||
|
// terminal drivers. Using VMIN=0 allows select() to work reliably.
|
||||||
|
settings.Cc[unix.VMIN] = 0
|
||||||
settings.Cc[unix.VTIME] = 0
|
settings.Cc[unix.VTIME] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
serial_wasm.go
Normal file
15
serial_wasm.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
|
||||||
|
package serial
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nativeOpen(portName string, mode *Mode) (Port, error) {
|
||||||
|
return nil, errors.New("nativeOpen is not supported on wasm")
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -18,48 +18,50 @@ package serial
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type windowsPort struct {
|
type windowsPort struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
handle syscall.Handle
|
handle windows.Handle
|
||||||
|
hasTimeout bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func nativeGetPortsList() ([]string, error) {
|
func nativeGetPortsList() ([]string, error) {
|
||||||
subKey, err := syscall.UTF16PtrFromString("HARDWARE\\DEVICEMAP\\SERIALCOMM\\")
|
key, err := registry.OpenKey(windows.HKEY_LOCAL_MACHINE, `HARDWARE\DEVICEMAP\SERIALCOMM\`, windows.KEY_READ)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, syscall.ERROR_FILE_NOT_FOUND):
|
||||||
|
// On machines with no serial ports the registry key does not exist.
|
||||||
|
// Return this as no serial ports instead of an error.
|
||||||
|
return nil, nil
|
||||||
|
case err != nil:
|
||||||
|
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
|
||||||
|
names, err := key.ReadValueNames(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &PortError{code: ErrorEnumeratingPorts}
|
return nil, &PortError{code: ErrorEnumeratingPorts, causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
var h syscall.Handle
|
var values []string
|
||||||
if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, subKey, 0, syscall.KEY_READ, &h); err != nil {
|
for _, n := range names {
|
||||||
if errno, isErrno := err.(syscall.Errno); isErrno && errno == syscall.ERROR_FILE_NOT_FOUND {
|
v, _, err := key.GetStringValue(n)
|
||||||
return []string{}, nil
|
if err != nil || v == "" {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return nil, &PortError{code: ErrorEnumeratingPorts}
|
|
||||||
}
|
|
||||||
defer syscall.RegCloseKey(h)
|
|
||||||
|
|
||||||
var valuesCount uint32
|
values = append(values, v)
|
||||||
if syscall.RegQueryInfoKey(h, nil, nil, nil, nil, nil, nil, &valuesCount, nil, nil, nil, nil) != nil {
|
|
||||||
return nil, &PortError{code: ErrorEnumeratingPorts}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list := make([]string, valuesCount)
|
return values, nil
|
||||||
for i := range list {
|
|
||||||
var data [1024]uint16
|
|
||||||
dataSize := uint32(len(data))
|
|
||||||
var name [1024]uint16
|
|
||||||
nameSize := uint32(len(name))
|
|
||||||
if regEnumValue(h, uint32(i), &name[0], &nameSize, nil, nil, &data[0], &dataSize) != nil {
|
|
||||||
return nil, &PortError{code: ErrorEnumeratingPorts}
|
|
||||||
}
|
|
||||||
list[i] = syscall.UTF16ToString(data[:])
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) Close() error {
|
func (port *windowsPort) Close() error {
|
||||||
@@ -71,7 +73,7 @@ func (port *windowsPort) Close() error {
|
|||||||
if port.handle == 0 {
|
if port.handle == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return syscall.CloseHandle(port.handle)
|
return windows.CloseHandle(port.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) Read(p []byte) (int, error) {
|
func (port *windowsPort) Read(p []byte) (int, error) {
|
||||||
@@ -80,28 +82,35 @@ func (port *windowsPort) Read(p []byte) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer syscall.CloseHandle(ev.HEvent)
|
defer windows.CloseHandle(ev.HEvent)
|
||||||
|
|
||||||
err = syscall.ReadFile(port.handle, p, &readed, ev)
|
for {
|
||||||
if err == syscall.ERROR_IO_PENDING {
|
err = windows.ReadFile(port.handle, p, &readed, ev)
|
||||||
err = getOverlappedResult(port.handle, ev, &readed, true)
|
if err == windows.ERROR_IO_PENDING {
|
||||||
}
|
err = windows.GetOverlappedResult(port.handle, ev, &readed, true)
|
||||||
switch err {
|
}
|
||||||
case nil:
|
switch err {
|
||||||
// operation completed successfully
|
case nil:
|
||||||
case syscall.ERROR_OPERATION_ABORTED:
|
// operation completed successfully
|
||||||
// port may have been closed
|
case windows.ERROR_OPERATION_ABORTED:
|
||||||
return int(readed), &PortError{code: PortClosed, causedBy: err}
|
// port may have been closed
|
||||||
default:
|
return int(readed), &PortError{code: PortClosed, causedBy: err}
|
||||||
// error happened
|
default:
|
||||||
return int(readed), err
|
// error happened
|
||||||
}
|
return int(readed), err
|
||||||
if readed > 0 {
|
}
|
||||||
return int(readed), nil
|
if readed > 0 {
|
||||||
}
|
return int(readed), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Timeout
|
// Timeout
|
||||||
return 0, nil
|
port.mu.Lock()
|
||||||
|
hasTimeout := port.hasTimeout
|
||||||
|
port.mu.Unlock()
|
||||||
|
if hasTimeout {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) Write(p []byte) (int, error) {
|
func (port *windowsPort) Write(p []byte) (int, error) {
|
||||||
@@ -110,32 +119,25 @@ func (port *windowsPort) Write(p []byte) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer syscall.CloseHandle(ev.HEvent)
|
defer windows.CloseHandle(ev.HEvent)
|
||||||
err = syscall.WriteFile(port.handle, p, &writed, ev)
|
err = windows.WriteFile(port.handle, p, &writed, ev)
|
||||||
if err == syscall.ERROR_IO_PENDING {
|
if err == windows.ERROR_IO_PENDING {
|
||||||
// wait for write to complete
|
// wait for write to complete
|
||||||
err = getOverlappedResult(port.handle, ev, &writed, true)
|
err = windows.GetOverlappedResult(port.handle, ev, &writed, true)
|
||||||
}
|
}
|
||||||
return int(writed), err
|
return int(writed), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) Drain() (err error) {
|
func (port *windowsPort) Drain() (err error) {
|
||||||
return syscall.FlushFileBuffers(port.handle)
|
return windows.FlushFileBuffers(port.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
purgeRxAbort uint32 = 0x0002
|
|
||||||
purgeRxClear = 0x0008
|
|
||||||
purgeTxAbort = 0x0001
|
|
||||||
purgeTxClear = 0x0004
|
|
||||||
)
|
|
||||||
|
|
||||||
func (port *windowsPort) ResetInputBuffer() error {
|
func (port *windowsPort) ResetInputBuffer() error {
|
||||||
return purgeComm(port.handle, purgeRxClear|purgeRxAbort)
|
return windows.PurgeComm(port.handle, windows.PURGE_RXCLEAR|windows.PURGE_RXABORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) ResetOutputBuffer() error {
|
func (port *windowsPort) ResetOutputBuffer() error {
|
||||||
return purgeComm(port.handle, purgeTxClear|purgeTxAbort)
|
return windows.PurgeComm(port.handle, windows.PURGE_TXCLEAR|windows.PURGE_TXABORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -152,119 +154,44 @@ const (
|
|||||||
dcbInX = 0x00000200
|
dcbInX = 0x00000200
|
||||||
dcbErrorChar = 0x00000400
|
dcbErrorChar = 0x00000400
|
||||||
dcbNull = 0x00000800
|
dcbNull = 0x00000800
|
||||||
dcbRTSControlDisbaleMask = ^uint32(0x00003000)
|
dcbRTSControlDisableMask = ^uint32(0x00003000)
|
||||||
dcbRTSControlEnable = 0x00001000
|
dcbRTSControlEnable = 0x00001000
|
||||||
dcbRTSControlHandshake = 0x00002000
|
dcbRTSControlHandshake = 0x00002000
|
||||||
dcbRTSControlToggle = 0x00003000
|
dcbRTSControlToggle = 0x00003000
|
||||||
dcbAbortOnError = 0x00004000
|
dcbAbortOnError = 0x00004000
|
||||||
)
|
)
|
||||||
|
|
||||||
type dcb struct {
|
|
||||||
DCBlength uint32
|
|
||||||
BaudRate uint32
|
|
||||||
|
|
||||||
// Flags field is a bitfield
|
|
||||||
// fBinary :1
|
|
||||||
// fParity :1
|
|
||||||
// fOutxCtsFlow :1
|
|
||||||
// fOutxDsrFlow :1
|
|
||||||
// fDtrControl :2
|
|
||||||
// fDsrSensitivity :1
|
|
||||||
// fTXContinueOnXoff :1
|
|
||||||
// fOutX :1
|
|
||||||
// fInX :1
|
|
||||||
// fErrorChar :1
|
|
||||||
// fNull :1
|
|
||||||
// fRtsControl :2
|
|
||||||
// fAbortOnError :1
|
|
||||||
// fDummy2 :17
|
|
||||||
Flags uint32
|
|
||||||
|
|
||||||
wReserved uint16
|
|
||||||
XonLim uint16
|
|
||||||
XoffLim uint16
|
|
||||||
ByteSize byte
|
|
||||||
Parity byte
|
|
||||||
StopBits byte
|
|
||||||
XonChar byte
|
|
||||||
XoffChar byte
|
|
||||||
ErrorChar byte
|
|
||||||
EOFChar byte
|
|
||||||
EvtChar byte
|
|
||||||
wReserved1 uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type commTimeouts struct {
|
|
||||||
ReadIntervalTimeout uint32
|
|
||||||
ReadTotalTimeoutMultiplier uint32
|
|
||||||
ReadTotalTimeoutConstant uint32
|
|
||||||
WriteTotalTimeoutMultiplier uint32
|
|
||||||
WriteTotalTimeoutConstant uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
noParity = 0
|
|
||||||
oddParity = 1
|
|
||||||
evenParity = 2
|
|
||||||
markParity = 3
|
|
||||||
spaceParity = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
var parityMap = map[Parity]byte{
|
var parityMap = map[Parity]byte{
|
||||||
NoParity: noParity,
|
NoParity: windows.NOPARITY,
|
||||||
OddParity: oddParity,
|
OddParity: windows.ODDPARITY,
|
||||||
EvenParity: evenParity,
|
EvenParity: windows.EVENPARITY,
|
||||||
MarkParity: markParity,
|
MarkParity: windows.MARKPARITY,
|
||||||
SpaceParity: spaceParity,
|
SpaceParity: windows.SPACEPARITY,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
oneStopBit = 0
|
|
||||||
one5StopBits = 1
|
|
||||||
twoStopBits = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
var stopBitsMap = map[StopBits]byte{
|
var stopBitsMap = map[StopBits]byte{
|
||||||
OneStopBit: oneStopBit,
|
OneStopBit: windows.ONESTOPBIT,
|
||||||
OnePointFiveStopBits: one5StopBits,
|
OnePointFiveStopBits: windows.ONE5STOPBITS,
|
||||||
TwoStopBits: twoStopBits,
|
TwoStopBits: windows.TWOSTOPBITS,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
commFunctionSetXOFF = 1
|
|
||||||
commFunctionSetXON = 2
|
|
||||||
commFunctionSetRTS = 3
|
|
||||||
commFunctionClrRTS = 4
|
|
||||||
commFunctionSetDTR = 5
|
|
||||||
commFunctionClrDTR = 6
|
|
||||||
commFunctionSetBreak = 8
|
|
||||||
commFunctionClrBreak = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
msCTSOn = 0x0010
|
|
||||||
msDSROn = 0x0020
|
|
||||||
msRingOn = 0x0040
|
|
||||||
msRLSDOn = 0x0080
|
|
||||||
)
|
|
||||||
|
|
||||||
func (port *windowsPort) SetMode(mode *Mode) error {
|
func (port *windowsPort) SetMode(mode *Mode) error {
|
||||||
params := dcb{}
|
params := windows.DCB{}
|
||||||
if getCommState(port.handle, ¶ms) != nil {
|
if windows.GetCommState(port.handle, ¶ms) != nil {
|
||||||
port.Close()
|
port.Close()
|
||||||
return &PortError{code: InvalidSerialPort}
|
return &PortError{code: InvalidSerialPort}
|
||||||
}
|
}
|
||||||
port.setModeParams(mode, ¶ms)
|
port.setModeParams(mode, ¶ms)
|
||||||
if setCommState(port.handle, ¶ms) != nil {
|
if windows.SetCommState(port.handle, ¶ms) != nil {
|
||||||
port.Close()
|
port.Close()
|
||||||
return &PortError{code: InvalidSerialPort}
|
return &PortError{code: InvalidSerialPort}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) setModeParams(mode *Mode, params *dcb) {
|
func (port *windowsPort) setModeParams(mode *Mode, params *windows.DCB) {
|
||||||
if mode.BaudRate == 0 {
|
if mode.BaudRate == 0 {
|
||||||
params.BaudRate = 9600 // Default to 9600
|
params.BaudRate = windows.CBR_9600 // Default to 9600
|
||||||
} else {
|
} else {
|
||||||
params.BaudRate = uint32(mode.BaudRate)
|
params.BaudRate = uint32(mode.BaudRate)
|
||||||
}
|
}
|
||||||
@@ -278,22 +205,22 @@ func (port *windowsPort) setModeParams(mode *Mode, params *dcb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) SetDTR(dtr bool) error {
|
func (port *windowsPort) SetDTR(dtr bool) error {
|
||||||
// Like for RTS there are problems with the escapeCommFunction
|
// Like for RTS there are problems with the windows.EscapeCommFunction
|
||||||
// observed behaviour was that DTR is set from false -> true
|
// observed behaviour was that DTR is set from false -> true
|
||||||
// when setting RTS from true -> false
|
// when setting RTS from true -> false
|
||||||
// 1) Connect -> RTS = true (low) DTR = true (low) OKAY
|
// 1) Connect -> RTS = true (low) DTR = true (low) OKAY
|
||||||
// 2) SetDTR(false) -> RTS = true (low) DTR = false (heigh) OKAY
|
// 2) SetDTR(false) -> RTS = true (low) DTR = false (high) OKAY
|
||||||
// 3) SetRTS(false) -> RTS = false (heigh) DTR = true (low) ERROR: DTR toggled
|
// 3) SetRTS(false) -> RTS = false (high) DTR = true (low) ERROR: DTR toggled
|
||||||
//
|
//
|
||||||
// In addition this way the CommState Flags are not updated
|
// In addition this way the CommState Flags are not updated
|
||||||
/*
|
/*
|
||||||
var res bool
|
var err error
|
||||||
if dtr {
|
if dtr {
|
||||||
res = escapeCommFunction(port.handle, commFunctionSetDTR)
|
err = windows.EscapeCommFunction(port.handle, windows.SETDTR)
|
||||||
} else {
|
} else {
|
||||||
res = escapeCommFunction(port.handle, commFunctionClrDTR)
|
err = windows.EscapeCommFunction(port.handle, windows.CLTDTR)
|
||||||
}
|
}
|
||||||
if !res {
|
if err != nil {
|
||||||
return &PortError{}
|
return &PortError{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -301,15 +228,15 @@ func (port *windowsPort) SetDTR(dtr bool) error {
|
|||||||
|
|
||||||
// The following seems a more reliable way to do it
|
// The following seems a more reliable way to do it
|
||||||
|
|
||||||
params := &dcb{}
|
params := &windows.DCB{}
|
||||||
if err := getCommState(port.handle, params); err != nil {
|
if err := windows.GetCommState(port.handle, params); err != nil {
|
||||||
return &PortError{causedBy: err}
|
return &PortError{causedBy: err}
|
||||||
}
|
}
|
||||||
params.Flags &= dcbDTRControlDisableMask
|
params.Flags &= dcbDTRControlDisableMask
|
||||||
if dtr {
|
if dtr {
|
||||||
params.Flags |= dcbDTRControlEnable
|
params.Flags |= windows.DTR_CONTROL_ENABLE
|
||||||
}
|
}
|
||||||
if err := setCommState(port.handle, params); err != nil {
|
if err := windows.SetCommState(port.handle, params); err != nil {
|
||||||
return &PortError{causedBy: err}
|
return &PortError{causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,13 +252,13 @@ func (port *windowsPort) SetRTS(rts bool) error {
|
|||||||
// In addition this way the CommState Flags are not updated
|
// In addition this way the CommState Flags are not updated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
var res bool
|
var err error
|
||||||
if rts {
|
if rts {
|
||||||
res = escapeCommFunction(port.handle, commFunctionSetRTS)
|
err = windows.EscapeCommFunction(port.handle, windows.SETRTS)
|
||||||
} else {
|
} else {
|
||||||
res = escapeCommFunction(port.handle, commFunctionClrRTS)
|
err = windows.EscapeCommFunction(port.handle, windows.CLRRTS)
|
||||||
}
|
}
|
||||||
if !res {
|
if err != nil {
|
||||||
return &PortError{}
|
return &PortError{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -339,38 +266,54 @@ func (port *windowsPort) SetRTS(rts bool) error {
|
|||||||
|
|
||||||
// The following seems a more reliable way to do it
|
// The following seems a more reliable way to do it
|
||||||
|
|
||||||
params := &dcb{}
|
params := &windows.DCB{}
|
||||||
if err := getCommState(port.handle, params); err != nil {
|
if err := windows.GetCommState(port.handle, params); err != nil {
|
||||||
return &PortError{causedBy: err}
|
return &PortError{causedBy: err}
|
||||||
}
|
}
|
||||||
params.Flags &= dcbRTSControlDisbaleMask
|
params.Flags &= dcbRTSControlDisableMask
|
||||||
if rts {
|
if rts {
|
||||||
params.Flags |= dcbRTSControlEnable
|
params.Flags |= windows.RTS_CONTROL_ENABLE
|
||||||
}
|
}
|
||||||
if err := setCommState(port.handle, params); err != nil {
|
if err := windows.SetCommState(port.handle, params); err != nil {
|
||||||
return &PortError{causedBy: err}
|
return &PortError{causedBy: err}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) {
|
func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) {
|
||||||
|
// GetCommModemStatus constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcommmodemstatus.
|
||||||
|
const (
|
||||||
|
MS_CTS_ON = 0x0010
|
||||||
|
MS_DSR_ON = 0x0020
|
||||||
|
MS_RING_ON = 0x0040
|
||||||
|
MS_RLSD_ON = 0x0080
|
||||||
|
)
|
||||||
var bits uint32
|
var bits uint32
|
||||||
if !getCommModemStatus(port.handle, &bits) {
|
if err := windows.GetCommModemStatus(port.handle, &bits); err != nil {
|
||||||
return nil, &PortError{}
|
return nil, &PortError{}
|
||||||
}
|
}
|
||||||
return &ModemStatusBits{
|
return &ModemStatusBits{
|
||||||
CTS: (bits & msCTSOn) != 0,
|
CTS: (bits & MS_CTS_ON) != 0,
|
||||||
DCD: (bits & msRLSDOn) != 0,
|
DCD: (bits & MS_RLSD_ON) != 0,
|
||||||
DSR: (bits & msDSROn) != 0,
|
DSR: (bits & MS_DSR_ON) != 0,
|
||||||
RI: (bits & msRingOn) != 0,
|
RI: (bits & MS_RING_ON) != 0,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
|
func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
|
||||||
commTimeouts := &commTimeouts{
|
// This is a brutal hack to make the CH340 chipset work properly.
|
||||||
|
// Normally this value should be 0xFFFFFFFE but, after a lot of
|
||||||
|
// tinkering, I discovered that any value with the highest
|
||||||
|
// bit set will make the CH340 driver behave like the timeout is 0,
|
||||||
|
// in the best cases leading to a spinning loop...
|
||||||
|
// (could this be a wrong signed vs unsigned conversion in the driver?)
|
||||||
|
// https://github.com/arduino/serial-monitor/issues/112
|
||||||
|
const MaxReadTotalTimeoutConstant = 0x7FFFFFFE
|
||||||
|
|
||||||
|
commTimeouts := &windows.CommTimeouts{
|
||||||
ReadIntervalTimeout: 0xFFFFFFFF,
|
ReadIntervalTimeout: 0xFFFFFFFF,
|
||||||
ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
|
ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
|
||||||
ReadTotalTimeoutConstant: 0xFFFFFFFE,
|
ReadTotalTimeoutConstant: MaxReadTotalTimeoutConstant,
|
||||||
WriteTotalTimeoutConstant: 0,
|
WriteTotalTimeoutConstant: 0,
|
||||||
WriteTotalTimeoutMultiplier: 0,
|
WriteTotalTimeoutMultiplier: 0,
|
||||||
}
|
}
|
||||||
@@ -379,53 +322,64 @@ func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
|
|||||||
if ms > 0xFFFFFFFE || ms < 0 {
|
if ms > 0xFFFFFFFE || ms < 0 {
|
||||||
return &PortError{code: InvalidTimeoutValue}
|
return &PortError{code: InvalidTimeoutValue}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ms > MaxReadTotalTimeoutConstant {
|
||||||
|
ms = MaxReadTotalTimeoutConstant
|
||||||
|
}
|
||||||
|
|
||||||
commTimeouts.ReadTotalTimeoutConstant = uint32(ms)
|
commTimeouts.ReadTotalTimeoutConstant = uint32(ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setCommTimeouts(port.handle, commTimeouts); err != nil {
|
port.mu.Lock()
|
||||||
|
defer port.mu.Unlock()
|
||||||
|
if err := windows.SetCommTimeouts(port.handle, commTimeouts); err != nil {
|
||||||
return &PortError{code: InvalidTimeoutValue, causedBy: err}
|
return &PortError{code: InvalidTimeoutValue, causedBy: err}
|
||||||
}
|
}
|
||||||
|
port.hasTimeout = (timeout != NoTimeout)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (port *windowsPort) Break(d time.Duration) error {
|
func (port *windowsPort) Break(d time.Duration) error {
|
||||||
if err := setCommBreak(port.handle); err != nil {
|
if err := windows.SetCommBreak(port.handle); err != nil {
|
||||||
return &PortError{causedBy: err}
|
return &PortError{causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
|
|
||||||
if err := clearCommBreak(port.handle); err != nil {
|
if err := windows.ClearCommBreak(port.handle); err != nil {
|
||||||
return &PortError{causedBy: err}
|
return &PortError{causedBy: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOverlappedEvent() (*syscall.Overlapped, error) {
|
func createOverlappedEvent() (*windows.Overlapped, error) {
|
||||||
h, err := createEvent(nil, true, false, nil)
|
h, err := windows.CreateEvent(nil, 1, 0, nil)
|
||||||
return &syscall.Overlapped{HEvent: h}, err
|
return &windows.Overlapped{HEvent: h}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func nativeOpen(portName string, mode *Mode) (*windowsPort, error) {
|
func nativeOpen(portName string, mode *Mode) (*windowsPort, error) {
|
||||||
portName = "\\\\.\\" + portName
|
if !strings.HasPrefix(portName, `\\.\`) {
|
||||||
path, err := syscall.UTF16PtrFromString(portName)
|
portName = `\\.\` + portName
|
||||||
|
}
|
||||||
|
path, err := windows.UTF16PtrFromString(portName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
handle, err := syscall.CreateFile(
|
handle, err := windows.CreateFile(
|
||||||
path,
|
path,
|
||||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
windows.GENERIC_READ|windows.GENERIC_WRITE,
|
||||||
0, nil,
|
0, nil,
|
||||||
syscall.OPEN_EXISTING,
|
windows.OPEN_EXISTING,
|
||||||
syscall.FILE_FLAG_OVERLAPPED,
|
windows.FILE_FLAG_OVERLAPPED,
|
||||||
0)
|
0,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case syscall.ERROR_ACCESS_DENIED:
|
case windows.ERROR_ACCESS_DENIED:
|
||||||
return nil, &PortError{code: PortBusy}
|
return nil, &PortError{code: PortBusy}
|
||||||
case syscall.ERROR_FILE_NOT_FOUND:
|
case windows.ERROR_FILE_NOT_FOUND:
|
||||||
return nil, &PortError{code: PortNotFound}
|
return nil, &PortError{code: PortNotFound}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -436,23 +390,23 @@ func nativeOpen(portName string, mode *Mode) (*windowsPort, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set port parameters
|
// Set port parameters
|
||||||
params := &dcb{}
|
params := &windows.DCB{}
|
||||||
if getCommState(port.handle, params) != nil {
|
if windows.GetCommState(port.handle, params) != nil {
|
||||||
port.Close()
|
port.Close()
|
||||||
return nil, &PortError{code: InvalidSerialPort}
|
return nil, &PortError{code: InvalidSerialPort}
|
||||||
}
|
}
|
||||||
port.setModeParams(mode, params)
|
port.setModeParams(mode, params)
|
||||||
params.Flags &= dcbDTRControlDisableMask
|
params.Flags &= dcbDTRControlDisableMask
|
||||||
params.Flags &= dcbRTSControlDisbaleMask
|
params.Flags &= dcbRTSControlDisableMask
|
||||||
if mode.InitialStatusBits == nil {
|
if mode.InitialStatusBits == nil {
|
||||||
params.Flags |= dcbDTRControlEnable
|
params.Flags |= windows.DTR_CONTROL_ENABLE
|
||||||
params.Flags |= dcbRTSControlEnable
|
params.Flags |= windows.RTS_CONTROL_ENABLE
|
||||||
} else {
|
} else {
|
||||||
if mode.InitialStatusBits.DTR {
|
if mode.InitialStatusBits.DTR {
|
||||||
params.Flags |= dcbDTRControlEnable
|
params.Flags |= windows.DTR_CONTROL_ENABLE
|
||||||
}
|
}
|
||||||
if mode.InitialStatusBits.RTS {
|
if mode.InitialStatusBits.RTS {
|
||||||
params.Flags |= dcbRTSControlEnable
|
params.Flags |= windows.RTS_CONTROL_ENABLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.Flags &^= dcbOutXCTSFlow
|
params.Flags &^= dcbOutXCTSFlow
|
||||||
@@ -468,7 +422,7 @@ func nativeOpen(portName string, mode *Mode) (*windowsPort, error) {
|
|||||||
params.XoffLim = 512
|
params.XoffLim = 512
|
||||||
params.XonChar = 17 // DC1
|
params.XonChar = 17 // DC1
|
||||||
params.XoffChar = 19 // C3
|
params.XoffChar = 19 // C3
|
||||||
if setCommState(port.handle, params) != nil {
|
if windows.SetCommState(port.handle, params) != nil {
|
||||||
port.Close()
|
port.Close()
|
||||||
return nil, &PortError{code: InvalidSerialPort}
|
return nil, &PortError{code: InvalidSerialPort}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
|
|
||||||
package serial
|
|
||||||
|
|
||||||
//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) = advapi32.RegEnumValueW
|
|
||||||
|
|
||||||
//sys getCommState(handle syscall.Handle, dcb *dcb) (err error) = GetCommState
|
|
||||||
|
|
||||||
//sys setCommState(handle syscall.Handle, dcb *dcb) (err error) = SetCommState
|
|
||||||
|
|
||||||
//sys setCommTimeouts(handle syscall.Handle, timeouts *commTimeouts) (err error) = SetCommTimeouts
|
|
||||||
|
|
||||||
//sys escapeCommFunction(handle syscall.Handle, function uint32) (res bool) = EscapeCommFunction
|
|
||||||
|
|
||||||
//sys getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) = GetCommModemStatus
|
|
||||||
|
|
||||||
//sys createEvent(eventAttributes *uint32, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) = CreateEventW
|
|
||||||
|
|
||||||
//sys resetEvent(handle syscall.Handle) (err error) = ResetEvent
|
|
||||||
|
|
||||||
//sys getOverlappedResult(handle syscall.Handle, overlapEvent *syscall.Overlapped, n *uint32, wait bool) (err error) = GetOverlappedResult
|
|
||||||
|
|
||||||
//sys purgeComm(handle syscall.Handle, flags uint32) (err error) = PurgeComm
|
|
||||||
|
|
||||||
//sys setCommBreak(handle syscall.Handle) (err error) = SetCommBreak
|
|
||||||
|
|
||||||
//sys clearCommBreak(handle syscall.Handle) (err error) = ClearCommBreak
|
|
||||||
83
test/deadlock/main.go
Normal file
83
test/deadlock/main.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.bug.st/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Testing serial port read/write/close operations...")
|
||||||
|
|
||||||
|
// Test case: Opening a non-existent port (should fail gracefully)
|
||||||
|
_, err := serial.Open("/dev/nonexistent", &serial.Mode{BaudRate: 9600})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Expected error when opening non-existent port: %v\n", err)
|
||||||
|
} else {
|
||||||
|
log.Fatal("Expected error when opening non-existent port, but it succeeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find available serial ports
|
||||||
|
ports, err := serial.GetPortsList()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ports) > 0 {
|
||||||
|
fmt.Printf("Found %d available serial port(s)\n", len(ports))
|
||||||
|
for _, portName := range ports {
|
||||||
|
fmt.Printf("- %s\n", portName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("No serial ports available for testing (this is normal if no devices are connected)")
|
||||||
|
fmt.Println("The fix has been implemented and should resolve the deadlock issue")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with the first available port
|
||||||
|
testPort := ports[0]
|
||||||
|
fmt.Printf("\nTesting port: %s\n", testPort)
|
||||||
|
|
||||||
|
// Test 1: Basic read timeout and close without data
|
||||||
|
mode := &serial.Mode{BaudRate: 9600}
|
||||||
|
port, err := serial.Open(testPort, mode)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Could not open port %s: %v\n", testPort, err)
|
||||||
|
fmt.Println("The fix has been implemented and should resolve the deadlock issue")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a short read timeout
|
||||||
|
if err := port.SetReadTimeout(200 * time.Millisecond); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent read and close (this would deadlock before the fix)
|
||||||
|
chErr := make(chan error)
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
fmt.Printf("Reading from port %s with %v timeout...\n", testPort, 200*time.Millisecond)
|
||||||
|
n, err := port.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Read error: %v\n", err)
|
||||||
|
} else if n == 0 {
|
||||||
|
fmt.Println("Read timeout (expected)")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Read %d byte: %v\n", n, buf)
|
||||||
|
}
|
||||||
|
chErr <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait a little, then close the port
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
fmt.Println("Closing port...")
|
||||||
|
if err := port.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for read goroutine to complete
|
||||||
|
<-chErr
|
||||||
|
fmt.Println("Test passed: No deadlock occurred!")
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
@@ -40,7 +40,7 @@ func (p *Pipe) ReadFD() int {
|
|||||||
return p.rd
|
return p.rd
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFD returns the flie handle for the write side of the pipe.
|
// WriteFD returns the file handle for the write side of the pipe.
|
||||||
func (p *Pipe) WriteFD() int {
|
func (p *Pipe) WriteFD() int {
|
||||||
if !p.opened {
|
if !p.opened {
|
||||||
return -1
|
return -1
|
||||||
@@ -48,7 +48,7 @@ func (p *Pipe) WriteFD() int {
|
|||||||
return p.wr
|
return p.wr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to the pipe the content of data. Returns the numbre of bytes written.
|
// Write to the pipe the content of data. Returns the number of bytes written.
|
||||||
func (p *Pipe) Write(data []byte) (int, error) {
|
func (p *Pipe) Write(data []byte) (int, error) {
|
||||||
if !p.opened {
|
if !p.opened {
|
||||||
return 0, fmt.Errorf("Pipe not opened")
|
return 0, fmt.Errorf("Pipe not opened")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2014-2023 Cristian Maglie. All rights reserved.
|
// Copyright 2014-2024 Cristian Maglie. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
// Code generated by 'go generate'; DO NOT EDIT.
|
|
||||||
|
|
||||||
package serial
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
// Do the interface allocations only once for common
|
|
||||||
// Errno values.
|
|
||||||
const (
|
|
||||||
errnoERROR_IO_PENDING = 997
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
|
||||||
errERROR_EINVAL error = syscall.EINVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// errnoErr returns common boxed Errno values, to prevent
|
|
||||||
// allocations at runtime.
|
|
||||||
func errnoErr(e syscall.Errno) error {
|
|
||||||
switch e {
|
|
||||||
case 0:
|
|
||||||
return errERROR_EINVAL
|
|
||||||
case errnoERROR_IO_PENDING:
|
|
||||||
return errERROR_IO_PENDING
|
|
||||||
}
|
|
||||||
// TODO: add more here, after collecting data on the common
|
|
||||||
// error values see on Windows. (perhaps when running
|
|
||||||
// all.bat?)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
|
|
||||||
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
|
|
||||||
procClearCommBreak = modkernel32.NewProc("ClearCommBreak")
|
|
||||||
procCreateEventW = modkernel32.NewProc("CreateEventW")
|
|
||||||
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
|
|
||||||
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
|
|
||||||
procGetCommState = modkernel32.NewProc("GetCommState")
|
|
||||||
procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult")
|
|
||||||
procPurgeComm = modkernel32.NewProc("PurgeComm")
|
|
||||||
procResetEvent = modkernel32.NewProc("ResetEvent")
|
|
||||||
procSetCommBreak = modkernel32.NewProc("SetCommBreak")
|
|
||||||
procSetCommState = modkernel32.NewProc("SetCommState")
|
|
||||||
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
|
|
||||||
)
|
|
||||||
|
|
||||||
func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, value *uint16, valueLen *uint32) (regerrno error) {
|
|
||||||
r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(value)), uintptr(unsafe.Pointer(valueLen)), 0)
|
|
||||||
if r0 != 0 {
|
|
||||||
regerrno = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearCommBreak(handle syscall.Handle) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createEvent(eventAttributes *uint32, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
|
|
||||||
var _p0 uint32
|
|
||||||
if manualReset {
|
|
||||||
_p0 = 1
|
|
||||||
}
|
|
||||||
var _p1 uint32
|
|
||||||
if initialState {
|
|
||||||
_p1 = 1
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(eventAttributes)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0)
|
|
||||||
handle = syscall.Handle(r0)
|
|
||||||
if handle == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func escapeCommFunction(handle syscall.Handle, function uint32) (res bool) {
|
|
||||||
r0, _, _ := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(function), 0)
|
|
||||||
res = r0 != 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCommModemStatus(handle syscall.Handle, bits *uint32) (res bool) {
|
|
||||||
r0, _, _ := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(bits)), 0)
|
|
||||||
res = r0 != 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCommState(handle syscall.Handle, dcb *dcb) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOverlappedResult(handle syscall.Handle, overlapEvent *syscall.Overlapped, n *uint32, wait bool) (err error) {
|
|
||||||
var _p0 uint32
|
|
||||||
if wait {
|
|
||||||
_p0 = 1
|
|
||||||
}
|
|
||||||
r1, _, e1 := syscall.Syscall6(procGetOverlappedResult.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(overlapEvent)), uintptr(unsafe.Pointer(n)), uintptr(_p0), 0, 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func purgeComm(handle syscall.Handle, flags uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(flags), 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetEvent(handle syscall.Handle) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(handle), 0, 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCommBreak(handle syscall.Handle) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCommState(handle syscall.Handle, dcb *dcb) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCommTimeouts(handle syscall.Handle, timeouts *commTimeouts) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user