mirror of
https://github.com/Luzifer/culmqtt.git
synced 2024-11-10 06:50:06 +00:00
258 lines
6.4 KiB
Go
258 lines
6.4 KiB
Go
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// This file contains OS-specific constants and types that work on OS X (tested
|
|
// on version 10.6.8).
|
|
//
|
|
// Helpful documentation for some of these options:
|
|
//
|
|
// http://www.unixwiz.net/techtips/termios-vmin-vtime.html
|
|
// http://www.taltech.com/support/entry/serial_intro
|
|
// http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_16.html
|
|
// http://permalink.gmane.org/gmane.linux.kernel/103713
|
|
//
|
|
|
|
package serial
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
)
|
|
import "os"
|
|
import "syscall"
|
|
import "unsafe"
|
|
|
|
// termios types
|
|
type cc_t byte
|
|
type speed_t uint64
|
|
type tcflag_t uint64
|
|
|
|
// sys/termios.h
|
|
const (
|
|
kCS5 = 0x00000000
|
|
kCS6 = 0x00000100
|
|
kCS7 = 0x00000200
|
|
kCS8 = 0x00000300
|
|
kCLOCAL = 0x00008000
|
|
kCREAD = 0x00000800
|
|
kCSTOPB = 0x00000400
|
|
kIGNPAR = 0x00000004
|
|
kPARENB = 0x00001000
|
|
kPARODD = 0x00002000
|
|
kCCTS_OFLOW = 0x00010000
|
|
kCRTS_IFLOW = 0x00020000
|
|
kCRTSCTS = kCCTS_OFLOW | kCRTS_IFLOW
|
|
|
|
kNCCS = 20
|
|
|
|
kVMIN = tcflag_t(16)
|
|
kVTIME = tcflag_t(17)
|
|
)
|
|
|
|
const (
|
|
|
|
// sys/ttycom.h
|
|
kTIOCGETA = 1078490131
|
|
kTIOCSETA = 2152231956
|
|
|
|
// IOKit: serial/ioss.h
|
|
kIOSSIOSPEED = 0x80045402
|
|
)
|
|
|
|
// sys/termios.h
|
|
type termios struct {
|
|
c_iflag tcflag_t
|
|
c_oflag tcflag_t
|
|
c_cflag tcflag_t
|
|
c_lflag tcflag_t
|
|
c_cc [kNCCS]cc_t
|
|
c_ispeed speed_t
|
|
c_ospeed speed_t
|
|
}
|
|
|
|
// setTermios updates the termios struct associated with a serial port file
|
|
// descriptor. This sets appropriate options for how the OS interacts with the
|
|
// port.
|
|
func setTermios(fd uintptr, src *termios) error {
|
|
// Make the ioctl syscall that sets the termios struct.
|
|
r1, _, errno :=
|
|
syscall.Syscall(
|
|
syscall.SYS_IOCTL,
|
|
fd,
|
|
uintptr(kTIOCSETA),
|
|
uintptr(unsafe.Pointer(src)))
|
|
|
|
// Did the syscall return an error?
|
|
if errno != 0 {
|
|
return os.NewSyscallError("SYS_IOCTL", errno)
|
|
}
|
|
|
|
// Just in case, check the return value as well.
|
|
if r1 != 0 {
|
|
return errors.New("Unknown error from SYS_IOCTL.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func convertOptions(options OpenOptions) (*termios, error) {
|
|
var result termios
|
|
|
|
// Ignore modem status lines. We don't want to receive SIGHUP when the serial
|
|
// port is disconnected, for example.
|
|
result.c_cflag |= kCLOCAL
|
|
|
|
// Enable receiving data.
|
|
//
|
|
// NOTE(jacobsa): I don't know exactly what this flag is for. The man page
|
|
// seems to imply that it shouldn't really exist.
|
|
result.c_cflag |= kCREAD
|
|
|
|
// 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.")
|
|
}
|
|
|
|
// Set VMIN and VTIME. Make sure to convert to tenths of seconds for VTIME.
|
|
result.c_cc[kVTIME] = cc_t(vtime / 100)
|
|
result.c_cc[kVMIN] = cc_t(vmin)
|
|
|
|
if !IsStandardBaudRate(options.BaudRate) {
|
|
// Non-standard baud-rates cannot be set via the standard IOCTL.
|
|
//
|
|
// Set an arbitrary baudrate. We'll set the real one later.
|
|
result.c_ispeed = 14400
|
|
result.c_ospeed = 14400
|
|
} else {
|
|
result.c_ispeed = speed_t(options.BaudRate)
|
|
result.c_ospeed = speed_t(options.BaudRate)
|
|
}
|
|
|
|
// Data bits
|
|
switch options.DataBits {
|
|
case 5:
|
|
result.c_cflag |= kCS5
|
|
case 6:
|
|
result.c_cflag |= kCS6
|
|
case 7:
|
|
result.c_cflag |= kCS7
|
|
case 8:
|
|
result.c_cflag |= kCS8
|
|
default:
|
|
return nil, errors.New("Invalid setting for DataBits.")
|
|
}
|
|
|
|
// Stop bits
|
|
switch options.StopBits {
|
|
case 1:
|
|
// Nothing to do; CSTOPB is already cleared.
|
|
case 2:
|
|
result.c_cflag |= kCSTOPB
|
|
default:
|
|
return nil, errors.New("Invalid setting for StopBits.")
|
|
}
|
|
|
|
// Parity mode
|
|
switch options.ParityMode {
|
|
case PARITY_NONE:
|
|
// Nothing to do; PARENB is already not set.
|
|
case PARITY_ODD:
|
|
// Enable parity generation and receiving at the hardware level using
|
|
// PARENB, but continue to deliver all bytes to the user no matter what (by
|
|
// not setting INPCK). Also turn on odd parity mode.
|
|
result.c_cflag |= kPARENB
|
|
result.c_cflag |= kPARODD
|
|
case PARITY_EVEN:
|
|
// Enable parity generation and receiving at the hardware level using
|
|
// PARENB, but continue to deliver all bytes to the user no matter what (by
|
|
// not setting INPCK). Leave out PARODD to use even mode.
|
|
result.c_cflag |= kPARENB
|
|
default:
|
|
return nil, errors.New("Invalid setting for ParityMode.")
|
|
}
|
|
|
|
if options.RTSCTSFlowControl {
|
|
result.c_cflag |= kCRTSCTS
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func openInternal(options OpenOptions) (io.ReadWriteCloser, error) {
|
|
// Open the serial port in non-blocking mode, since otherwise the OS will
|
|
// wait for the CARRIER line to be asserted.
|
|
file, err :=
|
|
os.OpenFile(
|
|
options.PortName,
|
|
syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK,
|
|
0600)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We want to do blocking I/O, so clear the non-blocking flag set above.
|
|
r1, _, errno :=
|
|
syscall.Syscall(
|
|
syscall.SYS_FCNTL,
|
|
uintptr(file.Fd()),
|
|
uintptr(syscall.F_SETFL),
|
|
uintptr(0))
|
|
|
|
if errno != 0 {
|
|
return nil, os.NewSyscallError("SYS_FCNTL", errno)
|
|
}
|
|
|
|
if r1 != 0 {
|
|
return nil, errors.New("Unknown error from SYS_FCNTL.")
|
|
}
|
|
|
|
// Set standard termios options.
|
|
terminalOptions, err := convertOptions(options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = setTermios(file.Fd(), terminalOptions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !IsStandardBaudRate(options.BaudRate) {
|
|
// Set baud rate with the IOSSIOSPEED ioctl, to support non-standard speeds.
|
|
r2, _, errno2 := syscall.Syscall(
|
|
syscall.SYS_IOCTL,
|
|
uintptr(file.Fd()),
|
|
uintptr(kIOSSIOSPEED),
|
|
uintptr(unsafe.Pointer(&options.BaudRate)))
|
|
|
|
if errno2 != 0 {
|
|
return nil, os.NewSyscallError("SYS_IOCTL", errno2)
|
|
}
|
|
|
|
if r2 != 0 {
|
|
return nil, errors.New("Unknown error from SYS_IOCTL.")
|
|
}
|
|
}
|
|
|
|
// We're done.
|
|
return file, nil
|
|
}
|