2022-04-02 18:14:21 +00:00
|
|
|
package main
|
|
|
|
|
2022-04-03 17:34:46 +00:00
|
|
|
import (
|
2023-07-28 22:22:26 +00:00
|
|
|
"bufio"
|
2023-07-28 13:09:54 +00:00
|
|
|
"context"
|
2022-04-03 17:34:46 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-07-28 13:09:54 +00:00
|
|
|
"github.com/joho/godotenv"
|
2022-04-03 17:34:46 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
2023-07-28 13:09:54 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2023-07-28 22:22:26 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2023-07-28 13:09:54 +00:00
|
|
|
"syscall"
|
2022-04-03 17:34:46 +00:00
|
|
|
)
|
2022-04-02 18:14:21 +00:00
|
|
|
import "time"
|
|
|
|
|
|
|
|
const wiegand_a = 17
|
|
|
|
const wiegand_b = 18
|
2022-04-03 13:50:21 +00:00
|
|
|
const wiegand_bit_timeout = time.Millisecond * 8
|
2022-04-02 18:14:21 +00:00
|
|
|
const solenoid = 21
|
|
|
|
|
2022-04-03 17:34:46 +00:00
|
|
|
type card struct {
|
|
|
|
UidHash string `json:"uid_hash"`
|
|
|
|
}
|
|
|
|
|
2023-07-28 13:09:54 +00:00
|
|
|
type cardList struct {
|
|
|
|
AllowedUids []struct {
|
|
|
|
Token card `json:"token"`
|
|
|
|
} `json:"allowed_uids"`
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-28 13:09:54 +00:00
|
|
|
type simpleUids struct {
|
|
|
|
tokens []string
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ValidUids map[string]bool // bool has no meaning
|
|
|
|
|
2023-07-28 13:09:54 +00:00
|
|
|
type Config struct {
|
|
|
|
door string
|
|
|
|
uid_salt string
|
2023-07-28 22:22:26 +00:00
|
|
|
mock string
|
2023-07-28 13:09:54 +00:00
|
|
|
api struct {
|
|
|
|
allowed string
|
|
|
|
longpoll string
|
|
|
|
swipe string
|
|
|
|
key string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var config Config
|
2023-07-28 22:22:26 +00:00
|
|
|
var globalLock sync.Mutex
|
|
|
|
var validUids ValidUids
|
|
|
|
var wiegand Wiegand
|
2023-07-28 13:09:54 +00:00
|
|
|
|
2022-04-02 18:14:21 +00:00
|
|
|
func main() {
|
|
|
|
|
2023-07-28 13:09:54 +00:00
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
godotenv.Load()
|
|
|
|
|
|
|
|
config.door = os.Getenv("KDOORPI_DOOR")
|
|
|
|
config.api.allowed = os.Getenv("KDOORPI_API_ALLOWED")
|
|
|
|
config.api.longpoll = os.Getenv("KDOORPI_API_LONGPOLL")
|
|
|
|
config.api.swipe = os.Getenv("KDOORPI_API_SWIPE")
|
|
|
|
config.api.key = os.Getenv("KDOORPI_API_KEY")
|
|
|
|
config.uid_salt = os.Getenv("KDOORPI_UID_SALT")
|
2023-07-28 22:22:26 +00:00
|
|
|
config.mock = os.Getenv("KDOORPI_MOCK_HW")
|
2023-07-28 13:09:54 +00:00
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
if config.mock == "true" {
|
|
|
|
wiegand = &WiegandMock{}
|
|
|
|
} else {
|
|
|
|
wiegand = WiegandSetup(wiegand_a, wiegand_b, wiegand_bit_timeout, solenoid)
|
|
|
|
}
|
2023-07-28 13:09:54 +00:00
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
http.DefaultClient.Timeout = 120 * time.Second
|
2022-04-02 18:14:21 +00:00
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
for {
|
|
|
|
err := reloadTokens()
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(10 * time.Second)
|
|
|
|
}
|
2023-07-28 13:09:54 +00:00
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
go cardRunner(wiegand)
|
2023-07-28 13:09:54 +00:00
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
waitEvents()
|
|
|
|
}
|
|
|
|
}()
|
2023-07-28 13:09:54 +00:00
|
|
|
|
|
|
|
fmt.Printf("Sleeping\n")
|
|
|
|
|
|
|
|
<-ctx.Done()
|
|
|
|
fmt.Printf("Cleanup\n")
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
}
|
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
func cardRunner(w Wiegand) {
|
|
|
|
for {
|
|
|
|
card, err := w.GetCardUid()
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
printCardId(card)
|
|
|
|
hashedHex := hashCardUid(card)
|
|
|
|
fmt.Println(hashedHex)
|
|
|
|
|
|
|
|
globalLock.Lock()
|
|
|
|
ok := validUids[hashedHex]
|
|
|
|
globalLock.Unlock()
|
|
|
|
if ok {
|
|
|
|
fmt.Println("Opening door")
|
|
|
|
w.OpenDoor()
|
|
|
|
} else {
|
|
|
|
fmt.Println("Unknown card")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseNextMessage(r *bufio.Reader) (string, error) {
|
|
|
|
var message string
|
|
|
|
|
|
|
|
for {
|
|
|
|
s, err := r.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return message, err
|
|
|
|
}
|
|
|
|
|
|
|
|
message = message + s
|
|
|
|
nextBytes, err := r.ReadByte()
|
|
|
|
if err != nil {
|
|
|
|
return message, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if nextBytes == '\n' {
|
|
|
|
return message, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r.UnreadByte()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func waitEvents() error {
|
2023-07-28 13:09:54 +00:00
|
|
|
req, err := http.NewRequest(http.MethodGet, config.api.longpoll, nil)
|
2022-04-03 17:34:46 +00:00
|
|
|
if err != nil {
|
2023-07-28 22:22:26 +00:00
|
|
|
return err
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
2023-07-28 13:09:54 +00:00
|
|
|
req.Header.Add("KEY", config.api.key)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
2023-07-28 22:22:26 +00:00
|
|
|
return err
|
2023-07-28 13:09:54 +00:00
|
|
|
}
|
|
|
|
fmt.Printf("%v\n", resp)
|
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
reader := bufio.NewReader(resp.Body)
|
|
|
|
for {
|
|
|
|
msg, err := ParseNextMessage(reader)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, line := range strings.Split(msg, "\n") {
|
|
|
|
data, found_data := strings.CutPrefix(line, "data:")
|
|
|
|
if !found_data {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fmt.Printf("got server data: %q\n", data)
|
|
|
|
if strings.TrimSpace(data) == config.door {
|
|
|
|
wiegand.OpenDoor()
|
|
|
|
}
|
|
|
|
|
|
|
|
go reloadTokens()
|
|
|
|
}
|
|
|
|
|
2023-07-28 13:09:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("%v\n", resp)
|
2023-07-28 22:22:26 +00:00
|
|
|
return nil
|
2023-07-28 13:09:54 +00:00
|
|
|
}
|
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
func reloadTokens() error {
|
2023-07-28 13:09:54 +00:00
|
|
|
req, err := http.NewRequest(http.MethodGet, config.api.allowed, nil)
|
|
|
|
if err != nil {
|
2023-07-28 22:22:26 +00:00
|
|
|
return err
|
2023-07-28 13:09:54 +00:00
|
|
|
}
|
|
|
|
req.Header.Add("KEY", config.api.key)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
2022-04-03 17:34:46 +00:00
|
|
|
if err != nil {
|
2023-07-28 22:22:26 +00:00
|
|
|
return err
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
|
|
|
fmt.Printf("%v\n", resp)
|
|
|
|
|
|
|
|
var cl cardList
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2023-07-28 22:22:26 +00:00
|
|
|
return err
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(body, &cl)
|
|
|
|
if err != nil {
|
2023-07-28 22:22:26 +00:00
|
|
|
return err
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-28 22:22:26 +00:00
|
|
|
globalLock.Lock()
|
|
|
|
defer globalLock.Unlock()
|
|
|
|
validUids = make(ValidUids)
|
2022-04-03 17:34:46 +00:00
|
|
|
for i, val := range cl.AllowedUids {
|
|
|
|
fmt.Printf("%d: %+v\n", i, val.Token.UidHash)
|
2023-07-28 22:22:26 +00:00
|
|
|
validUids[val.Token.UidHash] = true
|
2022-04-03 17:34:46 +00:00
|
|
|
}
|
2023-07-28 22:22:26 +00:00
|
|
|
|
|
|
|
return nil
|
2022-04-02 18:14:21 +00:00
|
|
|
}
|