Daniel Svitan 995bf90efb
All checks were successful
Gitea Build Action / build (push) Successful in 25s
🐛 Fixes peripheral delay on door open
2025-06-01 12:49:30 +02:00

329 lines
5.8 KiB
Go

package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"github.com/spf13/viper"
"github.com/urfave/cli/v3"
)
var (
server string
token string
)
type ChangeLockReq struct {
Locked bool `json:"locked"`
}
type ChangeAlertReq struct {
Alert bool `json:"alert"`
For int `json:"for"`
}
func load() error {
viper.SetConfigName("config")
viper.SetConfigType("toml")
viper.AddConfigPath("$HOME/.local/share/door-alarm")
err := viper.ReadInConfig()
if err != nil {
return err
}
server = viper.GetString("server")
if server == "" {
return errors.New("'server' property is required")
}
token = viper.GetString("token")
if token == "" {
return errors.New("'token' property is required")
}
return nil
}
func makeGetReq(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", token)
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer func() { res.Body.Close() }()
if res.StatusCode != 200 {
fmt.Printf("<-- %d\n", res.StatusCode)
}
return io.ReadAll(res.Body)
}
func makePostReq(url string, body any) ([]byte, error) {
bodyRaw, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyRaw))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = res.Body.Close() }()
if res.StatusCode != 200 {
fmt.Printf("<-- %d\n", res.StatusCode)
}
return io.ReadAll(res.Body)
}
func main() {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
cmd := &cli.Command{
Name: "doorctl",
Usage: "door-alarm server administration tool",
Commands: []*cli.Command{
{
Name: "version",
Usage: "print version and exit",
Action: func(context.Context, *cli.Command) error {
fmt.Println("version 1.1.0")
return nil
},
},
{
Name: "test",
Usage: "test server connection",
Action: test,
},
{
Name: "open",
Usage: "get door state (opened or closed)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "raw",
Usage: "print raw server output",
},
},
Action: getOpened,
},
{
Name: "lock",
Usage: "change lock status",
Commands: []*cli.Command{
{
Name: "on",
Usage: "lock the door",
Action: createManageLock(true),
},
{
Name: "off",
Usage: "unlock the door",
Action: createManageLock(false),
},
},
Action: getLocked,
},
{
Name: "alert",
Usage: "change alert status",
Commands: []*cli.Command{
{
Name: "on",
Usage: "resume alerts",
Action: createManageAlert(true),
},
{
Name: "off",
Usage: "pause alerts",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "for",
Usage: "pause for x seconds",
},
},
Action: createManageAlert(false),
},
},
Action: getAlert,
},
},
}
err := cmd.Run(context.Background(), os.Args)
if err != nil {
log.Fatal(err)
}
}
func test(context.Context, *cli.Command) error {
err := load()
if err != nil {
return err
}
url := fmt.Sprintf("%s/", server)
data, err := makeGetReq(url)
if err != nil {
return err
}
fmt.Printf("%s", data)
return nil
}
func getOpened(ctx context.Context, cmd *cli.Command) error {
err := load()
if err != nil {
return err
}
opened, err := makeGetReq(fmt.Sprintf("%s/open", server))
if err != nil {
return err
}
if cmd.Bool("raw") {
fmt.Println(opened)
} else {
if string(opened) == "true\n" {
fmt.Println("door is open")
} else {
fmt.Println("door is closed")
}
}
return nil
}
func getLocked(context.Context, *cli.Command) error {
err := load()
if err != nil {
return err
}
locked, err := makeGetReq(fmt.Sprintf("%s/lock", server))
if err != nil {
return err
}
if string(locked) == "true\n" {
fmt.Println("door is locked")
} else {
fmt.Println("door is unlocked")
}
return nil
}
func getAlert(context.Context, *cli.Command) error {
err := load()
if err != nil {
return err
}
alert, err := makeGetReq(fmt.Sprintf("%s/alert", server))
if err != nil {
return err
}
if string(alert) == "true\n" {
fmt.Println("alerts are active")
} else {
fmt.Println("alerts are paused")
}
return nil
}
func createManageLock(locked bool) func(context.Context, *cli.Command) error {
return func(ctx context.Context, cmd *cli.Command) error {
err := load()
if err != nil {
return err
}
var data ChangeLockReq
data.Locked = locked
_, err = makePostReq(fmt.Sprintf("%s/lock", server), data)
if err != nil {
return err
}
var action string
if locked {
action = "locked"
} else {
action = "unlocked"
}
fmt.Printf("door was %s\n", action)
return nil
}
}
func createManageAlert(alert bool) func(context.Context, *cli.Command) error {
return func(ctx context.Context, cmd *cli.Command) error {
err := load()
if err != nil {
return err
}
var data ChangeAlertReq
data.Alert = alert
var secs int
secsRaw := cmd.String("for")
if secsRaw != "" {
secs, err = strconv.Atoi(secsRaw)
if err != nil {
return err
}
data.For = secs
}
_, err = makePostReq(fmt.Sprintf("%s/alert", server), data)
if err != nil {
return err
}
var rest string
if secsRaw != "" {
rest = fmt.Sprintf(" for %d seconds", secs)
}
var action string
if alert {
action = "resumed"
} else {
action = "paused"
}
fmt.Printf("alerts were %sd%s\n", action, rest)
return nil
}
}