refactor: move types out of app.go into types/, renamed module files

This commit is contained in:
2025-08-01 18:08:01 -05:00
parent 3d178ad05e
commit 21358b73e1
13 changed files with 105 additions and 88 deletions

57
app.go
View File

@@ -17,6 +17,7 @@ import (
internal "github.com/Xevion/go-ha/internal" internal "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/internal/parse"
ws "github.com/Xevion/go-ha/internal/websocket" ws "github.com/Xevion/go-ha/internal/websocket"
"github.com/Xevion/go-ha/types"
) )
var ErrInvalidArgs = errors.New("invalid arguments provided") var ErrInvalidArgs = errors.New("invalid arguments provided")
@@ -41,10 +42,7 @@ type App struct {
eventListeners map[string][]*EventListener eventListeners map[string][]*EventListener
} }
type Item struct { type Item types.Item
Value interface{}
Priority float64
}
func (mi Item) Compare(other queue.Item) int { func (mi Item) Compare(other queue.Item) int {
if mi.Priority > other.(Item).Priority { if mi.Priority > other.(Item).Priority {
@@ -55,51 +53,6 @@ func (mi Item) Compare(other queue.Item) int {
return -1 return -1
} }
// DurationString represents a duration, such as "2s" or "24h".
// See https://pkg.go.dev/time#ParseDuration for all valid time units.
type DurationString string
// TimeString is a 24-hr format time "HH:MM" such as "07:30".
type TimeString string
type timeRange struct {
start time.Time
end time.Time
}
type NewAppRequest struct {
// Required
URL string
// Optional
// Deprecated: use URL instead
// IpAddress of your Home Assistant instance i.e. "localhost"
// or "192.168.86.59" etc.
IpAddress string
// Optional
// Deprecated: use URL instead
// 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
// Optional
// Whether to use secure connections for http and websockets.
// Setting this to `true` will use `https://` instead of `https://`
// and `wss://` instead of `ws://`.
Secure bool
}
// validateHomeZone verifies that the home zone entity exists and has latitude/longitude // validateHomeZone verifies that the home zone entity exists and has latitude/longitude
func validateHomeZone(state State, entityID string) error { func validateHomeZone(state State, entityID string) error {
entity, err := state.Get(entityID) entity, err := state.Get(entityID)
@@ -130,7 +83,7 @@ func validateHomeZone(state State, entityID string) error {
NewApp establishes the websocket connection and returns an object NewApp establishes the websocket connection and returns an object
you can use to register schedules and listeners. you can use to register schedules and listeners.
*/ */
func NewApp(request NewAppRequest) (*App, error) { func NewApp(request types.NewAppRequest) (*App, error) {
if (request.URL == "" && request.IpAddress == "") || request.HAAuthToken == "" { if (request.URL == "" && request.IpAddress == "") || request.HAAuthToken == "" {
slog.Error("URL and HAAuthToken are required arguments in NewAppRequest") slog.Error("URL and HAAuthToken are required arguments in NewAppRequest")
return nil, ErrInvalidArgs return nil, ErrInvalidArgs
@@ -306,7 +259,7 @@ func (a *App) RegisterEventListeners(evls ...EventListener) {
} }
} }
func getSunriseSunset(s *StateImpl, sunrise bool, dateToUse carbon.Carbon, offset ...DurationString) carbon.Carbon { func getSunriseSunset(s *StateImpl, sunrise bool, dateToUse carbon.Carbon, offset ...types.DurationString) carbon.Carbon {
date := dateToUse.Carbon2Time() date := dateToUse.Carbon2Time()
rise, set := sunriseLib.SunriseSunset(s.latitude, s.longitude, date.Year(), date.Month(), date.Day()) rise, set := sunriseLib.SunriseSunset(s.latitude, s.longitude, date.Year(), date.Month(), date.Day())
rise, set = rise.Local(), set.Local() rise, set = rise.Local(), set.Local()
@@ -339,7 +292,7 @@ func getSunriseSunset(s *StateImpl, sunrise bool, dateToUse carbon.Carbon, offse
return setOrRiseToday return setOrRiseToday
} }
func getNextSunRiseOrSet(a *App, sunrise bool, offset ...DurationString) carbon.Carbon { func getNextSunRiseOrSet(a *App, sunrise bool, offset ...types.DurationString) carbon.Carbon {
sunriseOrSunset := getSunriseSunset(a.state, sunrise, carbon.Now(), offset...) sunriseOrSunset := getSunriseSunset(a.state, sunrise, carbon.Now(), offset...)
if sunriseOrSunset.Lt(carbon.Now()) { if sunriseOrSunset.Lt(carbon.Now()) {
// if we're past today's sunset or sunrise (accounting for offset) then get tomorrows // if we're past today's sunset or sunrise (accounting for offset) then get tomorrows

View File

@@ -5,6 +5,7 @@ import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/internal/parse"
"github.com/Xevion/go-ha/types"
"github.com/golang-module/carbon" "github.com/golang-module/carbon"
) )
@@ -75,11 +76,11 @@ func checkExceptionDates(eList []time.Time) conditionCheck {
return cc return cc
} }
func checkExceptionRanges(eList []timeRange) conditionCheck { func checkExceptionRanges(eList []types.TimeRange) conditionCheck {
cc := conditionCheck{fail: false} cc := conditionCheck{fail: false}
now := time.Now() now := time.Now()
for _, eRange := range eList { for _, eRange := range eList {
if now.After(eRange.start) && now.Before(eRange.end) { if now.After(eRange.Start) && now.Before(eRange.End) {
cc.fail = true cc.fail = true
break break
} }
@@ -161,7 +162,7 @@ func checkAllowlistDates(eList []time.Time) conditionCheck {
return cc return cc
} }
func checkStartEndTime(s TimeString, isStart bool) conditionCheck { func checkStartEndTime(s types.TimeString, isStart bool) conditionCheck {
cc := conditionCheck{fail: false} cc := conditionCheck{fail: false}
// pass immediately if default // pass immediately if default
if s == "00:00" { if s == "00:00" {

View File

@@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -15,16 +16,16 @@ type MockState struct {
GetError bool GetError bool
} }
func (s MockState) AfterSunrise(_ ...DurationString) bool { func (s MockState) AfterSunrise(_ ...types.DurationString) bool {
return true return true
} }
func (s MockState) BeforeSunrise(_ ...DurationString) bool { func (s MockState) BeforeSunrise(_ ...types.DurationString) bool {
return true return true
} }
func (s MockState) AfterSunset(_ ...DurationString) bool { func (s MockState) AfterSunset(_ ...types.DurationString) bool {
return true return true
} }
func (s MockState) BeforeSunset(_ ...DurationString) bool { func (s MockState) BeforeSunset(_ ...types.DurationString) bool {
return true return true
} }
func (s MockState) Get(eid string) (EntityState, error) { func (s MockState) Get(eid string) (EntityState, error) {

View File

@@ -10,6 +10,7 @@ import (
"text/template" "text/template"
ga "github.com/Xevion/go-ha" ga "github.com/Xevion/go-ha"
"github.com/Xevion/go-ha/types"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -98,7 +99,7 @@ func generate(config Config) error {
config.HomeZoneEntityId = "zone.home" config.HomeZoneEntityId = "zone.home"
} }
app, err := ga.NewApp(ga.NewAppRequest{ app, err := ga.NewApp(types.NewAppRequest{
URL: config.URL, URL: config.URL,
HAAuthToken: config.HAAuthToken, HAAuthToken: config.HAAuthToken,
HomeZoneEntityId: config.HomeZoneEntityId, HomeZoneEntityId: config.HomeZoneEntityId,

View File

@@ -9,6 +9,7 @@ import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/internal/parse"
"github.com/Xevion/go-ha/types"
) )
type EntityListener struct { type EntityListener struct {
@@ -26,7 +27,7 @@ type EntityListener struct {
delayTimer *time.Timer delayTimer *time.Timer
exceptionDates []time.Time exceptionDates []time.Time
exceptionRanges []timeRange exceptionRanges []types.TimeRange
runOnStartup bool runOnStartup bool
runOnStartupCompleted bool runOnStartupCompleted bool
@@ -127,13 +128,13 @@ func (b elBuilder3) ToState(s string) elBuilder3 {
return b return b
} }
func (b elBuilder3) Duration(s DurationString) elBuilder3 { func (b elBuilder3) Duration(s types.DurationString) elBuilder3 {
d := parse.ParseDuration(string(s)) d := parse.ParseDuration(string(s))
b.entityListener.delay = d b.entityListener.delay = d
return b return b
} }
func (b elBuilder3) Throttle(s DurationString) elBuilder3 { func (b elBuilder3) Throttle(s types.DurationString) elBuilder3 {
d := parse.ParseDuration(string(s)) d := parse.ParseDuration(string(s))
b.entityListener.throttle = d b.entityListener.throttle = d
return b return b
@@ -145,7 +146,7 @@ func (b elBuilder3) ExceptionDates(t time.Time, tl ...time.Time) elBuilder3 {
} }
func (b elBuilder3) ExceptionRange(start, end time.Time) elBuilder3 { func (b elBuilder3) ExceptionRange(start, end time.Time) elBuilder3 {
b.entityListener.exceptionRanges = append(b.entityListener.exceptionRanges, timeRange{start, end}) b.entityListener.exceptionRanges = append(b.entityListener.exceptionRanges, types.TimeRange{start, end})
return b return b
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/internal/parse"
ws "github.com/Xevion/go-ha/internal/websocket" ws "github.com/Xevion/go-ha/internal/websocket"
"github.com/Xevion/go-ha/types"
) )
type EventListener struct { type EventListener struct {
@@ -21,7 +22,7 @@ type EventListener struct {
lastRan carbon.Carbon lastRan carbon.Carbon
exceptionDates []time.Time exceptionDates []time.Time
exceptionRanges []timeRange exceptionRanges []types.TimeRange
enabledEntities []internal.EnabledDisabledInfo enabledEntities []internal.EnabledDisabledInfo
disabledEntities []internal.EnabledDisabledInfo disabledEntities []internal.EnabledDisabledInfo
@@ -80,7 +81,7 @@ func (b eventListenerBuilder3) OnlyBefore(end string) eventListenerBuilder3 {
return b return b
} }
func (b eventListenerBuilder3) Throttle(s DurationString) eventListenerBuilder3 { func (b eventListenerBuilder3) Throttle(s types.DurationString) eventListenerBuilder3 {
d := parse.ParseDuration(string(s)) d := parse.ParseDuration(string(s))
b.eventListener.throttle = d b.eventListener.throttle = d
return b return b
@@ -92,7 +93,7 @@ func (b eventListenerBuilder3) ExceptionDates(t time.Time, tl ...time.Time) even
} }
func (b eventListenerBuilder3) ExceptionRange(start, end time.Time) eventListenerBuilder3 { func (b eventListenerBuilder3) ExceptionRange(start, end time.Time) eventListenerBuilder3 {
b.eventListener.exceptionRanges = append(b.eventListener.exceptionRanges, timeRange{start, end}) b.eventListener.exceptionRanges = append(b.eventListener.exceptionRanges, types.TimeRange{start, end})
return b return b
} }

View File

@@ -14,9 +14,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/gorilla/websocket"
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/gorilla/websocket"
) )
var ErrInvalidToken = errors.New("invalid authentication token") var ErrInvalidToken = errors.New("invalid authentication token")

View File

@@ -7,6 +7,7 @@ import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/internal/parse"
"github.com/Xevion/go-ha/types"
) )
type IntervalCallback func(*Service, State) type IntervalCallback func(*Service, State)
@@ -14,12 +15,12 @@ type IntervalCallback func(*Service, State)
type Interval struct { type Interval struct {
frequency time.Duration frequency time.Duration
callback IntervalCallback callback IntervalCallback
startTime TimeString startTime types.TimeString
endTime TimeString endTime types.TimeString
nextRunTime time.Time nextRunTime time.Time
exceptionDates []time.Time exceptionDates []time.Time
exceptionRanges []timeRange exceptionRanges []types.TimeRange
enabledEntities []internal.EnabledDisabledInfo enabledEntities []internal.EnabledDisabledInfo
disabledEntities []internal.EnabledDisabledInfo disabledEntities []internal.EnabledDisabledInfo
@@ -63,7 +64,7 @@ func (i Interval) String() string {
) )
} }
func formatStartOrEndString(s TimeString, isStart bool) string { func formatStartOrEndString(s types.TimeString, isStart bool) string {
if s == "00:00" { if s == "00:00" {
return "" return ""
} }
@@ -80,20 +81,20 @@ func (ib intervalBuilder) Call(callback IntervalCallback) intervalBuilderCall {
} }
// Takes a DurationString ("2h", "5m", etc) to set the frequency of the interval. // Takes a DurationString ("2h", "5m", etc) to set the frequency of the interval.
func (ib intervalBuilderCall) Every(s DurationString) intervalBuilderEnd { func (ib intervalBuilderCall) Every(s types.DurationString) intervalBuilderEnd {
d := parse.ParseDuration(string(s)) d := parse.ParseDuration(string(s))
ib.interval.frequency = d ib.interval.frequency = d
return intervalBuilderEnd(ib) return intervalBuilderEnd(ib)
} }
// Takes a TimeString ("HH:MM") when this interval will start running for the day. // Takes a TimeString ("HH:MM") when this interval will start running for the day.
func (ib intervalBuilderEnd) StartingAt(s TimeString) intervalBuilderEnd { func (ib intervalBuilderEnd) StartingAt(s types.TimeString) intervalBuilderEnd {
ib.interval.startTime = s ib.interval.startTime = s
return ib return ib
} }
// Takes a TimeString ("HH:MM") when this interval will stop running for the day. // Takes a TimeString ("HH:MM") when this interval will stop running for the day.
func (ib intervalBuilderEnd) EndingAt(s TimeString) intervalBuilderEnd { func (ib intervalBuilderEnd) EndingAt(s types.TimeString) intervalBuilderEnd {
ib.interval.endTime = s ib.interval.endTime = s
return ib return ib
} }
@@ -104,7 +105,7 @@ func (ib intervalBuilderEnd) ExceptionDates(t time.Time, tl ...time.Time) interv
} }
func (ib intervalBuilderEnd) ExceptionRange(start, end time.Time) intervalBuilderEnd { func (ib intervalBuilderEnd) ExceptionRange(start, end time.Time) intervalBuilderEnd {
ib.interval.exceptionRanges = append(ib.interval.exceptionRanges, timeRange{start, end}) ib.interval.exceptionRanges = append(ib.interval.exceptionRanges, types.TimeRange{start, end})
return ib return ib
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/internal/parse"
"github.com/Xevion/go-ha/types"
"github.com/golang-module/carbon" "github.com/golang-module/carbon"
) )
@@ -23,7 +24,7 @@ type DailySchedule struct {
isSunrise bool isSunrise bool
isSunset bool isSunset bool
sunOffset DurationString sunOffset types.DurationString
exceptionDates []time.Time exceptionDates []time.Time
allowlistDates []time.Time allowlistDates []time.Time
@@ -85,7 +86,7 @@ func (sb scheduleBuilderCall) At(s string) scheduleBuilderEnd {
// Sunrise takes an optional duration string that is passed to time.ParseDuration. // Sunrise takes an optional duration string that is passed to time.ParseDuration.
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration // Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
// for full list. // for full list.
func (sb scheduleBuilderCall) Sunrise(offset ...DurationString) scheduleBuilderEnd { func (sb scheduleBuilderCall) Sunrise(offset ...types.DurationString) scheduleBuilderEnd {
sb.schedule.isSunrise = true sb.schedule.isSunrise = true
if len(offset) > 0 { if len(offset) > 0 {
sb.schedule.sunOffset = offset[0] sb.schedule.sunOffset = offset[0]
@@ -96,7 +97,7 @@ func (sb scheduleBuilderCall) Sunrise(offset ...DurationString) scheduleBuilderE
// Sunset takes an optional duration string that is passed to time.ParseDuration. // Sunset takes an optional duration string that is passed to time.ParseDuration.
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration // Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
// for full list. // for full list.
func (sb scheduleBuilderCall) Sunset(offset ...DurationString) scheduleBuilderEnd { func (sb scheduleBuilderCall) Sunset(offset ...types.DurationString) scheduleBuilderEnd {
sb.schedule.isSunset = true sb.schedule.isSunset = true
if len(offset) > 0 { if len(offset) > 0 {
sb.schedule.sunOffset = offset[0] sb.schedule.sunOffset = offset[0]

View File

@@ -9,13 +9,14 @@ import (
"github.com/golang-module/carbon" "github.com/golang-module/carbon"
internal "github.com/Xevion/go-ha/internal" internal "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/types"
) )
type State interface { type State interface {
AfterSunrise(...DurationString) bool AfterSunrise(...types.DurationString) bool
BeforeSunrise(...DurationString) bool BeforeSunrise(...types.DurationString) bool
AfterSunset(...DurationString) bool AfterSunset(...types.DurationString) bool
BeforeSunset(...DurationString) bool BeforeSunset(...types.DurationString) bool
ListEntities() ([]EntityState, error) ListEntities() ([]EntityState, error)
Get(entityId string) (EntityState, error) Get(entityId string) (EntityState, error)
Equals(entityId, state string) (bool, error) Equals(entityId, state string) (bool, error)
@@ -99,20 +100,20 @@ func (s *StateImpl) Equals(entityId string, expectedState string) (bool, error)
return currentState.State == expectedState, nil return currentState.State == expectedState, nil
} }
func (s *StateImpl) BeforeSunrise(offset ...DurationString) bool { func (s *StateImpl) BeforeSunrise(offset ...types.DurationString) bool {
sunrise := getSunriseSunset(s /* sunrise = */, true, carbon.Now(), offset...) sunrise := getSunriseSunset(s /* sunrise = */, true, carbon.Now(), offset...)
return carbon.Now().Lt(sunrise) return carbon.Now().Lt(sunrise)
} }
func (s *StateImpl) AfterSunrise(offset ...DurationString) bool { func (s *StateImpl) AfterSunrise(offset ...types.DurationString) bool {
return !s.BeforeSunrise(offset...) return !s.BeforeSunrise(offset...)
} }
func (s *StateImpl) BeforeSunset(offset ...DurationString) bool { func (s *StateImpl) BeforeSunset(offset ...types.DurationString) bool {
sunset := getSunriseSunset(s /* sunrise = */, false, carbon.Now(), offset...) sunset := getSunriseSunset(s /* sunrise = */, false, carbon.Now(), offset...)
return carbon.Now().Lt(sunset) return carbon.Now().Lt(sunset)
} }
func (s *StateImpl) AfterSunset(offset ...DurationString) bool { func (s *StateImpl) AfterSunset(offset ...types.DurationString) bool {
return !s.BeforeSunset(offset...) return !s.BeforeSunset(offset...)
} }

35
types/app.go Normal file
View File

@@ -0,0 +1,35 @@
package types
// NewAppRequest contains the configuration for creating a new App instance.
type NewAppRequest struct {
// Required
URL string
// Optional
// Deprecated: use URL instead
// IpAddress of your Home Assistant instance i.e. "localhost"
// or "192.168.86.59" etc.
IpAddress string
// Optional
// Deprecated: use URL instead
// 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
// Optional
// Whether to use secure connections for http and websockets.
// Setting this to `true` will use `https://` instead of `https://`
// and `wss://` instead of `ws://`.
Secure bool
}

22
types/common.go Normal file
View File

@@ -0,0 +1,22 @@
package types
import "time"
// DurationString represents a duration, such as "2s" or "24h".
// See https://pkg.go.dev/time#ParseDuration for all valid time units.
type DurationString string
// TimeString is a 24-hr format time "HH:MM" such as "07:30".
type TimeString string
// TimeRange represents a time range with start and end times.
type TimeRange struct {
Start time.Time
End time.Time
}
// Item represents a priority queue item with a value and priority.
type Item struct {
Value interface{}
Priority float64
}

View File