From a8de32fe78a49756be2214f4ae86d6f86edabc85 Mon Sep 17 00:00:00 2001 From: Callan Barrett Date: Sat, 6 Jul 2024 19:31:55 +0800 Subject: [PATCH] add simple serial driver (#78) --- go.mod | 2 + go.sum | 4 + pkg/daemon/reader.go | 11 ++ pkg/readers/libnfc/libnfc.go | 2 + pkg/readers/simple_serial/simple_serial.go | 196 +++++++++++++++++++++ 5 files changed, 215 insertions(+) create mode 100644 pkg/readers/simple_serial/simple_serial.go diff --git a/go.mod b/go.mod index 3ca11ac0..d75f88b0 100755 --- a/go.mod +++ b/go.mod @@ -24,9 +24,11 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect + github.com/creack/goselect v0.1.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.9.0 // indirect + go.bug.st/serial v1.6.2 // indirect golang.org/x/term v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 47f7e8eb..94b8ce37 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/bendahl/uinput v1.6.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRq github.com/clausecker/nfc/v2 v2.1.4 h1:zw2Cnny7pxPnuxVMBo+DXqXYETzUN7pMhNEA61yT5gY= github.com/clausecker/nfc/v2 v2.1.4/go.mod h1:BjRBQUQTQmiwh2tEfQ+xBM5xY05sV2gnZ0JRYEHog/o= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -46,6 +48,8 @@ github.com/txn2/txeh v1.4.0 h1:0tdvpA4HGJrj8X3kmrU6o/JFStI009nKxwDpMK5CnRU= github.com/txn2/txeh v1.4.0/go.mod h1:Mgq0hY184zCrDBLgvkIp+9NYGHoYbJcu4xKqUcx1shc= github.com/wizzomafizzo/mrext v0.0.0-20240705120538-5efdc40f9c08 h1:IfCVEDGCzNY4Lx+1tUUkTOLEv4yxXh9Jpsj6T+8YWr4= github.com/wizzomafizzo/mrext v0.0.0-20240705120538-5efdc40f9c08/go.mod h1:pWjoPIzJIXlDfEmdf++eUqsZKrEsYVkOHy39s/H7WLA= +go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8= +go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= diff --git a/pkg/daemon/reader.go b/pkg/daemon/reader.go index f019703b..f8f88734 100644 --- a/pkg/daemon/reader.go +++ b/pkg/daemon/reader.go @@ -14,6 +14,7 @@ import ( "github.com/wizzomafizzo/tapto/pkg/readers/file" "github.com/wizzomafizzo/tapto/pkg/readers/libnfc" + "github.com/wizzomafizzo/tapto/pkg/readers/simple_serial" "github.com/wizzomafizzo/tapto/pkg/tokens" ) @@ -85,6 +86,16 @@ func connectReaders( st.SetReader(device, r) log.Info().Msgf("opened file reader: %s", device) } + } else if rt == "simple_serial" { + r := simple_serial.NewReader(cfg) + err := r.Open(device, iq) + if err != nil { + log.Error().Msgf("error opening simple serial reader: %s", err) + continue + } else { + st.SetReader(device, r) + log.Info().Msgf("opened simple serial reader: %s", device) + } } else { r := libnfc.NewReader(cfg) err := r.Open(device, iq) diff --git a/pkg/readers/libnfc/libnfc.go b/pkg/readers/libnfc/libnfc.go index d1ebccca..a0d0198a 100644 --- a/pkg/readers/libnfc/libnfc.go +++ b/pkg/readers/libnfc/libnfc.go @@ -124,7 +124,9 @@ func (r *Reader) Close() error { func (r *Reader) Ids() []string { return []string{ "pn532_uart", + "pn532_i2c", "acr122_usb", + "pcsc", } } diff --git a/pkg/readers/simple_serial/simple_serial.go b/pkg/readers/simple_serial/simple_serial.go new file mode 100644 index 00000000..2479b372 --- /dev/null +++ b/pkg/readers/simple_serial/simple_serial.go @@ -0,0 +1,196 @@ +package simple_serial + +import ( + "errors" + "os" + "path/filepath" + "strings" + "time" + + "github.com/rs/zerolog/log" + "github.com/wizzomafizzo/tapto/pkg/config" + "github.com/wizzomafizzo/tapto/pkg/readers" + "github.com/wizzomafizzo/tapto/pkg/tokens" + "github.com/wizzomafizzo/tapto/pkg/utils" + + "go.bug.st/serial" +) + +type SimpleSerialReader struct { + cfg *config.UserConfig + device string + path string + polling bool + port serial.Port + lastToken *tokens.Token +} + +func NewReader(cfg *config.UserConfig) *SimpleSerialReader { + return &SimpleSerialReader{ + cfg: cfg, + } +} + +func (r *SimpleSerialReader) Ids() []string { + return []string{"simple_serial"} +} + +func (r *SimpleSerialReader) parseLine(line string) (*tokens.Token, error) { + line = strings.TrimSpace(line) + line = strings.Trim(line, "\r") + + if len(line) == 0 { + return nil, nil + } + + if !strings.HasPrefix(line, "SCAN\t") { + return nil, nil + } + + args := line[5:] + if len(args) == 0 { + return nil, nil + } + + t := tokens.Token{ + Data: line, + ScanTime: time.Now(), + Source: r.device, + } + + ps := strings.Split(args, "\t") + for i := 0; i < len(ps); i++ { + ps[i] = strings.TrimSpace(ps[i]) + if strings.HasPrefix(ps[i], "uid=") { + t.UID = ps[i][4:] + } else if strings.HasPrefix(ps[i], "text=") { + t.Text = ps[i][5:] + } + } + + return &t, nil +} + +func (r *SimpleSerialReader) Open(device string, iq chan<- readers.Scan) error { + ps := strings.SplitN(device, ":", 2) + if len(ps) != 2 { + return errors.New("invalid device string: " + device) + } + + if !utils.Contains(r.Ids(), ps[0]) { + return errors.New("invalid reader id: " + ps[0]) + } + + path := ps[1] + + if !filepath.IsAbs(path) { + return errors.New("invalid device path, must be absolute") + } + + if _, err := os.Stat(path); err != nil { + return err + } + + port, err := serial.Open(path, &serial.Mode{ + BaudRate: 115200, + }) + if err != nil { + return err + } + + err = port.SetReadTimeout(100 * time.Millisecond) + if err != nil { + return err + } + + r.port = port + r.device = device + r.path = path + r.polling = true + + go func() { + var lineBuf []byte + + for r.polling { + time.Sleep(100 * time.Millisecond) + + buf := make([]byte, 4096) + n, err := r.port.Read(buf) + if err != nil { + log.Error().Err(err).Msg("failed to read from serial port") + err = r.Close() + if err != nil { + log.Error().Err(err).Msg("failed to close serial port") + } + break + } + + for i := 0; i < n; i++ { + if buf[i] == '\n' { + line := string(lineBuf) + lineBuf = nil + + t, err := r.parseLine(line) + if err != nil { + log.Error().Err(err).Msg("failed to parse line") + continue + } + + if t != nil && !utils.TokensEqual(t, r.lastToken) { + iq <- readers.Scan{ + Source: r.device, + Token: t, + } + } + + if t != nil { + r.lastToken = t + } + } else { + lineBuf = append(lineBuf, buf[i]) + } + } + + if r.lastToken != nil && time.Since(r.lastToken.ScanTime) > 1*time.Second { + iq <- readers.Scan{ + Source: r.device, + Token: nil, + } + r.lastToken = nil + } + } + }() + + return nil +} + +func (r *SimpleSerialReader) Close() error { + r.polling = false + if r.port != nil { + err := r.port.Close() + if err != nil { + return err + } + } + return nil +} + +func (r *SimpleSerialReader) Detect(connected []string) string { + return "" +} + +func (r *SimpleSerialReader) Device() string { + return r.device +} + +func (r *SimpleSerialReader) Connected() bool { + return r.polling && r.port != nil +} + +func (r *SimpleSerialReader) Info() string { + return r.path +} + +func (r *SimpleSerialReader) Write(text string) (*tokens.Token, error) { + return nil, errors.New("writing not supported on this reader") +}