lol, lmao even
This commit is contained in:
commit
9a61264570
5 changed files with 269 additions and 0 deletions
10
go.mod
Normal file
10
go.mod
Normal file
|
@ -0,0 +1,10 @@
|
|||
module code.hackerspace.pl/q3k/jankssi
|
||||
|
||||
go 1.22.3
|
||||
|
||||
require go.bug.st/serial v1.6.2
|
||||
|
||||
require (
|
||||
github.com/creack/goselect v0.1.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||
)
|
14
go.sum
Normal file
14
go.sum
Normal file
|
@ -0,0 +1,14 @@
|
|||
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.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
||||
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
49
main.go
Normal file
49
main.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
type FileSerial struct {
|
||||
f serial.Port
|
||||
}
|
||||
|
||||
func (f *FileSerial) Write(ctx context.Context, data []byte) error {
|
||||
_, err := f.f.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *FileSerial) Read(ctx context.Context, data []byte) (int, error) {
|
||||
n, err := f.f.Read(data)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func main() {
|
||||
f, err := serial.Open("/dev/ttyACM0", &serial.Mode{})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open serial port: %v", err)
|
||||
}
|
||||
|
||||
bC := make(chan []byte)
|
||||
ssi := NewSSI(&FileSerial{f: f}, func(ty uint8, data []byte) {
|
||||
log.Printf("Barcode: %x/%s", ty, data)
|
||||
select {
|
||||
case bC <- data:
|
||||
default:
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
go ssi.Run(ctx)
|
||||
|
||||
http.HandleFunc("/barcode", func(w http.ResponseWriter, r *http.Request) {
|
||||
b := <-bC
|
||||
fmt.Fprintf(w, "barcode: %s", b)
|
||||
})
|
||||
http.ListenAndServe(":2137", nil)
|
||||
}
|
179
ssi.go
Normal file
179
ssi.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CallbackOnBarcode func(ty uint8, data []byte)
|
||||
|
||||
type SerialPort interface {
|
||||
Write(ctx context.Context, data []byte) error
|
||||
Read(ctx context.Context, out []byte) (int, error)
|
||||
}
|
||||
|
||||
type CommandID uint8
|
||||
|
||||
const (
|
||||
CommandIDBeep CommandID = 0xe6
|
||||
CommandIDAck CommandID = 0xd0
|
||||
CommandDecodeData CommandID = 0xf3
|
||||
)
|
||||
|
||||
type Flag uint8
|
||||
|
||||
const (
|
||||
FlagRetransmission Flag = 1
|
||||
)
|
||||
|
||||
type Direction uint8
|
||||
|
||||
const (
|
||||
DirectionFromHost Direction = 4
|
||||
DirectionFromDevice Direction = 0
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
ID CommandID
|
||||
Direction Direction
|
||||
Flags Flag
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (c *Command) Serialize() []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
binary.Write(buf, binary.BigEndian, c.ID)
|
||||
binary.Write(buf, binary.BigEndian, c.Direction)
|
||||
binary.Write(buf, binary.BigEndian, c.Flags)
|
||||
buf.Write(c.Data)
|
||||
res := buf.Bytes()
|
||||
res = append([]byte{uint8(len(res) + 1)}, res...)
|
||||
sum := int16(0)
|
||||
for _, b := range res {
|
||||
sum += int16(b)
|
||||
}
|
||||
sum = -sum
|
||||
buf = bytes.NewBuffer(res)
|
||||
binary.Write(buf, binary.BigEndian, sum)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
type SSI struct {
|
||||
serial SerialPort
|
||||
|
||||
mu sync.Mutex
|
||||
pendingOut *Command
|
||||
|
||||
cbOnBarcode CallbackOnBarcode
|
||||
}
|
||||
|
||||
func NewSSI(serial SerialPort, cbOnBarcode CallbackOnBarcode) *SSI {
|
||||
return &SSI{
|
||||
serial: serial,
|
||||
cbOnBarcode: cbOnBarcode,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSI) reset() {
|
||||
time.Sleep(time.Second)
|
||||
// flush in/out
|
||||
}
|
||||
|
||||
func (s *SSI) Run(ctx context.Context) error {
|
||||
for {
|
||||
buf := make([]byte, 1)
|
||||
_, err := s.serial.Read(ctx, buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading length: %w", err)
|
||||
}
|
||||
size := buf[0]
|
||||
if size < 4 {
|
||||
// Packet too small, desync.
|
||||
log.Printf("Packet too small")
|
||||
s.reset()
|
||||
continue
|
||||
}
|
||||
rest := make([]byte, size+1)
|
||||
ctxT, ctxC := context.WithTimeout(ctx, time.Millisecond*100)
|
||||
_, err = s.serial.Read(ctxT, rest)
|
||||
ctxC()
|
||||
if err != nil {
|
||||
if errors.Is(err, ctxT.Err()) {
|
||||
// Timeout ocurred, reset state machine
|
||||
log.Printf("Read timeout")
|
||||
s.reset()
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("reading rest of packet: %w", err)
|
||||
}
|
||||
|
||||
checksumB := rest[len(rest)-2:]
|
||||
body := rest[:len(rest)-2]
|
||||
var checksum int16
|
||||
binary.Read(bytes.NewBuffer(checksumB), binary.BigEndian, &checksum)
|
||||
|
||||
sum := int16(size)
|
||||
for _, b := range body {
|
||||
sum += int16(b)
|
||||
}
|
||||
if sum != -checksum {
|
||||
// Desynnc
|
||||
log.Printf("Checksum failed, wanted %d, got %d", sum, -checksum)
|
||||
s.reset()
|
||||
}
|
||||
|
||||
cmd := Command{
|
||||
ID: CommandID(body[0]),
|
||||
Direction: Direction(body[1]),
|
||||
Flags: Flag(body[2]),
|
||||
Data: body[3:],
|
||||
}
|
||||
|
||||
if err := s.handleIn(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSI) handleIn(ctx context.Context, cmd Command) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// TODO: handle received retransmissions
|
||||
|
||||
switch cmd.ID {
|
||||
case CommandIDAck:
|
||||
if s.pendingOut != nil {
|
||||
s.pendingOut = nil
|
||||
} else {
|
||||
log.Printf("Received ack for unknown packet")
|
||||
s.reset()
|
||||
}
|
||||
return nil
|
||||
case CommandDecodeData:
|
||||
if (cmd.Flags & FlagRetransmission) == 0 {
|
||||
s.cbOnBarcode(cmd.Data[0], cmd.Data[1:])
|
||||
} else {
|
||||
log.Printf("Retransmitted data from device")
|
||||
}
|
||||
ack := Command{
|
||||
ID: CommandIDAck,
|
||||
Direction: DirectionFromHost,
|
||||
}
|
||||
return s.serial.Write(ctx, ack.Serialize())
|
||||
default:
|
||||
log.Printf("Unknown command received: %+v", cmd)
|
||||
s.reset()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSI) Beep(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
17
ssi_test.go
Normal file
17
ssi_test.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
cmd := Command{
|
||||
ID: CommandIDBeep,
|
||||
Direction: DirectionFromHost,
|
||||
Data: []byte{1},
|
||||
}
|
||||
if want, got := []byte{0x05, 0xe6, 0x04, 0x00, 0x01, 0xff, 0x10}, cmd.Serialize(); !bytes.Equal(want, got) {
|
||||
t.Fatalf("Serialize failed: wanted %+v, got %+v", want, got)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue