Successful RFID implementation
This commit is contained in:
parent
2bf5c0ff99
commit
75035e2751
11
go.mod
11
go.mod
@ -4,15 +4,18 @@ go 1.21.4
|
||||
|
||||
require (
|
||||
git.tcp.direct/kayos/zwrap v0.4.2
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/davecgh/go-spew v1.1.0
|
||||
github.com/clausecker/nfc/v2 v2.1.4
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
github.com/stianeikeland/go-rpio/v4 v4.6.0
|
||||
periph.io/x/conn/v3 v3.7.0
|
||||
periph.io/x/devices/v3 v3.7.1
|
||||
periph.io/x/host/v3 v3.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
)
|
||||
|
41
go.sum
41
go.sum
@ -1,11 +1,14 @@
|
||||
git.tcp.direct/kayos/zwrap v0.4.2 h1:yXO21VNkAb+iMi3dOAythw42dvv1bVzXw+TJJXENVdQ=
|
||||
git.tcp.direct/kayos/zwrap v0.4.2/go.mod h1:SA9+Sww1LBKMw54Gjot5t4AhwEgRX1lFhy6pv5Mm78Q=
|
||||
github.com/clausecker/nfc/v2 v2.1.4 h1:zw2Cnny7pxPnuxVMBo+DXqXYETzUN7pMhNEA61yT5gY=
|
||||
github.com/clausecker/nfc/v2 v2.1.4/go.mod h1:BjRBQUQTQmiwh2tEfQ+xBM5xY05sV2gnZ0JRYEHog/o=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
|
||||
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@ -13,6 +16,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -20,26 +25,28 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stianeikeland/go-rpio/v4 v4.6.0 h1:eAJgtw3jTtvn/CqwbC82ntcS+dtzUTgo5qlZKe677EY=
|
||||
github.com/stianeikeland/go-rpio/v4 v4.6.0/go.mod h1:A3GvHxC1Om5zaId+HqB3HKqx4K/AqeckxB7qRjxMK7o=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
periph.io/x/conn/v3 v3.7.0 h1:f1EXLn4pkf7AEWwkol2gilCNZ0ElY+bxS4WE2PQXfrA=
|
||||
periph.io/x/conn/v3 v3.7.0/go.mod h1:ypY7UVxgDbP9PJGwFSVelRRagxyXYfttVh7hJZUHEhg=
|
||||
periph.io/x/devices/v3 v3.7.1 h1:BsExlfYJlZUZoawzpMF7ksgC9f1eBAdqvKRCGvb+VYw=
|
||||
periph.io/x/devices/v3 v3.7.1/go.mod h1:ezQOe8WknDaMbKZXVwQUQkIauyLyJshwAHkIohHXA94=
|
||||
periph.io/x/host/v3 v3.8.0 h1:T5ojZ2wvnZHGPS4h95N2ZpcCyHnsvH3YRZ1UUUiv5CQ=
|
||||
periph.io/x/host/v3 v3.8.0/go.mod h1:rzOLH+2g9bhc6pWZrkCrmytD4igwQ2vxFw6Wn6ZOlLY=
|
||||
|
152
main.go
152
main.go
@ -1,104 +1,92 @@
|
||||
//go:build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"git.tcp.direct/kayos/zwrap"
|
||||
"github.com/cretz/bine/tor"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/stianeikeland/go-rpio/v4"
|
||||
|
||||
"git.tcp.direct/kayos/door5/config"
|
||||
"git.tcp.direct/kayos/door5/iot"
|
||||
"git.tcp.direct/kayos/door5/logger"
|
||||
)
|
||||
|
||||
type Knobs struct {
|
||||
conf tor.ListenConf
|
||||
log zwrap.Logger
|
||||
tor.Tor
|
||||
}
|
||||
type BlueDoor struct {
|
||||
conf *Knobs
|
||||
listeners []net.Listener
|
||||
localPaths []string
|
||||
var conf *config.Config
|
||||
|
||||
func OpenDoor() {
|
||||
pin := rpio.Pin(conf.Pins[iot.Door])
|
||||
pin.Output()
|
||||
pin.High()
|
||||
}
|
||||
|
||||
func MemoryMapOSFolder() (path string, err error) {
|
||||
// create memory mapped folder for tor data directory (linux)
|
||||
// un
|
||||
return "", nil
|
||||
func CloseDoor() {
|
||||
pin := rpio.Pin(conf.Pins[iot.Door])
|
||||
pin.Output()
|
||||
pin.Low()
|
||||
}
|
||||
|
||||
func NewListener(listen string) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func NewOnion() (*tor.Tor, error) {
|
||||
log := logger.Get()
|
||||
|
||||
log.Logger.Info().Msg("starting and registering onion service...")
|
||||
|
||||
t, err := tor.Start(nil, &tor.StartConf{
|
||||
RetainTempDataDir: false,
|
||||
DebugWriter: logger.Get().With().Str("module", "tor").Logger(),
|
||||
|
||||
NoAutoSocksPort: false,
|
||||
GeoIPFileReader: nil,
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicf("Unable to start Tor: %v", err)
|
||||
}
|
||||
defer func(t *tor.Tor) {
|
||||
if err = t.Close(); err != nil {
|
||||
log.Panicf("Unable to close Tor: %v", err)
|
||||
func watchButton() {
|
||||
logger.Get().Logger.Debug().Uint8("pin", uint8(conf.Pins[iot.Button])).Msg("exit button daemon started")
|
||||
defer logger.Get().Logger.Debug().Msg("exit button daemon stopped")
|
||||
pin := rpio.Pin(conf.Pins[iot.Button])
|
||||
pin.Input()
|
||||
pin.PullUp()
|
||||
count := 0
|
||||
for {
|
||||
if pin.Read() != rpio.Low {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
if count > 0 {
|
||||
count--
|
||||
}
|
||||
continue
|
||||
}
|
||||
}(t)
|
||||
|
||||
// Wait at most a few minutes to publish the service
|
||||
listenCtx, listenCancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer listenCancel()
|
||||
|
||||
// Create a v3 onion service to listen on any port but show as 80
|
||||
onion, err := t.Listen(listenCtx, &tor.ListenConf{
|
||||
LocalPort: 0,
|
||||
LocalListener: nil,
|
||||
RemotePorts: []int{80},
|
||||
Key: nil,
|
||||
Version3: false,
|
||||
ClientAuths: nil,
|
||||
MaxStreams: 0,
|
||||
DiscardKey: false,
|
||||
Detach: false,
|
||||
NonAnonymous: false,
|
||||
MaxStreamsCloseCircuit: false,
|
||||
NoWait: false,
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicf("Unable to create onion service: %v", err)
|
||||
}
|
||||
defer func(onion *tor.OnionService) {
|
||||
if err = onion.Close(); err != nil {
|
||||
log.Panicf("Unable to close onion service: %v", err)
|
||||
count++
|
||||
if count >= 100 {
|
||||
logger.Get().Logger.Info().Int("count", count).Msg("door opened")
|
||||
OpenDoor()
|
||||
time.Sleep(5 * time.Second)
|
||||
CloseDoor()
|
||||
count = 0
|
||||
}
|
||||
}(onion)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
log.Logger.Info().Msgf("live: %v.onion", onion.ID)
|
||||
return t, nil
|
||||
func loadConfig() {
|
||||
var err error
|
||||
path, _ := os.UserConfigDir()
|
||||
path = filepath.Join(path, "door5", "config.toml")
|
||||
if conf, err = config.ReadConfig(path); err != nil {
|
||||
println(err.Error())
|
||||
println("Writing default config")
|
||||
_ = os.MkdirAll(filepath.Dir(path), 0755)
|
||||
c := &config.Config{
|
||||
Pins: config.Pins{
|
||||
iot.Door: 22,
|
||||
iot.Button: 5,
|
||||
},
|
||||
}
|
||||
if err = c.WriteConfig(path); err != nil {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
conf = c
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
o, e := NewOnion()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
logger.Get().Logger.Info().Msg("starting door5")
|
||||
logger.Get().Logger.Trace().Msg("loading config")
|
||||
loadConfig()
|
||||
logger.Get().Logger.Debug().Msg(spew.Sdump(conf))
|
||||
logger.Get().Logger.Info().Msg("initializing gpio")
|
||||
if err := rpio.Open(); err != nil {
|
||||
logger.Get().Logger.Fatal().Msg(err.Error())
|
||||
}
|
||||
pi, err := o.Control.ProtocolInfo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
spew.Dump(pi)
|
||||
|
||||
go watchButton()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
379
pkg/access/rfid.go
Normal file
379
pkg/access/rfid.go
Normal file
@ -0,0 +1,379 @@
|
||||
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/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
|
||||
|
||||
}
|
122
pkg/access/rfid_test.go
Normal file
122
pkg/access/rfid_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Helper function to create a mock card
|
||||
func mockCard(uid string, cardType string) *Card {
|
||||
return &Card{
|
||||
UID: uid,
|
||||
Type: cardType,
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewCardTimeSeries tests the NewCardTimeSeries constructor.
|
||||
func TestNewCardTimeSeries(t *testing.T) {
|
||||
cts := NewCardTimeSeries()
|
||||
if cts.Map == nil || cts.crossRef == nil || cts.mu == nil {
|
||||
t.Error("NewCardTimeSeries() failed to initialize all fields")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCardTimeSeries_CheckIn tests the CheckIn method.
|
||||
func TestCardTimeSeries_CheckIn(t *testing.T) {
|
||||
cts := NewCardTimeSeries()
|
||||
card := mockCard("12345", "TestType")
|
||||
cts.CheckIn(card)
|
||||
|
||||
if _, ok := cts.Map[cts.last]; !ok {
|
||||
t.Errorf("CheckIn() failed to add card to Map")
|
||||
}
|
||||
|
||||
if _, ok := cts.crossRef[card]; !ok {
|
||||
t.Errorf("CheckIn() failed to add card to crossRef")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCardTimeSeries_Get tests the Get method.
|
||||
func TestCardTimeSeries_Get(t *testing.T) {
|
||||
cts := NewCardTimeSeries()
|
||||
card := mockCard("12345", "TestType")
|
||||
now := time.Now()
|
||||
cts.Map[now] = card
|
||||
|
||||
if got := cts.Get(now); !reflect.DeepEqual(got, card) {
|
||||
t.Errorf("Get() = %v, want %v", got, card)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCardTimeSeries_GetClosest tests the GetClosest method.
|
||||
func TestCardTimeSeries_GetClosest(t *testing.T) {
|
||||
cts := NewCardTimeSeries()
|
||||
card1 := mockCard("12345", "TestType")
|
||||
card2 := mockCard("67890", "TestType")
|
||||
now := time.Now()
|
||||
cts.Map[now] = card1
|
||||
cts.Map[now.Add(-1*time.Hour)] = card2
|
||||
|
||||
if got := cts.GetClosest(now.Add(-30 * time.Minute)); !reflect.DeepEqual(got, card2) {
|
||||
t.Errorf("GetClosest() = %v, want %v", got, card2)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCardTimeSeries_GetLast tests the GetLast method.
|
||||
func TestCardTimeSeries_GetLast(t *testing.T) {
|
||||
cts := NewCardTimeSeries()
|
||||
card := mockCard("12345", "TestType")
|
||||
cts.CheckIn(card)
|
||||
|
||||
if got := cts.GetLast(); !reflect.DeepEqual(got, card) {
|
||||
t.Errorf("GetLast() = %v, want %v", got, card)
|
||||
}
|
||||
}
|
||||
|
||||
// TestKeys_ListenForTags tests the ListenForTags method.
|
||||
func TestKeys_ListenForTags(t *testing.T) {
|
||||
// This test may require mocking of the NFC reader and its methods
|
||||
// Assuming a mock implementation is available, this test would simulate the behavior of the ListenForTags method
|
||||
}
|
||||
|
||||
// TestKeys_ListenForOneTag tests the ListenForOneTag method.
|
||||
func TestKeys_ListenForOneTag(t *testing.T) {
|
||||
connString := os.Getenv("TEST_RFID")
|
||||
if connString == "" {
|
||||
t.Skip("TEST_RFID environment variable not set")
|
||||
}
|
||||
|
||||
// Assuming NewRFID function and related dependencies are properly mocked
|
||||
rfid, err := NewRFID(connString, SPI) // Or I2C, depending on the test setup
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create RFID instance: %v", err)
|
||||
}
|
||||
defer rfid.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
card, err := rfid.ListenForOneTag(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("ListenForOneTag() returned an error: %v", err)
|
||||
}
|
||||
|
||||
if card == nil {
|
||||
t.Errorf("ListenForOneTag() returned nil, expected a card")
|
||||
}
|
||||
|
||||
t.Run("CheckIn", func(t *testing.T) {
|
||||
if rfid.AccessLog.GetLast() != card {
|
||||
t.Errorf("ListenForOneTag() failed to check card into timeseries log")
|
||||
}
|
||||
if rfid.AccessLog.GetClosest(time.Now()) != card {
|
||||
t.Errorf("ListenForOneTag() failed to check card into timeseries log")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Note: Additional mocking and setup may be required to test methods that interact with external dependencies like NFC reader.
|
60
pkg/access/timeseries.go
Normal file
60
pkg/access/timeseries.go
Normal file
@ -0,0 +1,60 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CardTimeSeries struct {
|
||||
Map map[time.Time]*Card `json:"nfc_checkins"`
|
||||
crossRef map[*Card][]time.Time
|
||||
last time.Time
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCardTimeSeries() *CardTimeSeries {
|
||||
return &CardTimeSeries{
|
||||
Map: make(map[time.Time]*Card),
|
||||
crossRef: make(map[*Card][]time.Time),
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cts *CardTimeSeries) CheckIn(c *Card) {
|
||||
cts.mu.Lock()
|
||||
defer cts.mu.Unlock()
|
||||
tnow := time.Now()
|
||||
cts.Map[tnow] = c
|
||||
if cts.crossRef[c] == nil {
|
||||
cts.crossRef[c] = make([]time.Time, 0)
|
||||
}
|
||||
cts.crossRef[c] = append(cts.crossRef[c], tnow)
|
||||
cts.last = tnow
|
||||
}
|
||||
|
||||
func (cts *CardTimeSeries) Get(t time.Time) *Card {
|
||||
cts.mu.RLock()
|
||||
defer cts.mu.RUnlock()
|
||||
return cts.Map[t]
|
||||
}
|
||||
|
||||
func (cts *CardTimeSeries) GetClosest(t time.Time) *Card {
|
||||
cts.mu.RLock()
|
||||
defer cts.mu.RUnlock()
|
||||
if cts.Map[t] != nil {
|
||||
return cts.Map[t]
|
||||
}
|
||||
var closest time.Time
|
||||
for k := range cts.Map {
|
||||
if k.Before(t) && k.After(closest) {
|
||||
closest = k
|
||||
}
|
||||
}
|
||||
return cts.Map[closest]
|
||||
}
|
||||
|
||||
func (cts *CardTimeSeries) GetLast() *Card {
|
||||
cts.mu.RLock()
|
||||
defer cts.mu.RUnlock()
|
||||
return cts.Map[cts.last]
|
||||
}
|
42
pkg/config/toml.go
Normal file
42
pkg/config/toml.go
Normal file
@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
|
||||
"git.tcp.direct/kayos/door5/iot"
|
||||
)
|
||||
|
||||
type Pins map[iot.Thing]iot.GPIO
|
||||
|
||||
type Config struct {
|
||||
Pins Pins `toml:"pins"`
|
||||
}
|
||||
|
||||
func (p Pins) MarshalTOML() ([]byte, error) {
|
||||
var rendered string
|
||||
for thing, pin := range p {
|
||||
rendered += fmt.Sprintf("%s = %d\n", thing.String(), pin)
|
||||
}
|
||||
return []byte(rendered), nil
|
||||
}
|
||||
|
||||
func ReadConfig(path string) (*Config, error) {
|
||||
var config = &Config{}
|
||||
b, e := os.ReadFile(path)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
err := toml.Unmarshal(b, config)
|
||||
return config, err
|
||||
}
|
||||
|
||||
func (c *Config) WriteConfig(path string) error {
|
||||
b, e := toml.Marshal(c)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return os.WriteFile(path, b, 0644)
|
||||
}
|
32
pkg/iot/types.go
Normal file
32
pkg/iot/types.go
Normal file
@ -0,0 +1,32 @@
|
||||
package iot
|
||||
|
||||
type (
|
||||
Thing uint16
|
||||
GPIO uint8
|
||||
)
|
||||
|
||||
const (
|
||||
Door Thing = iota
|
||||
Button
|
||||
NFC
|
||||
Webcam
|
||||
)
|
||||
|
||||
func (t Thing) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
func (t Thing) String() string {
|
||||
switch t {
|
||||
case Door:
|
||||
return "Door"
|
||||
case Button:
|
||||
return "Button"
|
||||
case NFC:
|
||||
return "NFC"
|
||||
case Webcam:
|
||||
return "Webcam"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package logger
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
@ -17,7 +18,7 @@ var (
|
||||
|
||||
func Get() *zwrap.Logger {
|
||||
setupOnce.Do(func() {
|
||||
if err := Setup("", zerolog.InfoLevel); err != nil {
|
||||
if err := Setup("", zerolog.TraceLevel); err != nil {
|
||||
println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
@ -42,7 +43,7 @@ func fileWriter(path string) (io.Writer, error) {
|
||||
return nil, err
|
||||
}
|
||||
var f *os.File
|
||||
if f, err = os.OpenFile(path+"/openspa.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil {
|
||||
if f, err = os.OpenFile(filepath.Join(path, "door5.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
Loading…
Reference in New Issue
Block a user