door5/pkg/access/rfid.go

380 lines
8.7 KiB
Go

package access
// built for PN53x
import (
"context"
"encoding/hex"
"fmt"
"net"
"time"
"github.com/clausecker/nfc/v2"
"github.com/rs/zerolog"
"git.tcp.direct/kayos/door5/pkg/logger"
)
type ConnectionMode uint8
const (
I2C ConnectionMode = iota
SPI
)
type Keys struct {
*nfc.Device
AccessLog *CardTimeSeries
incoming chan nfc.Target
connectivity ConnectionMode
infoString string
readDeadline time.Time
writeDeadline time.Time
resetPin int
}
type localAddr struct {
*Keys
}
func (la *localAddr) String() string {
return la.Keys.infoString
}
func (la *localAddr) Network() string {
switch la.Keys.connectivity {
case I2C:
return "i2c"
case SPI:
return "spi"
}
return "unknown"
}
func (rfid *Keys) Write(b []byte) (n int, err error) {
logger.Get().Logger.Panic().Msg("not implemented")
return 0, nil
}
func (rfid *Keys) Network() string {
switch rfid.connectivity {
case I2C:
return "i2c"
case SPI:
return "spi"
}
return "unknown"
}
func (rfid *Keys) LocalAddr() net.Addr {
return &localAddr{rfid}
}
func (rfid *Keys) RemoteAddr() net.Addr {
return rfid
}
func (rfid *Keys) SetDeadline(t time.Time) error {
rfid.readDeadline = t
rfid.writeDeadline = t
return nil
}
func (rfid *Keys) SetReadDeadline(t time.Time) error {
rfid.readDeadline = t
return nil
}
func (rfid *Keys) SetWriteDeadline(t time.Time) error {
rfid.writeDeadline = t
return nil
}
func (rfid *Keys) HasIRQ() bool {
return rfid.connectivity == SPI && rfid.resetPin != 0
}
//nolint:typecheck
func NewRFID(connString string, mode ConnectionMode, ResetPin ...int) (*Keys, error) {
log := logger.Get().Logger
lg := log.With().Str("conn_string", connString).Logger()
log = &lg
reader := &Keys{
AccessLog: NewCardTimeSeries(),
}
reader.incoming = make(chan nfc.Target, 1)
reader.connectivity = mode
if len(ResetPin) > 0 {
lg = log.With().Int("reset_pin", ResetPin[0]).Logger()
log = &lg
reader.resetPin = ResetPin[0]
}
tries := 0
lg = log.With().Int("tries", tries).Bool("hasIRQ", reader.HasIRQ()).Logger()
log = &lg
try:
log.Trace().Msg("opening connection to NFC reader")
dev, err := nfc.Open(connString)
switch {
case err != nil && reader.HasIRQ():
if tries > 3 {
return nil, fmt.Errorf("failed to open device: %w", err)
}
log.Debug().Msg("errored during open, have IRQ, resetting and trying again...")
reader.Reset()
tries++
goto try
case err != nil:
log.Debug().Err(err).Msg("FUBAR")
return nil, fmt.Errorf("failed to open device: %w", err)
default:
}
reader.Device = &dev
if reader.infoString, err = reader.Information(); err != nil {
log.Warn().Err(err).Msg("failed to get device info, continuing anyway...")
}
if reader.infoString != "" {
log.Debug().Str("info", reader.infoString).Msg("got device info")
}
log.Trace().Msg("initializing NFC reader")
if err = reader.InitiatorInit(); err != nil {
log.Debug().Err(err).Msg("FUBAR")
return nil, fmt.Errorf("failed to initialize reader: %w", err)
}
return reader, nil
}
func (rfid *Keys) Reset() {
log := logger.Get().Logger
log.Debug().Msg("Resetting the RFID chip...")
log.Panic().Msg("haha jk not implemented yet")
}
func (rfid *Keys) Close() error {
if rfid.HasIRQ() {
rfid.Reset()
}
return rfid.Device.Close()
}
type tagReadStatus struct {
err error
tagCount int
currentTarget nfc.Target
found []*Card
}
func (trs *tagReadStatus) Run(e *zerolog.Event, level zerolog.Level, message string) {
stringers := make([]fmt.Stringer, len(trs.found))
for i, v := range trs.found {
stringers[i] = v
}
e.Int("tagCount", trs.tagCount).Stringers("found", stringers)
if trs.err != nil {
e.Err(trs.err)
}
}
func (trs *tagReadStatus) Error() string {
return trs.err.Error()
}
type Card struct {
nfc.Target
Type string `json:"type"`
Details map[string]interface{} `json:"details"`
UID string `json:"uid"`
}
func (c *Card) String() string {
return fmt.Sprintf("%s (%s)", c.UID, c.Type)
}
func (rfid *Keys) ListenForTags(ctx context.Context, want int) ([]*Card, error) {
var status = &tagReadStatus{
err: nil,
tagCount: 0,
found: make([]*Card, 0),
}
log := logger.Get().Logger.Hook(status)
var modulations = []nfc.Modulation{
{Type: nfc.ISO14443a, BaudRate: nfc.Nbr106},
{Type: nfc.ISO14443b, BaudRate: nfc.Nbr106},
{Type: nfc.Felica, BaudRate: nfc.Nbr212},
{Type: nfc.Felica, BaudRate: nfc.Nbr424},
{Type: nfc.Jewel, BaudRate: nfc.Nbr106},
{Type: nfc.ISO14443biClass, BaudRate: nfc.Nbr106},
}
var (
errChan = make(chan error, want)
)
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
go func() {
for {
select {
case <-ctx.Done():
var err = ctx.Err()
if rfid.LastError() != nil {
err = fmt.Errorf("error from NFC reader: %w + context canceled: %s", rfid.LastError(), ctx.Err())
}
errChan <- err
return
default:
}
var found int
if found, status.currentTarget, status.err =
rfid.InitiatorPollTarget(modulations, 1, 300*time.Millisecond); status.err != nil || found == 0 {
log.Warn().Err(status.err).Int("found", found).Msg("error polling for target")
continue
}
status.tagCount++
log.Debug().Msgf("got tag: %s", status.currentTarget.String())
var (
card *Card
)
// Transform the target to a specific tag Type and send the UID to the channel
switch status.currentTarget.Modulation() {
case nfc.Modulation{Type: nfc.ISO14443a, BaudRate: nfc.Nbr106}:
cardCasted := status.currentTarget.(*nfc.ISO14443aTarget)
card = &Card{
UID: hex.EncodeToString(cardCasted.UID[:cardCasted.UIDLen]),
Type: "ISO14443a",
Target: cardCasted,
}
case nfc.Modulation{Type: nfc.ISO14443b, BaudRate: nfc.Nbr106}:
cardCasted := status.currentTarget.(*nfc.ISO14443bTarget)
card = &Card{
UID: hex.EncodeToString(cardCasted.ApplicationData[:len(cardCasted.ApplicationData)]),
Type: "ISO14443b",
Target: cardCasted,
Details: map[string]interface{}{
"ProtocolInfo": cardCasted.ProtocolInfo,
"BaudRate": cardCasted.Modulation().BaudRate,
"ApplicationData": cardCasted.ApplicationData,
"PUPI": cardCasted.Pupi,
"CardIdentifier": cardCasted.CardIdentifier,
},
}
case nfc.Modulation{Type: nfc.Felica, BaudRate: nfc.Nbr212},
nfc.Modulation{Type: nfc.Felica, BaudRate: nfc.Nbr424}:
cardCasted := status.currentTarget.(*nfc.FelicaTarget)
card = &Card{
UID: hex.EncodeToString(cardCasted.ID[:cardCasted.Len]),
Type: "Felica",
Target: cardCasted,
Details: map[string]interface{}{
"Len": cardCasted.Len,
"Pad": cardCasted.Pad,
"SysCode": cardCasted.SysCode,
"ResCode": cardCasted.ResCode,
},
}
switch cardCasted.Modulation().BaudRate {
case nfc.Nbr212:
card.Details["kbps"] = 212
case nfc.Nbr424:
card.Details["kbps"] = 424
default:
}
case nfc.Modulation{Type: nfc.Jewel, BaudRate: nfc.Nbr106}:
cardCasted := status.currentTarget.(*nfc.JewelTarget)
card = &Card{
UID: hex.EncodeToString(cardCasted.ID[:len(cardCasted.ID)]),
Type: "Jewel",
Target: cardCasted,
Details: map[string]interface{}{
"SensRes": cardCasted.SensRes,
},
}
case nfc.Modulation{Type: nfc.ISO14443biClass, BaudRate: nfc.Nbr106}:
cardCasted := status.currentTarget.(*nfc.ISO14443biClassTarget)
card = &Card{
UID: hex.EncodeToString(cardCasted.UID[:len(cardCasted.UID)]),
Type: "ISO14443biClass",
Target: cardCasted,
Details: map[string]interface{}{
"Baud": cardCasted.Baud,
},
}
}
cardLog := log.With().Fields(card.Details).Str("uid", card.UID).Str("type", card.Type).Logger()
cardLog.Info().Msg("+1 card")
cardLog.Trace().Msg("checking card into timeseries log")
rfid.AccessLog.CheckIn(card)
cardLog.Trace().Msg("checked card into timeseries log")
status.found = append(status.found, card)
if status.tagCount >= want {
cancel()
return
}
time.Sleep(300 * time.Millisecond)
}
}()
select {
case e := <-errChan:
return status.found, e
case <-ctx.Done():
switch status.tagCount {
case 0:
return nil, fmt.Errorf("failed to read any tags")
case want:
return status.found, nil
default:
return status.found, fmt.Errorf("failed to read %d tags, only read %d", want, status.tagCount)
}
}
}
func (rfid *Keys) ListenForOneTag(ctx context.Context) (*Card, error) {
if cards, err := rfid.ListenForTags(ctx, 1); err != nil {
return nil, err
} else {
return cards[0], nil
}
}
func (rfid *Keys) Read(buf []byte) (int, error) {
found, err := rfid.ListenForOneTag(context.Background())
if err != nil {
return 0, err
}
return copy(buf, []byte(found.UID)), nil
}