fass/main.go

253 lines
5.6 KiB
Go
Raw Normal View History

2024-08-19 19:43:17 -06:00
package main
import (
"cmp"
"crypto/tls"
"crypto/x509"
2024-08-19 19:43:17 -06:00
"fmt"
2024-08-23 20:42:03 -06:00
"io"
2024-08-19 19:43:17 -06:00
"log"
"net/http"
"slices"
"strings"
"time"
2024-08-19 19:43:17 -06:00
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
2024-08-23 20:42:03 -06:00
"fyne.io/fyne/v2/dialog"
2024-08-19 19:43:17 -06:00
"fyne.io/fyne/v2/driver/desktop"
2024-08-23 20:42:03 -06:00
"fyne.io/fyne/v2/storage"
2024-08-19 19:43:17 -06:00
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/pawal/go-hass"
)
func sortEntries(entries *hass.States) {
slices.SortFunc(*entries, func(a hass.State, b hass.State) int {
an := fmt.Sprintf("%s", a.Attributes["friendly_name"])
bn := fmt.Sprintf("%s", b.Attributes["friendly_name"])
if n := strings.Compare(an, bn); n != 0 {
return n
}
return cmp.Compare(a.EntityID, b.EntityID)
})
}
2024-08-23 20:42:03 -06:00
func loadData(h *hass.Access, lightCards *[]fyne.CanvasObject, switchCards *[]fyne.CanvasObject) {
lights, err := h.FilterStates("light")
if err != nil {
log.Fatalln(err)
}
switches, err := h.FilterStates("switch")
if err != nil {
log.Fatalln(err)
}
2024-08-19 19:43:17 -06:00
sortEntries(&lights)
sortEntries(&switches)
2024-08-23 20:42:03 -06:00
for entity := range lights {
e := lights[entity]
card := makeEntity(e, h)
if card != nil {
*lightCards = append(*lightCards, card)
}
2024-08-23 20:42:03 -06:00
}
for entity := range switches {
e := switches[entity]
card := makeEntity(e, h)
if card != nil {
*switchCards = append(*switchCards, card)
}
2024-08-23 20:42:03 -06:00
}
2024-08-19 19:43:17 -06:00
}
func getDevice(id string, h *hass.Access) (hass.Device, error) {
s, err := h.GetState(id)
if err != nil {
return nil, err
}
return h.GetDevice(s)
}
func makeEntity(e hass.State, h *hass.Access) *widget.Card {
if e.State != "on" && e.State != "off" {
return nil
}
2024-08-19 19:43:17 -06:00
fmt.Printf("%s: (%s) %s\n", e.EntityID,
e.Attributes["friendly_name"],
e.State)
entityName := fmt.Sprintf("%s", e.Attributes["friendly_name"])
entityButton := NewToggle()
if e.State == "on" {
entityButton.On = true
}
entityButton.OnChanged(func(on bool) {
dev, err := getDevice(e.EntityID, h)
if err != nil {
log.Println(err)
return
}
dev.Toggle()
})
card := widget.NewCard("", entityName, container.NewVBox(
2024-08-19 19:43:17 -06:00
container.NewHBox(entityButton),
))
card.Refresh()
return card
2024-08-19 19:43:17 -06:00
}
func loadSavedData(a fyne.App, w fyne.Window, input *widget.Entry, file string) {
2024-08-23 20:42:03 -06:00
uri, err := storage.Child(a.Storage().RootURI(), file)
2024-08-19 19:43:17 -06:00
if err != nil {
2024-08-23 20:42:03 -06:00
return
2024-08-19 19:43:17 -06:00
}
2024-08-23 20:42:03 -06:00
reader, err := storage.Reader(uri)
if err != nil {
return
2024-08-19 19:43:17 -06:00
}
2024-08-23 20:42:03 -06:00
defer reader.Close()
2024-08-19 19:43:17 -06:00
2024-08-23 20:42:03 -06:00
content, err := io.ReadAll(reader)
if err != nil {
dialog.ShowError(err, w)
2024-08-23 20:42:03 -06:00
return
}
2024-08-19 19:43:17 -06:00
2024-08-23 20:42:03 -06:00
input.SetText(string(content))
}
func saveData(a fyne.App, w fyne.Window, input *widget.Entry, file string) {
uri, err := storage.Child(a.Storage().RootURI(), file)
2024-08-19 19:43:17 -06:00
if err != nil {
2024-08-23 20:42:03 -06:00
dialog.ShowError(err, w)
return
2024-08-19 19:43:17 -06:00
}
2024-08-23 20:42:03 -06:00
writer, err := storage.Writer(uri)
2024-08-19 19:43:17 -06:00
if err != nil {
2024-08-23 20:42:03 -06:00
dialog.ShowError(err, w)
return
}
defer writer.Close()
_, err = writer.Write([]byte(input.Text))
if err != nil {
dialog.ShowError(err, w)
return
}
dialog.ShowInformation("Success", "", w)
}
func main() {
a := app.New()
w := a.NewWindow("fass")
if w == nil {
log.Fatalln("unable to create window")
2024-08-19 19:43:17 -06:00
}
var lightCards []fyne.CanvasObject
var switchCards []fyne.CanvasObject
2024-08-23 20:42:03 -06:00
haFile, _ := storage.Child(a.Storage().RootURI(), "haurl")
haExists, _ := storage.Exists(haFile)
tokenFile, _ := storage.Child(a.Storage().RootURI(), "hatoken")
tkExists, _ := storage.Exists(tokenFile)
certFile, _ := storage.Child(a.Storage().RootURI(), "haCAcert")
certExists, _ := storage.Exists(certFile)
2024-08-23 20:42:03 -06:00
urlEntry := widget.NewEntry()
passEntry := widget.NewPasswordEntry()
certEntry := widget.NewMultiLineEntry()
2024-08-23 20:42:03 -06:00
loadSavedData(a, w, urlEntry, "haurl")
loadSavedData(a, w, passEntry, "hatoken")
loadSavedData(a, w, certEntry, "haCAcert")
2024-08-23 20:42:03 -06:00
h := hass.NewAccess(urlEntry.Text, "")
if haExists && tkExists {
if certExists && certEntry.Text != "" {
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
if ok := rootCAs.AppendCertsFromPEM([]byte(certEntry.Text)); !ok {
dialog.ShowError(fmt.Errorf("No certs appended, using system certs only"), w)
}
client := &http.Client{
Timeout: time.Second * 10,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
},
},
}
h.SetClient(client)
}
2024-08-23 20:42:03 -06:00
h.SetBearerToken(passEntry.Text)
err := h.CheckAPI()
if err != nil {
dialog.ShowError(err, w)
} else {
loadData(h, &lightCards, &switchCards)
}
2024-08-19 19:43:17 -06:00
}
2024-08-23 20:42:03 -06:00
settingsForm := &widget.Form{
Items: []*widget.FormItem{
{Text: "Home Assistant URL:", Widget: urlEntry},
{Text: "Access Token:", Widget: passEntry},
{Text: "CA Certificate:", Widget: certEntry},
2024-08-23 20:42:03 -06:00
{Text: "", Widget: widget.NewButton("Save", func() {
saveData(a, w, urlEntry, "haurl")
saveData(a, w, passEntry, "hatoken")
saveData(a, w, certEntry, "haCAcert")
2024-08-23 20:42:03 -06:00
})},
},
2024-08-19 19:43:17 -06:00
}
2024-08-23 20:42:03 -06:00
ctrlQ := &desktop.CustomShortcut{KeyName: fyne.KeyQ, Modifier: fyne.KeyModifierControl}
ctrlW := &desktop.CustomShortcut{KeyName: fyne.KeyW, Modifier: fyne.KeyModifierControl}
w.Canvas().AddShortcut(ctrlQ, func(shortcut fyne.Shortcut) {
a.Quit()
})
w.Canvas().AddShortcut(ctrlW, func(shortcut fyne.Shortcut) {
w.Hide()
})
cols := 5
2024-08-19 19:43:17 -06:00
tabs := container.NewAppTabs(
container.NewTabItemWithIcon("Lights",
theme.VisibilityIcon(),
container.NewAdaptiveGrid(cols, lightCards...),
),
2024-08-19 19:43:17 -06:00
container.NewTabItemWithIcon("Switches",
theme.RadioButtonIcon(),
container.NewAdaptiveGrid(cols, switchCards...),
2024-08-19 19:43:17 -06:00
),
)
tabs.SetTabLocation(container.TabLocationLeading)
2024-08-23 20:42:03 -06:00
w.SetContent(
container.NewAppTabs(
container.NewTabItem("Toggles", tabs),
container.NewTabItem("Settings", container.NewStack(
settingsForm,
)),
),
)
2024-08-19 19:43:17 -06:00
w.SetCloseIntercept(func() {
w.Hide()
})
w.ShowAndRun()
}