jankssi/ssi.go
2024-08-20 23:40:32 +02:00

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
}