1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
|
// +build darwin linux freebsd openbsd netbsd
package serial
import (
"errors"
"fmt"
"log"
"os"
"syscall"
"time"
"unsafe"
)
// port implements Port interface.
type port struct {
fd int
oldTermios *syscall.Termios
timeout time.Duration
}
const (
rs485Enabled = 1 << 0
rs485RTSOnSend = 1 << 1
rs485RTSAfterSend = 1 << 2
rs485RXDuringTX = 1 << 4
rs485Tiocs = 0x542f
)
// rs485_ioctl_opts is used to configure RS485 options in the driver
type rs485_ioctl_opts struct {
flags uint32
delay_rts_before_send uint32
delay_rts_after_send uint32
padding [5]uint32
}
// New allocates and returns a new serial port controller.
func New() Port {
return &port{fd: -1}
}
// Open connects to the given serial port.
func (p *port) Open(c *Config) (err error) {
termios, err := newTermios(c)
if err != nil {
return
}
// See man termios(3).
// O_NOCTTY: no controlling terminal.
// O_NDELAY: no data carrier detect.
p.fd, err = syscall.Open(c.Address, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NDELAY|syscall.O_CLOEXEC, 0666)
if err != nil {
return
}
// Backup current termios to restore on closing.
p.backupTermios()
if err = p.setTermios(termios); err != nil {
// No need to restore termios
syscall.Close(p.fd)
p.fd = -1
p.oldTermios = nil
return err
}
if err = enableRS485(p.fd, &c.RS485); err != nil {
p.Close()
return err
}
p.timeout = c.Timeout
return
}
func (p *port) Close() (err error) {
if p.fd == -1 {
return
}
p.restoreTermios()
err = syscall.Close(p.fd)
p.fd = -1
p.oldTermios = nil
return
}
// Read reads from serial port. Port must be opened before calling this method.
// It is blocked until all data received or timeout after p.timeout.
func (p *port) Read(b []byte) (n int, err error) {
var rfds syscall.FdSet
fd := p.fd
fdset(fd, &rfds)
var tv *syscall.Timeval
if p.timeout > 0 {
timeout := syscall.NsecToTimeval(p.timeout.Nanoseconds())
tv = &timeout
}
for {
// If syscall.Select() returns EINTR (Interrupted system call), retry it
if err = syscallSelect(fd+1, &rfds, nil, nil, tv); err == nil {
break
}
if err != syscall.EINTR {
err = fmt.Errorf("serial: could not select: %v", err)
return
}
}
if !fdisset(fd, &rfds) {
// Timeout
err = ErrTimeout
return
}
n, err = syscall.Read(fd, b)
return
}
// Write writes data to the serial port.
func (p *port) Write(b []byte) (n int, err error) {
n, err = syscall.Write(p.fd, b)
return
}
func (p *port) setTermios(termios *syscall.Termios) (err error) {
if err = tcsetattr(p.fd, termios); err != nil {
err = fmt.Errorf("serial: could not set setting: %v", err)
}
return
}
// backupTermios saves current termios setting.
// Make sure that device file has been opened before calling this function.
func (p *port) backupTermios() {
oldTermios := &syscall.Termios{}
if err := tcgetattr(p.fd, oldTermios); err != nil {
// Warning only.
log.Printf("serial: could not get setting: %v\n", err)
return
}
// Will be reloaded when closing.
p.oldTermios = oldTermios
}
// restoreTermios restores backed up termios setting.
// Make sure that device file has been opened before calling this function.
func (p *port) restoreTermios() {
if p.oldTermios == nil {
return
}
if err := tcsetattr(p.fd, p.oldTermios); err != nil {
// Warning only.
log.Printf("serial: could not restore setting: %v\n", err)
return
}
p.oldTermios = nil
}
// Helpers for termios
func newTermios(c *Config) (termios *syscall.Termios, err error) {
termios = &syscall.Termios{}
flag := termios.Cflag
// Baud rate
if c.BaudRate == 0 {
// 19200 is the required default.
flag = syscall.B19200
} else {
var ok bool
flag, ok = baudRates[c.BaudRate]
if !ok {
err = fmt.Errorf("serial: unsupported baud rate %v", c.BaudRate)
return
}
}
termios.Cflag |= flag
// Input baud.
cfSetIspeed(termios, flag)
// Output baud.
cfSetOspeed(termios, flag)
// Character size.
if c.DataBits == 0 {
flag = syscall.CS8
} else {
var ok bool
flag, ok = charSizes[c.DataBits]
if !ok {
err = fmt.Errorf("serial: unsupported character size %v", c.DataBits)
return
}
}
termios.Cflag |= flag
// Stop bits
switch c.StopBits {
case 0, 1:
// Default is one stop bit.
// noop
case 2:
// CSTOPB: Set two stop bits.
termios.Cflag |= syscall.CSTOPB
default:
err = fmt.Errorf("serial: unsupported stop bits %v", c.StopBits)
return
}
switch c.Parity {
case "N":
// noop
case "O":
// PARODD: Parity is odd.
termios.Cflag |= syscall.PARODD
fallthrough
case "", "E":
// As mentioned in the modbus spec, the default parity mode must be Even parity
// PARENB: Enable parity generation on output.
termios.Cflag |= syscall.PARENB
// INPCK: Enable input parity checking.
termios.Iflag |= syscall.INPCK
default:
err = fmt.Errorf("serial: unsupported parity %v", c.Parity)
return
}
// Control modes.
// CREAD: Enable receiver.
// CLOCAL: Ignore control lines.
termios.Cflag |= syscall.CREAD | syscall.CLOCAL
// Special characters.
// VMIN: Minimum number of characters for noncanonical read.
// VTIME: Time in deciseconds for noncanonical read.
// Both are unused as NDELAY is we utilized when opening device.
return
}
// enableRS485 enables RS485 functionality of driver via an ioctl if the config says so
func enableRS485(fd int, config *RS485Config) error {
if !config.Enabled {
return nil
}
rs485 := rs485_ioctl_opts{
rs485Enabled,
uint32(config.DelayRtsBeforeSend / time.Millisecond),
uint32(config.DelayRtsAfterSend / time.Millisecond),
[5]uint32{0, 0, 0, 0, 0},
}
if config.RtsHighDuringSend {
rs485.flags |= rs485RTSOnSend
}
if config.RtsHighAfterSend {
rs485.flags |= rs485RTSAfterSend
}
if config.RxDuringTx {
rs485.flags |= rs485RXDuringTX
}
r, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fd),
uintptr(rs485Tiocs),
uintptr(unsafe.Pointer(&rs485)))
if errno != 0 {
return os.NewSyscallError("SYS_IOCTL (RS485)", errno)
}
if r != 0 {
return errors.New("serial: unknown error from SYS_IOCTL (RS485)")
}
return nil
}
|