179 lines
3.4 KiB
Go
179 lines
3.4 KiB
Go
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
|
|
}
|