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"
"github.com/Xevion/go-ha/internal/parse"
ws "github.com/Xevion/go-ha/internal/websocket"
"github.com/Xevion/go-ha/types"
)
var ErrInvalidArgs = errors.New("invalid arguments provided")
@@ -41,10 +42,7 @@ type App struct {
eventListeners map[string][]*EventListener
}
type Item struct {
Value interface{}
Priority float64
}
type Item types.Item
func (mi Item) Compare(other queue.Item) int {
if mi.Priority > other.(Item).Priority {
@@ -55,51 +53,6 @@ func (mi Item) Compare(other queue.Item) int {
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
func validateHomeZone(state State, entityID string) error {
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
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 == "" {
slog.Error("URL and HAAuthToken are required arguments in NewAppRequest")
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()
rise, set := sunriseLib.SunriseSunset(s.latitude, s.longitude, date.Year(), date.Month(), date.Day())
rise, set = rise.Local(), set.Local()
@@ -339,7 +292,7 @@ func getSunriseSunset(s *StateImpl, sunrise bool, dateToUse carbon.Carbon, offse
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...)
if sunriseOrSunset.Lt(carbon.Now()) {
// 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/parse"
"github.com/Xevion/go-ha/types"
"github.com/golang-module/carbon"
)
@@ -75,11 +76,11 @@ func checkExceptionDates(eList []time.Time) conditionCheck {
return cc
}
func checkExceptionRanges(eList []timeRange) conditionCheck {
func checkExceptionRanges(eList []types.TimeRange) conditionCheck {
cc := conditionCheck{fail: false}
now := time.Now()
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
break
}
@@ -161,7 +162,7 @@ func checkAllowlistDates(eList []time.Time) conditionCheck {
return cc
}
func checkStartEndTime(s TimeString, isStart bool) conditionCheck {
func checkStartEndTime(s types.TimeString, isStart bool) conditionCheck {
cc := conditionCheck{fail: false}
// pass immediately if default
if s == "00:00" {

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,9 +14,8 @@ import (
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/Xevion/go-ha/internal"
"github.com/gorilla/websocket"
)
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/parse"
"github.com/Xevion/go-ha/types"
)
type IntervalCallback func(*Service, State)
@@ -14,12 +15,12 @@ type IntervalCallback func(*Service, State)
type Interval struct {
frequency time.Duration
callback IntervalCallback
startTime TimeString
endTime TimeString
startTime types.TimeString
endTime types.TimeString
nextRunTime time.Time
exceptionDates []time.Time
exceptionRanges []timeRange
exceptionRanges []types.TimeRange
enabledEntities []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" {
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.
func (ib intervalBuilderCall) Every(s DurationString) intervalBuilderEnd {
func (ib intervalBuilderCall) Every(s types.DurationString) intervalBuilderEnd {
d := parse.ParseDuration(string(s))
ib.interval.frequency = d
return intervalBuilderEnd(ib)
}
// 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
return ib
}
// 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
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 {
ib.interval.exceptionRanges = append(ib.interval.exceptionRanges, timeRange{start, end})
ib.interval.exceptionRanges = append(ib.interval.exceptionRanges, types.TimeRange{start, end})
return ib
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse"
"github.com/Xevion/go-ha/types"
"github.com/golang-module/carbon"
)
@@ -23,7 +24,7 @@ type DailySchedule struct {
isSunrise bool
isSunset bool
sunOffset DurationString
sunOffset types.DurationString
exceptionDates []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.
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
// for full list.
func (sb scheduleBuilderCall) Sunrise(offset ...DurationString) scheduleBuilderEnd {
func (sb scheduleBuilderCall) Sunrise(offset ...types.DurationString) scheduleBuilderEnd {
sb.schedule.isSunrise = true
if len(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.
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
// for full list.
func (sb scheduleBuilderCall) Sunset(offset ...DurationString) scheduleBuilderEnd {
func (sb scheduleBuilderCall) Sunset(offset ...types.DurationString) scheduleBuilderEnd {
sb.schedule.isSunset = true
if len(offset) > 0 {
sb.schedule.sunOffset = offset[0]

View File

@@ -9,13 +9,14 @@ import (
"github.com/golang-module/carbon"
internal "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/types"
)
type State interface {
AfterSunrise(...DurationString) bool
BeforeSunrise(...DurationString) bool
AfterSunset(...DurationString) bool
BeforeSunset(...DurationString) bool
AfterSunrise(...types.DurationString) bool
BeforeSunrise(...types.DurationString) bool
AfterSunset(...types.DurationString) bool
BeforeSunset(...types.DurationString) bool
ListEntities() ([]EntityState, error)
Get(entityId string) (EntityState, 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
}
func (s *StateImpl) BeforeSunrise(offset ...DurationString) bool {
func (s *StateImpl) BeforeSunrise(offset ...types.DurationString) bool {
sunrise := getSunriseSunset(s /* sunrise = */, true, carbon.Now(), offset...)
return carbon.Now().Lt(sunrise)
}
func (s *StateImpl) AfterSunrise(offset ...DurationString) bool {
func (s *StateImpl) AfterSunrise(offset ...types.DurationString) bool {
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...)
return carbon.Now().Lt(sunset)
}
func (s *StateImpl) AfterSunset(offset ...DurationString) bool {
func (s *StateImpl) AfterSunset(offset ...types.DurationString) bool {
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