package main import ( "bufio" "context" "encoding/json" "fmt" "github.com/joho/godotenv" "io" "net/http" "os" "os/signal" "strings" "sync" "syscall" ) import "time" const wiegand_a = 17 const wiegand_b = 18 const wiegand_bit_timeout = time.Millisecond * 8 const solenoid = 21 type card struct { UidHash string `json:"uid_hash"` } type cardList struct { AllowedUids []struct { Token card `json:"token"` } `json:"allowed_uids"` } type simpleUids struct { tokens []string } type ValidUids map[string]bool // bool has no meaning type Config struct { door string uid_salt string mock string api struct { allowed string longpoll string swipe string key string } } var config Config var globalLock sync.Mutex var validUids ValidUids var wiegand Wiegand func main() { 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") config.mock = os.Getenv("KDOORPI_MOCK_HW") if config.mock == "true" { wiegand = &WiegandMock{} } else { wiegand = WiegandSetup(wiegand_a, wiegand_b, wiegand_bit_timeout, solenoid) } http.DefaultClient.Timeout = 120 * time.Second for { err := reloadTokens() if err == nil { break } time.Sleep(10 * time.Second) } go cardRunner(wiegand) go func() { for { waitEvents() } }() fmt.Printf("Sleeping\n") <-ctx.Done() fmt.Printf("Cleanup\n") // cleanup } 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 { req, err := http.NewRequest(http.MethodGet, config.api.longpoll, nil) if err != nil { return err } req.Header.Add("KEY", config.api.key) resp, err := http.DefaultClient.Do(req) if err != nil { return err } fmt.Printf("%v\n", resp) 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() } } fmt.Printf("%v\n", resp) return nil } func reloadTokens() error { req, err := http.NewRequest(http.MethodGet, config.api.allowed, nil) if err != nil { return err } req.Header.Add("KEY", config.api.key) resp, err := http.DefaultClient.Do(req) if err != nil { return err } fmt.Printf("%v\n", resp) var cl cardList body, err := io.ReadAll(resp.Body) if err != nil { return err } err = json.Unmarshal(body, &cl) if err != nil { return err } globalLock.Lock() defer globalLock.Unlock() validUids = make(ValidUids) for i, val := range cl.AllowedUids { fmt.Printf("%d: %+v\n", i, val.Token.UidHash) validUids[val.Token.UidHash] = true } return nil }