diff --git a/build.bat b/build.bat index a5e17d2..5bd6972 100644 --- a/build.bat +++ b/build.bat @@ -9,4 +9,5 @@ REM Optional: Add Go binary to the path if not already set REM set PATH=C:\Go\bin;%PATH% REM Compile the Go application -go build -o bin/wgreader_rpi .\cmd\wgreader\main.go \ No newline at end of file +go build -o bin/wgreader_rpi .\cmd\wgreader\main.go +go build -o bin/dooropener_rpi .\cmd\dooropener\main.go \ No newline at end of file diff --git a/cmd/dooropener/main.go b/cmd/dooropener/main.go new file mode 100644 index 0000000..df7e241 --- /dev/null +++ b/cmd/dooropener/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/themulle/chronokeyaccess/pkg/dooropener" +) + +func main() { + relayPin := flag.Int("relaypin", 18, "relay pin number") + closedState := flag.Bool("closedstate", true, "default closed state") + logLevelFlag := flag.String("loglevel", "info", "Log level (debug, info, error)") + open := flag.Bool("open", true, "initialize opener to default state") + // Parse command-line flags + flag.Parse() + + setupLogger(*logLevelFlag) + + slog.Info("starting door opener") + opener := dooropener.NewDoorOpener(*relayPin, *closedState) + opener.InitAsOutput() + if *open { + opener.OpenDoor() + } + + slog.Info("done") +} + +func startShutdownListener(cancel context.CancelFunc) { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-c + log.Println("shutdown") + cancel() + }() +} + +func setupLogger(logLevelFlag string) error { + logLevel := slog.LevelInfo + switch logLevelFlag { + case "debug": + logLevel = slog.LevelDebug + case "info": + logLevel = slog.LevelInfo + case "error": + logLevel = slog.LevelError + default: + return fmt.Errorf("Invalid log level: %s. Use 'debug', 'info', or 'error'", logLevelFlag) + } + + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: logLevel, + }))) + + return nil +} diff --git a/cmd/web/coderequest.go b/cmd/web/coderequest.go index e277eaa..1fd8459 100644 --- a/cmd/web/coderequest.go +++ b/cmd/web/coderequest.go @@ -2,9 +2,7 @@ package main import ( "fmt" - "log" "strings" - "time" "github.com/themulle/chronokeyaccess/internal/store" "github.com/themulle/chronokeyaccess/pkg/accesslog" @@ -32,16 +30,10 @@ func getCodes(cr CodeRequest) (codemanager.EntranceCodes, error) { return retval, err } for _, dayTimeString := range cr.DayTime { - var dayTime time.Time - var err error - if dayTime, err = time.Parse("2006-01-02T15:04:05-0700", dayTimeString); err == nil { - } else if dayTime, err = time.Parse("2006-01-02", dayTimeString); err == nil { - } else if dayTime, err = time.Parse("2006-01-02T15:04", dayTimeString); err == nil { - } else if dayTime, err = time.Parse("01.02.2006", dayTimeString); err == nil { - } else { + dayTime, err := dateparser.Parse(dayTimeString) + if err != nil { return retval, fmt.Errorf("invalid date format: %s", dayTimeString) } - log.Printf("code request for: %s", dayTime.String()) dayCodes := cm.GetEntranceCodes(dayTime) if cr.ExactMatch { tmp := codemanager.EntranceCodes{} @@ -78,49 +70,8 @@ func getCodes(cr CodeRequest) (codemanager.EntranceCodes, error) { return retval.Uniq(), nil } -func getSeriesPin(cr CodeRequest) (codemanager.EntranceCodes, error) { - retval := codemanager.EntranceCodes{} - var cm codemanager.CodeManager - - if codeManagerStore, err := store.LoadConfiguration("config.json", "personalcodes.csv", true); err != nil { - return retval, err - } else if cm, err = codemanager.InitFromStore(codeManagerStore); err != nil { - return retval, err - } else { - - /*for _,s := range codeManagerStore.Slots { - if ccs, err:=s.(*codemanager.CronCodeSlot); err==nil { - - } - }*/ - } - - for _, dayTimeString := range cr.DayTime { - var dayTime time.Time - var err error - if dayTime, err = dateparser.Parse(dayTimeString); err != nil { - return retval, fmt.Errorf("invalid date format: %s", dayTimeString) - } - log.Printf("code request for: %s", dayTime.String()) - dayCodes := cm.GetEntranceCodes(dayTime) - if cr.ExactMatch { - tmp := codemanager.EntranceCodes{} - for _, d := range dayCodes { - if d.Start == dayTime { - tmp = append(tmp, d) - } - } - dayCodes = tmp - } - retval = append(retval, dayCodes...) - } - - retval.Sort() - return retval.Uniq(), nil -} - func getAccessLogs() (accesslog.AccessLogs, error) { - retval := accesslog.AccessLogs{} + var retval accesslog.AccessLogs var err error if retval, err = store.LoadAccessLogCSV("accesslog.csv"); err != nil { diff --git a/cmd/wgreader/main.go b/cmd/wgreader/main.go index eae0226..30b2e49 100644 --- a/cmd/wgreader/main.go +++ b/cmd/wgreader/main.go @@ -1,46 +1,93 @@ package main import ( + "context" + "flag" "fmt" + "log" "log/slog" "os" + "os/signal" + "syscall" "time" "github.com/themulle/chronokeyaccess/pkg/wiegand" "github.com/themulle/chronokeyaccess/pkg/wiegand/wieganddecoder" ) -func main() { - reader := wiegand.NewWiegandReader(14, 15) - decoder := wieganddecoder.NewCommonDecoder() +func setupLogger(logLevelFlag string) error { + logLevel := slog.LevelInfo + switch logLevelFlag { + case "debug": + logLevel = slog.LevelDebug + case "info": + logLevel = slog.LevelInfo + case "error": + logLevel = slog.LevelError + default: + return fmt.Errorf("Invalid log level: %s. Use 'debug', 'info', or 'error'", logLevelFlag) + } - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: slog.LevelInfo, + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: logLevel, }))) + return nil +} + +func main() { + data0pin := flag.Int("data0pin", 14, "data0 pin number") + data1pin := flag.Int("data1pin", 15, "data1 pin number") + logLevelFlag := flag.String("loglevel", "info", "Log level (debug, info, error)") + // Parse command-line flags + flag.Parse() + + setupLogger(*logLevelFlag) + + reader := wiegand.NewWiegandReader(*data0pin, *data1pin) + decoder := wieganddecoder.NewCommonDecoder() + slog.Info("starting wiegand reader") if err := reader.Start(); err != nil { slog.Error("Error initializing Wiegand reader", "error", err) return } - for { - time.Sleep(time.Millisecond * 100) - bits, start, stop := reader.GetCache() - if decoder.CheckInputComplete(bits, start, stop) { - reader.ResetCache() - number, err := decoder.Decode(bits) - slog.Info("got input", "number", number, - "cache", wieganddecoder.BitsToString(bits), - "error", err, - "start", start, - "stop", stop) - } + ctx, cancel := context.WithCancel(context.Background()) + startShutdownListener(cancel) - } + go func() { + for { + time.Sleep(time.Millisecond * 100) + bits, start, stop := reader.GetCache() + if decoder.CheckInputComplete(bits, start, stop) { + reader.ResetCache() + number, err := decoder.Decode(bits) + slog.Info("got input", "number", number, + "cache", wieganddecoder.BitsToString(bits), + "error", err, + "start", start, + "stop", stop) + + fmt.Printf("%d %s %s %s %t\n", number, start.Format(time.RFC3339), stop.Format(time.RFC3339), wieganddecoder.BitsToString(bits), err == nil) + } + } + }() + + <-ctx.Done() reader.Close() - fmt.Println("quitting") + slog.Info("done") + +} +func startShutdownListener(cancel context.CancelFunc) { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-c + log.Println("shutdown") + cancel() + }() } diff --git a/pkg/dooropener/dooropener.go b/pkg/dooropener/dooropener.go new file mode 100644 index 0000000..955e916 --- /dev/null +++ b/pkg/dooropener/dooropener.go @@ -0,0 +1,64 @@ +package dooropener + +import ( + "time" + + "github.com/warthog618/go-gpiocdev" +) + +type DoorOpener struct { + RelayPin int + Chip string + ClosedState int + OpenState int + Runtime time.Duration +} + +// NewWiegandReader erstellt einen neuen WiegandReader +func NewDoorOpener(relayPin int, closedState bool) *DoorOpener { + opener := &DoorOpener{ + RelayPin: relayPin, + Chip: "gpiochip0", + Runtime: time.Second * 3, + } + if closedState { + opener.ClosedState = 1 + opener.OpenState = 0 + } else { + opener.ClosedState = 0 + opener.OpenState = 1 + } + + return opener +} + +func (d *DoorOpener) InitAsOutput() error { + line, err := gpiocdev.RequestLine(d.Chip, d.RelayPin, gpiocdev.AsOutput(d.ClosedState)) + if err != nil { + return err + } + return line.Close() +} + +func (d *DoorOpener) OpenDoor() error { + line, err := gpiocdev.RequestLine(d.Chip, d.RelayPin, gpiocdev.AsOutput(d.ClosedState)) + if err != nil { + return err + } + defer line.Close() + defer line.SetValue(d.ClosedState) + + start := time.Now() + for time.Since(start) < d.Runtime { + if err = line.SetValue(d.OpenState); err != nil { + return err + } + time.Sleep(time.Millisecond * 400) + if err = line.SetValue(d.ClosedState); err != nil { + return err + } + time.Sleep(time.Millisecond * 100) + } + + return nil +}