168 lines
3.4 KiB
Go
168 lines
3.4 KiB
Go
package serial
|
|
|
|
import (
|
|
"fmt"
|
|
"syscall"
|
|
)
|
|
|
|
type port struct {
|
|
handle syscall.Handle
|
|
|
|
oldDCB c_DCB
|
|
oldTimeouts c_COMMTIMEOUTS
|
|
}
|
|
|
|
// New allocates and returns a new serial port controller.
|
|
func New() Port {
|
|
return &port{
|
|
handle: syscall.InvalidHandle,
|
|
}
|
|
}
|
|
|
|
// Open connects to the given serial port.
|
|
func (p *port) Open(c *Config) (err error) {
|
|
p.handle, err = newHandle(c)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
syscall.CloseHandle(p.handle)
|
|
p.handle = syscall.InvalidHandle
|
|
}
|
|
}()
|
|
err = p.setSerialConfig(c)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = p.setTimeouts(c)
|
|
return
|
|
}
|
|
|
|
func (p *port) Close() (err error) {
|
|
if p.handle == syscall.InvalidHandle {
|
|
return
|
|
}
|
|
err1 := SetCommTimeouts(p.handle, &p.oldTimeouts)
|
|
err2 := SetCommState(p.handle, &p.oldDCB)
|
|
err = syscall.CloseHandle(p.handle)
|
|
if err == nil {
|
|
if err1 == nil {
|
|
err = err2
|
|
} else {
|
|
err = err1
|
|
}
|
|
}
|
|
p.handle = syscall.InvalidHandle
|
|
return
|
|
}
|
|
|
|
// Read reads from serial port.
|
|
// It is blocked until data received or timeout after p.timeout.
|
|
func (p *port) Read(b []byte) (n int, err error) {
|
|
var done uint32
|
|
if err = syscall.ReadFile(p.handle, b, &done, nil); err != nil {
|
|
return
|
|
}
|
|
if done == 0 {
|
|
err = ErrTimeout
|
|
return
|
|
}
|
|
n = int(done)
|
|
return
|
|
}
|
|
|
|
// Write writes data to the serial port.
|
|
func (p *port) Write(b []byte) (n int, err error) {
|
|
var done uint32
|
|
if err = syscall.WriteFile(p.handle, b, &done, nil); err != nil {
|
|
return
|
|
}
|
|
n = int(done)
|
|
return
|
|
}
|
|
|
|
func (p *port) setTimeouts(c *Config) error {
|
|
var timeouts c_COMMTIMEOUTS
|
|
// Read and write timeout
|
|
if c.Timeout > 0 {
|
|
timeout := toDWORD(int(c.Timeout.Nanoseconds() / 1E6))
|
|
// wait until a byte arrived or time out
|
|
timeouts.ReadIntervalTimeout = c_MAXDWORD
|
|
timeouts.ReadTotalTimeoutMultiplier = c_MAXDWORD
|
|
timeouts.ReadTotalTimeoutConstant = timeout
|
|
timeouts.WriteTotalTimeoutConstant = timeout
|
|
}
|
|
err := GetCommTimeouts(p.handle, &p.oldTimeouts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = SetCommTimeouts(p.handle, &timeouts)
|
|
if err != nil {
|
|
// reset
|
|
SetCommTimeouts(p.handle, &p.oldTimeouts)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (p *port) setSerialConfig(c *Config) error {
|
|
var dcb c_DCB
|
|
if c.BaudRate == 0 {
|
|
dcb.BaudRate = 19200
|
|
} else {
|
|
dcb.BaudRate = toDWORD(c.BaudRate)
|
|
}
|
|
// Data bits
|
|
if c.DataBits == 0 {
|
|
dcb.ByteSize = 8
|
|
} else {
|
|
dcb.ByteSize = toBYTE(c.DataBits)
|
|
}
|
|
// Stop bits
|
|
switch c.StopBits {
|
|
case 0, 1:
|
|
// Default is one stop bit.
|
|
dcb.StopBits = c_ONESTOPBIT
|
|
case 2:
|
|
dcb.StopBits = c_TWOSTOPBITS
|
|
default:
|
|
return fmt.Errorf("serial: unsupported stop bits %v", c.StopBits)
|
|
}
|
|
// Parity
|
|
switch c.Parity {
|
|
case "", "E":
|
|
// Default parity mode is Even.
|
|
dcb.Parity = c_EVENPARITY
|
|
dcb.Pad_cgo_0[0] |= 0x02 // fParity
|
|
case "O":
|
|
dcb.Parity = c_ODDPARITY
|
|
dcb.Pad_cgo_0[0] |= 0x02 // fParity
|
|
case "N":
|
|
dcb.Parity = c_NOPARITY
|
|
default:
|
|
return fmt.Errorf("serial: unsupported parity %v", c.Parity)
|
|
}
|
|
dcb.Pad_cgo_0[0] |= 0x01 // fBinary
|
|
|
|
err := GetCommState(p.handle, &p.oldDCB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = SetCommState(p.handle, &dcb)
|
|
if err != nil {
|
|
SetCommState(p.handle, &p.oldDCB)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func newHandle(c *Config) (handle syscall.Handle, err error) {
|
|
handle, err = syscall.CreateFile(
|
|
syscall.StringToUTF16Ptr(c.Address),
|
|
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
|
0, // mode
|
|
nil, // security
|
|
syscall.OPEN_EXISTING, // create mode
|
|
0, // attributes
|
|
0) // templates
|
|
return
|
|
}
|