380 lines
8.7 KiB
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
|
|
|
|
}
|