Reading from USB COM port in go

July 30, 2024 0 By addshore

If you want an easy copy and paste, no nonsense way to print the output of a COM PORT to the terminal in go, then have a look at the code at the bottom of this post.

Firstly, the go.bug.st/serial/enumerator package provides a very nice interface for getting details of connected devices, and includes more details than the example code in go.bug.st/serial that I found first time around.

Thanks to the toit devs for being responsive and helping me quickly figure out how I could make my ESP spit some USB content out over the COM port.

The code

In a nutshell is this:

  • Loops forever
  • Targets a specific device ID, such a 1a86:7523
  • Waits for it to appear as connected
    • Uses baud rate 115200
    • Opens the port
    • Prints all output received to the console
package main

import (
	"fmt"
	"time"

	"github.com/sirupsen/logrus"
	"go.bug.st/serial"
	"go.bug.st/serial/enumerator"
)

func main() {
	for {
		doStuff()
	}
}

func doStuff() {
	targetID := "1a86:7523" // the esp boards

	var found *enumerator.PortDetails
	for {
		ports, err := enumerator.GetDetailedPortsList()
		if err != nil {
			logrus.Error(err)
		}
		if len(ports) == 0 {
			fmt.Println("No serial ports found!")
			time.Sleep(1 * time.Second)
			continue
		}
		fmt.Printf("Found %d serial ports\n", len(ports))
		for _, port := range ports {
			fmt.Printf("Found port: %s\n", port.Name)
			if port.IsUSB {
				fmt.Printf("   USB ID     %s:%s\n", port.VID, port.PID)
				fmt.Printf("   USB serial %s\n", port.SerialNumber)
				if port.VID+":"+port.PID == targetID {
					if found != nil {
						logrus.Error("Multiple ports found with expected ID", targetID)
						time.Sleep(1 * time.Second)
						continue
					}
					found = port
				}
			}
			if port.Product != "" {
				fmt.Printf("   Product    %s\n", port.Product)
			}
		}

		if found != nil {
			break
		}

		time.Sleep(1 * time.Second)
	}

	fmt.Println("Found port with expected ID", targetID)

	// baud 115200
	mode := &serial.Mode{
		BaudRate: 115200,
	}

	fmt.Println("Opening port")
	port, err := serial.Open(found.Name, mode)
	if err != nil {
		logrus.Error(err)
		return
	}
	defer port.Close()

	fmt.Println("Reading from port")
	buff := make([]byte, 100)
	for {
		n, err := port.Read(buff)
		if err != nil {
			logrus.Error(err)
			break
		}
		if n == 0 {
			fmt.Println("\nEOF")
			break
		}
		fmt.Printf("%v", string(buff[:n]))
	}

}
Code language: JavaScript (javascript)

Testing with toit & an ESP32

I had a small chat with the toit developers in their Discord channel, and they helpfully pointed out that the print function in toit by default sends over the port via the usb-uart converter.

You can just print in Toit, and it will go through the usb-uart converter.

On the other side you can listen to the uart port and get the data out.

floitsch

So I was able to use a simple hello world type script

main:
  print "Hello world"
  while true:
    print "Time: $Time.now"
    sleep --ms=1000Code language: PHP (php)

Run it on my ESP32 using jaguar.

jag run uart.toit --device 192.168.68.51Code language: CSS (css)

And see my go code connect to the device and start printing the output that was coming over USB.