fix bug when registering multiple listeners and change sunrise/sunset to non-ha impl

This commit is contained in:
Sam Lewis
2023-01-12 22:33:38 -05:00
parent a868c08d40
commit e58a75388e
10 changed files with 112 additions and 62 deletions
+65 -34
View File
@@ -4,11 +4,11 @@ import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/golang-module/carbon"
"github.com/gorilla/websocket"
sunriseLib "github.com/nathan-osman/go-sunrise"
"saml.dev/gome-assistant/internal"
"saml.dev/gome-assistant/internal/http"
pq "saml.dev/gome-assistant/internal/priorityqueue"
@@ -26,9 +26,9 @@ type App struct {
schedules pq.PriorityQueue
intervals pq.PriorityQueue
entityListeners map[string][]*EntityListener
entityListeners map[string][]EntityListener
entityListenersId int64
eventListeners map[string][]*EventListener
eventListeners map[string][]EventListener
}
/*
@@ -47,18 +47,46 @@ type timeRange struct {
end time.Time
}
type NewAppRequest struct {
// Required
// IpAddress of your Home Assistant instance i.e. "localhost"
// or "192.168.86.59" etc.
IpAddress string
// Optional
// Port number Home Assistant is running on. Defaults to 8123.
Port string
// Required
// Auth token generated in Home Assistant. Used
// to connect to the Websocket API.
HAAuthToken string
// Required
// EntityId of the zone representing your home e.g. "zone.home".
// Used to pull latitude/longitude from Home Assistant
// to calculate sunset/sunrise times.
HomeZoneEntityId string
}
/*
NewApp establishes the websocket connection and returns an object
you can use to register schedules and listeners.
*/
func NewApp(connString string) *App {
token := os.Getenv("HA_AUTH_TOKEN")
conn, ctx, ctxCancel := ws.SetupConnection(connString, token)
func NewApp(request NewAppRequest) *App {
if request.IpAddress == "" || request.HAAuthToken == "" || request.HomeZoneEntityId == "" {
log.Fatalln("IpAddress, HAAuthToken, and HomeZoneEntityId are all required arguments in NewAppRequest.")
}
port := request.Port
if port == "" {
port = "8123"
}
conn, ctx, ctxCancel := ws.SetupConnection(request.IpAddress, port, request.HAAuthToken)
httpClient := http.NewHttpClient(connString, token)
httpClient := http.NewHttpClient(request.IpAddress, port, request.HAAuthToken)
service := newService(conn, ctx, httpClient)
state := newState(httpClient)
state := newState(httpClient, request.HomeZoneEntityId)
return &App{
conn: conn,
@@ -69,8 +97,8 @@ func NewApp(connString string) *App {
state: state,
schedules: pq.New(),
intervals: pq.New(),
entityListeners: map[string][]*EntityListener{},
eventListeners: map[string][]*EventListener{},
entityListeners: map[string][]EntityListener{},
eventListeners: map[string][]EventListener{},
}
}
@@ -84,7 +112,7 @@ func (a *App) RegisterSchedules(schedules ...DailySchedule) {
for _, s := range schedules {
// realStartTime already set for sunset/sunrise
if s.isSunrise || s.isSunset {
s.nextRunTime = getSunriseSunsetFromApp(a, s.isSunrise, s.sunOffset).Carbon2Time()
s.nextRunTime = getNextSunRiseOrSet(a, s.isSunrise, s.sunOffset).Carbon2Time()
a.schedules.Insert(s, float64(s.nextRunTime.Unix()))
continue
}
@@ -105,7 +133,7 @@ func (a *App) RegisterSchedules(schedules ...DailySchedule) {
func (a *App) RegisterIntervals(intervals ...Interval) {
for _, i := range intervals {
if i.frequency == 0 {
panic("A schedule must use either set frequency via Every().")
log.Fatalf("A schedule must use either set frequency via Every().\n")
}
i.nextRunTime = internal.ParseTime(string(i.startTime)).Carbon2Time()
@@ -113,7 +141,6 @@ func (a *App) RegisterIntervals(intervals ...Interval) {
for i.nextRunTime.Before(now) {
i.nextRunTime = i.nextRunTime.Add(i.frequency)
}
fmt.Println(i)
a.intervals.Insert(i, float64(i.nextRunTime.Unix()))
}
}
@@ -121,14 +148,14 @@ func (a *App) RegisterIntervals(intervals ...Interval) {
func (a *App) RegisterEntityListeners(etls ...EntityListener) {
for _, etl := range etls {
if etl.delay != 0 && etl.toState == "" {
panic("EntityListener error: you have to use ToState() when using Duration()")
log.Fatalln("EntityListener error: you have to use ToState() when using Duration()")
}
for _, entity := range etl.entityIds {
if elList, ok := a.entityListeners[entity]; ok {
a.entityListeners[entity] = append(elList, &etl)
a.entityListeners[entity] = append(elList, etl)
} else {
a.entityListeners[entity] = []*EntityListener{&etl}
a.entityListeners[entity] = []EntityListener{etl}
}
}
}
@@ -138,50 +165,54 @@ func (a *App) RegisterEventListeners(evls ...EventListener) {
for _, evl := range evls {
for _, eventType := range evl.eventTypes {
if elList, ok := a.eventListeners[eventType]; ok {
a.eventListeners[eventType] = append(elList, &evl)
a.eventListeners[eventType] = append(elList, evl)
} else {
ws.SubscribeToEventType(eventType, a.conn, a.ctx)
a.eventListeners[eventType] = []*EventListener{&evl}
a.eventListeners[eventType] = []EventListener{evl}
}
}
}
}
func getSunriseSunsetFromState(s *State, sunrise bool, offset ...DurationString) carbon.Carbon {
func getSunriseSunset(s *State, sunrise bool, dateToUse carbon.Carbon, offset ...DurationString) carbon.Carbon {
date := dateToUse.Carbon2Time()
rise, set := sunriseLib.SunriseSunset(s.latitude, s.longitude, date.Year(), date.Month(), date.Day())
rise, set = rise.Local(), set.Local()
val := set
printString := "Sunset"
attrKey := "next_setting"
if sunrise {
val = rise
printString = "Sunrise"
attrKey = "next_rising"
}
setOrRiseToday := carbon.Parse(val.String())
var t time.Duration
var err error
if len(offset) == 1 {
t, err = time.ParseDuration(string(offset[0]))
if err != nil {
panic(fmt.Sprintf("Could not parse offset passed to %s: \"%s\"", printString, offset[0]))
log.Fatalf(fmt.Sprintf("Could not parse offset passed to %s: \"%s\"\n", printString, offset[0]))
}
}
// get next sunrise/sunset time from HA
state, err := s.Get("sun.sun")
if err != nil {
panic(fmt.Sprintf("Couldn't get sun.sun state from HA to calculate %s", printString))
}
nextSetOrRise := carbon.Parse(state.Attributes[attrKey].(string))
// add offset if set, this code works for negative values too
if t.Microseconds() != 0 {
nextSetOrRise = nextSetOrRise.AddMinutes(int(t.Minutes()))
setOrRiseToday = setOrRiseToday.AddMinutes(int(t.Minutes()))
}
return nextSetOrRise
return setOrRiseToday
}
func getSunriseSunsetFromApp(a *App, sunrise bool, offset ...DurationString) carbon.Carbon {
return getSunriseSunsetFromState(a.state, sunrise, offset...)
func getNextSunRiseOrSet(a *App, sunrise bool, offset ...DurationString) carbon.Carbon {
sunriseOrSunset := getSunriseSunset(a.state, sunrise, carbon.Now(), offset...)
if sunriseOrSunset.Lt(carbon.Now()) {
// if we're past today's sunset or sunrise (accounting for offset) then get tomorrows
// as that's the next time the schedule will run
sunriseOrSunset = getSunriseSunset(a.state, sunrise, carbon.Tomorrow(), offset...)
}
return sunriseOrSunset
}
func (a *App) Start() {