blob: c348f7f505b9c18d9d574a6d67614c4e3d4a651f [file] [log] [blame]
package main
import (
"flag"
"fmt"
"io"
"strconv"
"sync"
"time"
"github.com/huin/goserial"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
)
// Command is an alias for a string command.
type Command string
const (
// command understood by the Arduino to calibrate a servo.
CMD_CALIBRATE Command = "calibrate"
// command understood by the Arduino to reset a specific port.
CMD_RESET Command = "reset"
// duration how long the arduino pushes the power button.
RESET_DELAY = 12 * time.Second
// serial-over-USB device.
SERIAL_DEVICE = "/dev/ttyACM0"
// baud rate expected to by the Arduino board.
BAUD_RATE = 9600
)
type ArduinoClient struct {
conf *goserial.Config
rwc io.ReadWriteCloser
mutex sync.RWMutex
}
func NewArduinoClient(devName string, baud int) (*ArduinoClient, error) {
ret := &ArduinoClient{
conf: &goserial.Config{Name: devName, Baud: baud},
}
if err := ret.reopen(); err != nil {
return nil, err
}
return ret, nil
}
func (a *ArduinoClient) reopen() error {
if a.rwc != nil {
util.Close(a.rwc)
}
var err error
if a.rwc, err = goserial.OpenPort(a.conf); err != nil {
return err
}
return nil
}
// retry will try to run the given function for the given number of
// times. If it fails all tries the last error will be returned.
func (a *ArduinoClient) retry(nTimes int, fn func() error) error {
var err error
for i := 0; i < nTimes; i++ {
if err = fn(); (err == nil) || (err != io.EOF) {
sklog.Errorf("Error in retry: %s", err)
return err
}
if err = a.reopen(); err != nil {
return err
}
}
return err
}
// Send sends the given command and port to the Arduino board
// over the serial USB connection. It returns an error if the
// send failed.
func (a *ArduinoClient) Send(cmd Command, port int) error {
a.mutex.Lock()
// TODO(stephana): The retries might not be necessary. Keeping it
// for stability right now, but should be removed if we don't see
// errors in production. Errors are logged in the retry function.
err := a.retry(3, func() error {
_, err := a.rwc.Write([]byte(fmt.Sprintf("%s %d", cmd, port)))
return err
})
a.mutex.Unlock()
if err != nil {
return err
}
time.Sleep(RESET_DELAY)
return err
}
func (a *ArduinoClient) Close() error {
return a.rwc.Close()
}
func main() {
common.Init()
ports := flag.Args()
client, err := NewArduinoClient(SERIAL_DEVICE, BAUD_RATE)
if err != nil {
sklog.Fatalf("Error: %s", err)
}
defer util.Close(client)
if err := client.Send(CMD_CALIBRATE, 1); err != nil {
sklog.Fatalf("Error callibrating: %s", err)
}
for _, portStr := range ports {
port, err := strconv.ParseInt(portStr, 10, 64)
if err != nil {
sklog.Fatalf("Wrong port %s. Needs to be an integer. ", portStr)
}
if err := client.Send(CMD_RESET, int(port)); err != nil {
sklog.Fatalf("Error writing: %s", err)
}
}
}