Skip to content

Instantly share code, notes, and snippets.

@aka-mj
Created June 26, 2025 13:15
Show Gist options
  • Select an option

  • Save aka-mj/72feacb1fb9c1eaa8186e12c01fa74a2 to your computer and use it in GitHub Desktop.

Select an option

Save aka-mj/72feacb1fb9c1eaa8186e12c01fa74a2 to your computer and use it in GitHub Desktop.
Serial in Go, no external dependencies
❯ socat -d -d pty,raw,echo=0 pty,raw,echo=0
2020/10/24 10:44:43 socat[786] N PTY is /dev/pts/16
2020/10/24 10:44:43 socat[786] N PTY is /dev/pts/17
2020/10/24 10:44:43 socat[786] N starting data transfer loop with FDs [5,5] and [7,7]
❯ ./serial /dev/pts/16 /dev/pts/17
2020/10/24 11:08:38 Sent: Test message to send!!
2020/10/24 11:08:38 Received: Test message to send!!
package main
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"os"
"syscall"
"time"
"unsafe"
)
const (
recvTimeout uint8 = 2 // Timeout in deciseconds
baud = syscall.B115200
)
type serial struct {
f *os.File
}
func openPort(serialDevice string) *serial {
var err error
s := new(serial)
// Open serial port to the device
s.f, err = os.OpenFile(serialDevice, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
if err != nil {
fmt.Printf("Failed to open %s: %v\n", serialDevice, err)
os.Exit(1)
}
// Termios settings
t := syscall.Termios{
Iflag: syscall.IGNPAR,
Cflag: syscall.CS8 | syscall.CREAD | syscall.CLOCAL | baud,
Cc: [32]uint8{syscall.VMIN: 0, syscall.VTIME: recvTimeout},
Ispeed: baud,
Ospeed: baud,
}
// syscall to set our termios settings for our file handle
syscall.Syscall6(syscall.SYS_IOCTL,
uintptr(s.f.Fd()), uintptr(syscall.TCSETS), uintptr(unsafe.Pointer(&t)),
0, 0, 0,
)
// Set nonblocking to false
if err := syscall.SetNonblock(int(s.f.Fd()), false); err != nil {
fmt.Printf("Failed to set nonblocking mode: %v\n", err)
os.Exit(1)
}
return s
}
// Close serial port.
func (s *serial) Close() {
s.f.Close()
}
// Write data to the serial port.
// Appends a carriage return to the end of str.
func (s *serial) Write(str string) error {
nbytes, err := s.f.WriteString(str + "\r")
if err != nil {
return err
}
if nbytes != len(str)+1 {
return errors.New("Looks like we failed to send the full message")
}
return nil
}
// Read reply from the serial port.
func (s *serial) Read() (string, error) {
buf := make([]byte, 128)
ret := new(bytes.Buffer)
for {
_, err := s.f.Read(buf)
if err != nil && err != io.EOF {
return "", err
}
if err == io.EOF {
break
}
ret.Write(buf)
}
// remove null bytes
ret2 := new(bytes.Buffer)
for _, i := range ret.Bytes() {
if i != 0x00 {
ret2.WriteByte(i)
}
}
return ret2.String(), nil
}
func main() {
s1 := openPort(os.Args[1])
defer s1.Close()
s2 := openPort(os.Args[2])
defer s2.Close()
sbuf := bytes.NewBufferString("Test message to send!!")
go func() {
// Send the data
err := s1.Write(sbuf.String())
if err != nil {
log.Fatal(err)
os.Exit(2)
}
log.Printf("Sent: %v\n", sbuf.String())
}()
// Receive the data
rbuf := new(bytes.Buffer)
count := 0
// Loop till we should have received everything
for count < len(sbuf.Bytes()) {
str, err := s2.Read()
if err != nil {
log.Fatal(err)
os.Exit(3)
}
rbuf.WriteString(str) //write read bytes to buffer
count = count + len(str)
}
log.Printf("Received: %v\n", rbuf.String())
time.Sleep(time.Second)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment