Initial working serial port writer
commit
3c9ba66295
|
@ -0,0 +1 @@
|
||||||
|
apply
|
|
@ -0,0 +1,93 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device interface {
|
||||||
|
ReadTimeout(time.Duration) ([]byte, error)
|
||||||
|
WriteLine([]byte) error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorDeviceOperationNotSupported = errors.New("device operation not supported")
|
||||||
|
|
||||||
|
type ConfigurationLine interface {
|
||||||
|
Apply(Device) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigurationDirective struct {
|
||||||
|
Name string
|
||||||
|
Argument string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigurationSimple []byte
|
||||||
|
|
||||||
|
func (d ConfigurationDirective) Apply(dev Device) error {
|
||||||
|
if d.Name == "sleep" {
|
||||||
|
seconds := 0
|
||||||
|
_, err := fmt.Sscanf(string(d.Argument), "%d", &seconds)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bad sleep arguments: %w", err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(seconds) * time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ConfigurationSimple) Apply(dev Device) error {
|
||||||
|
err := dev.WriteLine([]byte(s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response, err := dev.ReadTimeout(200 * time.Millisecond)
|
||||||
|
if errors.Is(err, ErrorDeviceOperationNotSupported) {
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", response)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigurationReader bufio.Reader
|
||||||
|
|
||||||
|
func NewConfigurationReader(r io.Reader) *ConfigurationReader {
|
||||||
|
return (*ConfigurationReader)(bufio.NewReader(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigurationReader) Read() (ConfigurationLine, error) {
|
||||||
|
line, err := (*bufio.Reader)(c).ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
line = bytes.TrimSuffix(line, []byte{'\n'})
|
||||||
|
|
||||||
|
if len(line) > 1 && line[0] == '!' && line[1] == ' ' {
|
||||||
|
// Comment
|
||||||
|
return ConfigurationSimple(string(line)), nil
|
||||||
|
} else if len(line) > 0 && line[0] == '!' {
|
||||||
|
// Directive
|
||||||
|
line = line[1:]
|
||||||
|
|
||||||
|
firstSep := bytes.IndexByte(line, ' ')
|
||||||
|
if firstSep < 0 {
|
||||||
|
return ConfigurationDirective{
|
||||||
|
Name: string(line),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
directive := line[:firstSep]
|
||||||
|
rest := line[firstSep+1:]
|
||||||
|
return ConfigurationDirective{
|
||||||
|
Name: string(directive),
|
||||||
|
Argument: string(rest),
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return ConfigurationSimple(line), nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeviceFile struct {
|
||||||
|
file *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeviceFile(name string) (DeviceFile, error) {
|
||||||
|
file, err := os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return DeviceFile{}, err
|
||||||
|
}
|
||||||
|
return DeviceFile{
|
||||||
|
file: file,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DeviceFile) ReadTimeout(timeout time.Duration) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("%w: Read", ErrorDeviceOperationNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DeviceFile) WriteLine(buf []byte) error {
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
f.file.Write(buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DeviceFile) Close() error {
|
||||||
|
return f.file.Close()
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
module git.makerforce.io/dump/sit/cs2203/confs/apply
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
||||||
|
go.bug.st/serial v1.1.0
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
|
||||||
|
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
|
go.bug.st/serial v1.1.0 h1:O0EHZw8ZdhmTAikak5ZY/8vyKCpFxZYgqZw1bGegxU8=
|
||||||
|
go.bug.st/serial v1.1.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
|
||||||
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
apply interacts with a serial port to configure a Cisco product using files.
|
||||||
|
|
||||||
|
This tool does not attempt to understand the proprietary donkey format that is
|
||||||
|
the Cisco CLI. Instead, it provides some directives around it to make life
|
||||||
|
easier when crafting configuration files.
|
||||||
|
|
||||||
|
Basic usage is very simple: Pass a file containing Cisco commands and it will
|
||||||
|
write it out to serial port. It also prints the output back to stdout for
|
||||||
|
visual inspection of success.
|
||||||
|
|
||||||
|
apply also contains some additional features to help you in handling files. The
|
||||||
|
main feature is directives that perform certain actions. These are expressed
|
||||||
|
within a line beginning with "!".
|
||||||
|
|
||||||
|
Directives
|
||||||
|
|
||||||
|
!sleep <seconds>
|
||||||
|
|
||||||
|
Sleep a fixed number of seconds.
|
||||||
|
|
||||||
|
!assert <text>
|
||||||
|
|
||||||
|
Ensure that in the next 1 second, output from the serial port contains <text>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Usage = func() {
|
||||||
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [-port PORT] [-writefile] FILE...\n", os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := flag.String("port", "/dev/ttyUSB0", "Serial port to write to")
|
||||||
|
writefile := flag.Bool("writefile", false, "Treat PORT as file, and write to file")
|
||||||
|
flag.Parse()
|
||||||
|
confs := flag.Args()
|
||||||
|
|
||||||
|
if len(confs) < 1 {
|
||||||
|
Usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var device Device
|
||||||
|
var err error
|
||||||
|
if *writefile {
|
||||||
|
device, err = NewDeviceFile(*port)
|
||||||
|
} else {
|
||||||
|
device, err = NewDeviceSerial(*port)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer device.Close()
|
||||||
|
|
||||||
|
for _, conf := range confs {
|
||||||
|
file, err := os.Open(conf)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("file %s: %w", conf, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := NewConfigurationReader(file)
|
||||||
|
lineNum := 0
|
||||||
|
|
||||||
|
var line ConfigurationLine
|
||||||
|
for {
|
||||||
|
lineNum++
|
||||||
|
|
||||||
|
line, err = reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := line.(ConfigurationSimple); !ok {
|
||||||
|
fmt.Printf("directive: %v\n", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = line.Apply(device)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic(fmt.Errorf("file %s line %d: %w", conf, lineNum, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to read rest of output
|
||||||
|
response, err := device.ReadTimeout(1000 * time.Millisecond)
|
||||||
|
if errors.Is(err, ErrorDeviceOperationNotSupported) {
|
||||||
|
} else if err != nil {
|
||||||
|
panic(fmt.Errorf("device: %w", err))
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", response)
|
||||||
|
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
panic(fmt.Errorf("file %s: %w", conf, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.bug.st/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeviceSerial struct {
|
||||||
|
port serial.Port
|
||||||
|
lines chan []byte
|
||||||
|
lineErr chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeviceSerial(name string) (DeviceSerial, error) {
|
||||||
|
serialMode := &serial.Mode{
|
||||||
|
BaudRate: 9600,
|
||||||
|
DataBits: 8,
|
||||||
|
Parity: serial.NoParity,
|
||||||
|
StopBits: serial.OneStopBit,
|
||||||
|
}
|
||||||
|
port, err := serial.Open(name, serialMode)
|
||||||
|
if err != nil {
|
||||||
|
return DeviceSerial{}, err
|
||||||
|
}
|
||||||
|
lines, lineErr := lineChannel(port)
|
||||||
|
return DeviceSerial{
|
||||||
|
port: port,
|
||||||
|
lines: lines,
|
||||||
|
lineErr: lineErr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DeviceSerial) ReadTimeout(timeout time.Duration) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
buf := make([]byte, 0)
|
||||||
|
t := timerChannel(timeout)
|
||||||
|
for {
|
||||||
|
// Read lines until timeout
|
||||||
|
select {
|
||||||
|
case line := <-s.lines:
|
||||||
|
buf = append(buf, line...)
|
||||||
|
case err = <-s.lineErr:
|
||||||
|
return buf, err
|
||||||
|
case <-t:
|
||||||
|
return buf, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DeviceSerial) WriteLine(buf []byte) error {
|
||||||
|
// TODO: handle n
|
||||||
|
buf = append(buf, '\r')
|
||||||
|
_, err := s.port.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DeviceSerial) Close() error {
|
||||||
|
return s.port.Close()
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lineChannel(r io.Reader) (chan []byte, chan error) {
|
||||||
|
lineChan := make(chan []byte, 100)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
reader := bufio.NewReader(r)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
line, err := reader.ReadBytes('\n')
|
||||||
|
lineChan <- line
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(lineChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return lineChan, errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func timerChannel(t time.Duration) chan bool {
|
||||||
|
timerChan := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(t)
|
||||||
|
timerChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
return timerChan
|
||||||
|
}
|
Loading…
Reference in New Issue