Files
HATray/internal/app.go

191 lines
4.3 KiB
Go

package internal
import (
"embed"
"fmt"
"log/slog"
"os"
"os/signal"
"runtime"
"time"
"github.com/getlantern/systray"
dotenv "github.com/joho/godotenv"
"golang.org/x/text/cases"
"golang.org/x/text/language"
ga "saml.dev/gome-assistant"
)
var ()
type TrayApp struct {
doorIdentifier string
log *slog.Logger
stateChannel chan string
app *ga.App
service *ga.Service
}
// Status will return the operational status of the service
func (ta *TrayApp) Status() Status {
return StatusUnknown
}
func (ta *TrayApp) State() string {
// TODO: Implement this method
return ""
}
func (ta *TrayApp) Connected() bool {
// TODO: Implement this method
return false
}
func (ta *TrayApp) Reload() error {
// TODO: Implement this method
return nil
}
func (ta *TrayApp) Pause() error {
// TODO: Implement this method
return nil
}
func (ta *TrayApp) Resume() error {
// TODO: Implement this method
return nil
}
func NewApp() *TrayApp {
// Connect to Home Assistant
app, err := ga.NewApp(ga.NewAppRequest{
IpAddress: "home.imfucked.lol", // Replace with your Home Assistant IP Address
HAAuthToken: os.Getenv("HA_AUTH_TOKEN"),
HomeZoneEntityId: "zone.home",
Port: "443",
Secure: true,
})
if err != nil {
slog.Error("Error connecting to Home Assistant", "error", err)
os.Exit(1)
}
service := app.GetService()
return &TrayApp{
app: app,
service: service,
stateChannel: make(chan string),
doorIdentifier: "binary_sensor.bedroom_door_opening",
}
}
var (
//go:embed "resources/*.ico"
icons embed.FS
)
func (ta *TrayApp) HandleState(newState string) {
switch newState {
case "on":
ta.stateChannel <- "open"
case "off":
ta.stateChannel <- "closed"
default:
slog.Error("unknown state encountered", "newState", newState)
ta.stateChannel <- "unknown"
}
}
func (ta *TrayApp) setupHomeAssistant() {
var err error
// Get the initial state
state, err := ta.app.GetState().Get(ta.doorIdentifier)
if err != nil {
slog.Error("Unable to get initial state", "error", err)
} else {
slog.Debug("Initial State Received")
ta.HandleState(state.State)
}
ta.app.RegisterEntityListeners(ga.
NewEntityListener().
EntityIds(ta.doorIdentifier).
Call(func(service *ga.Service, state ga.State, sensor ga.EntityData) {
slog.Debug("Event Received", "identifier", ta.doorIdentifier, "sensor", sensor)
ta.HandleState(sensor.ToState)
}).
Build())
ta.app.Start()
slog.Warn("Home Assistant thread died")
ta.stateChannel <- "unknown"
}
func (ta *TrayApp) Start() {
dotenv.Load()
slog.SetDefault(slog.New(slog.NewJSONHandler(
os.Stdout,
&slog.HandlerOptions{
Level: slog.LevelDebug,
},
)))
// binfo, err := buildinfo.
slog.Info("Startup", "runtime", runtime.Version(), "os", runtime.GOOS, "arch", runtime.GOARCH, "pid", os.Getpid())
go ta.setupHomeAssistant()
systray.Run(ta.onReady, func() {})
}
func (ta *TrayApp) onReady() {
systray.SetTitle("door-tray")
systray.SetTooltip("Setting up...")
menuQuit := systray.AddMenuItem("Quit", "Stops the application")
menuOpenLogs := systray.AddMenuItem("Open Logs", "Opens the logs in the default editor")
menuOpenLogs.Disable()
// Load icons
systray.SetIcon(getIcon("unknown"))
// Handle Ctrl+C interrupt
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, os.Interrupt)
signal.Notify(interruptChannel, os.Kill)
loop:
for {
select {
case signal := <-interruptChannel:
slog.Info("Received interrupt signal, quitting", "signal", signal)
break loop
case <-menuQuit.ClickedCh:
slog.Info("Quit clicked")
break loop
case <-menuOpenLogs.ClickedCh:
slog.Info("Open Logs clicked")
case newState := <-ta.stateChannel:
timeString := time.Now().Format("3:04 PM")
if newState != "unknown" {
systray.SetTooltip(fmt.Sprintf("%s as of %s", cases.Title(language.AmericanEnglish, cases.NoLower).String(newState), timeString))
switch newState {
case "open":
systray.SetIcon(getIcon("open_fault"))
case "closed":
systray.SetIcon(getIcon("closed"))
}
} else {
slog.Warn("Unknown state", "state", newState)
systray.SetTooltip(fmt.Sprintf("Unknown as of %s", timeString))
systray.SetIcon(getIcon("unknown"))
}
}
}
slog.Info("Cleaning up")
systray.Quit()
ta.app.Cleanup()
}