mirror of
https://github.com/Luzifer/culmqtt.git
synced 2024-11-14 00:32:51 +00:00
209 lines
4.3 KiB
Go
209 lines
4.3 KiB
Go
package serial
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
//
|
|
// Grab the constants with the following little program, to avoid using cgo:
|
|
//
|
|
// #include <stdio.h>
|
|
// #include <stdlib.h>
|
|
// #include <linux/termios.h>
|
|
//
|
|
// int main(int argc, const char **argv) {
|
|
// printf("TCSETS2 = 0x%08X\n", TCSETS2);
|
|
// printf("BOTHER = 0x%08X\n", BOTHER);
|
|
// printf("NCCS = %d\n", NCCS);
|
|
// return 0;
|
|
// }
|
|
//
|
|
const (
|
|
kTCSETS2 = 0x402C542B
|
|
kBOTHER = 0x1000
|
|
kNCCS = 19
|
|
)
|
|
|
|
//
|
|
// Types from asm-generic/termbits.h
|
|
//
|
|
|
|
type cc_t byte
|
|
type speed_t uint32
|
|
type tcflag_t uint32
|
|
type termios2 struct {
|
|
c_iflag tcflag_t // input mode flags
|
|
c_oflag tcflag_t // output mode flags
|
|
c_cflag tcflag_t // control mode flags
|
|
c_lflag tcflag_t // local mode flags
|
|
c_line cc_t // line discipline
|
|
c_cc [kNCCS]cc_t // control characters
|
|
c_ispeed speed_t // input speed
|
|
c_ospeed speed_t // output speed
|
|
}
|
|
|
|
// Constants for RS485 operation
|
|
|
|
const (
|
|
sER_RS485_ENABLED = (1 << 0)
|
|
sER_RS485_RTS_ON_SEND = (1 << 1)
|
|
sER_RS485_RTS_AFTER_SEND = (1 << 2)
|
|
sER_RS485_RX_DURING_TX = (1 << 4)
|
|
tIOCSRS485 = 0x542F
|
|
)
|
|
|
|
type serial_rs485 struct {
|
|
flags uint32
|
|
delay_rts_before_send uint32
|
|
delay_rts_after_send uint32
|
|
padding [5]uint32
|
|
}
|
|
|
|
//
|
|
// Returns a pointer to an instantiates termios2 struct, based on the given
|
|
// OpenOptions. Termios2 is a Linux extension which allows arbitrary baud rates
|
|
// to be specified.
|
|
//
|
|
func makeTermios2(options OpenOptions) (*termios2, error) {
|
|
|
|
// Sanity check inter-character timeout and minimum read size options.
|
|
|
|
vtime := uint(round(float64(options.InterCharacterTimeout)/100.0) * 100)
|
|
vmin := options.MinimumReadSize
|
|
|
|
if vmin == 0 && vtime < 100 {
|
|
return nil, errors.New("invalid values for InterCharacterTimeout and MinimumReadSize")
|
|
}
|
|
|
|
if vtime > 25500 {
|
|
return nil, errors.New("invalid value for InterCharacterTimeout")
|
|
}
|
|
|
|
ccOpts := [kNCCS]cc_t{}
|
|
ccOpts[syscall.VTIME] = cc_t(vtime / 100)
|
|
ccOpts[syscall.VMIN] = cc_t(vmin)
|
|
|
|
t2 := &termios2{
|
|
c_cflag: syscall.CLOCAL | syscall.CREAD | kBOTHER,
|
|
c_ispeed: speed_t(options.BaudRate),
|
|
c_ospeed: speed_t(options.BaudRate),
|
|
c_cc: ccOpts,
|
|
}
|
|
|
|
switch options.StopBits {
|
|
case 1:
|
|
case 2:
|
|
t2.c_cflag |= syscall.CSTOPB
|
|
|
|
default:
|
|
return nil, errors.New("invalid setting for StopBits")
|
|
}
|
|
|
|
switch options.ParityMode {
|
|
case PARITY_NONE:
|
|
case PARITY_ODD:
|
|
t2.c_cflag |= syscall.PARENB
|
|
t2.c_cflag |= syscall.PARODD
|
|
|
|
case PARITY_EVEN:
|
|
t2.c_cflag |= syscall.PARENB
|
|
|
|
default:
|
|
return nil, errors.New("invalid setting for ParityMode")
|
|
}
|
|
|
|
switch options.DataBits {
|
|
case 5:
|
|
t2.c_cflag |= syscall.CS5
|
|
case 6:
|
|
t2.c_cflag |= syscall.CS6
|
|
case 7:
|
|
t2.c_cflag |= syscall.CS7
|
|
case 8:
|
|
t2.c_cflag |= syscall.CS8
|
|
default:
|
|
return nil, errors.New("invalid setting for DataBits")
|
|
}
|
|
|
|
if options.RTSCTSFlowControl {
|
|
t2.c_cflag |= unix.CRTSCTS
|
|
}
|
|
|
|
return t2, nil
|
|
}
|
|
|
|
func openInternal(options OpenOptions) (io.ReadWriteCloser, error) {
|
|
|
|
file, openErr :=
|
|
os.OpenFile(
|
|
options.PortName,
|
|
syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK,
|
|
0600)
|
|
if openErr != nil {
|
|
return nil, openErr
|
|
}
|
|
|
|
// Clear the non-blocking flag set above.
|
|
nonblockErr := syscall.SetNonblock(int(file.Fd()), false)
|
|
if nonblockErr != nil {
|
|
return nil, nonblockErr
|
|
}
|
|
|
|
t2, optErr := makeTermios2(options)
|
|
if optErr != nil {
|
|
return nil, optErr
|
|
}
|
|
|
|
r, _, errno := syscall.Syscall(
|
|
syscall.SYS_IOCTL,
|
|
uintptr(file.Fd()),
|
|
uintptr(kTCSETS2),
|
|
uintptr(unsafe.Pointer(t2)))
|
|
|
|
if errno != 0 {
|
|
return nil, os.NewSyscallError("SYS_IOCTL", errno)
|
|
}
|
|
|
|
if r != 0 {
|
|
return nil, errors.New("unknown error from SYS_IOCTL")
|
|
}
|
|
|
|
if options.Rs485Enable {
|
|
rs485 := serial_rs485{
|
|
sER_RS485_ENABLED,
|
|
uint32(options.Rs485DelayRtsBeforeSend),
|
|
uint32(options.Rs485DelayRtsAfterSend),
|
|
[5]uint32{0, 0, 0, 0, 0},
|
|
}
|
|
|
|
if options.Rs485RtsHighDuringSend {
|
|
rs485.flags |= sER_RS485_RTS_ON_SEND
|
|
}
|
|
|
|
if options.Rs485RtsHighAfterSend {
|
|
rs485.flags |= sER_RS485_RTS_AFTER_SEND
|
|
}
|
|
|
|
r, _, errno := syscall.Syscall(
|
|
syscall.SYS_IOCTL,
|
|
uintptr(file.Fd()),
|
|
uintptr(tIOCSRS485),
|
|
uintptr(unsafe.Pointer(&rs485)))
|
|
|
|
if errno != 0 {
|
|
return nil, os.NewSyscallError("SYS_IOCTL (RS485)", errno)
|
|
}
|
|
|
|
if r != 0 {
|
|
return nil, errors.New("Unknown error from SYS_IOCTL (RS485)")
|
|
}
|
|
}
|
|
|
|
return file, nil
|
|
}
|