9 Commits

50 changed files with 508 additions and 557 deletions

View File

@@ -85,7 +85,7 @@ The general flow is
3. Start app 3. Start app
```go ```go
import ga "github.com/Xevion/go-ha" import ha "github.com/Xevion/go-ha"
// replace with IP and port of your Home Assistant installation // replace with IP and port of your Home Assistant installation
app, err := ga.NewApp(ga.NewAppRequest{ app, err := ga.NewApp(ga.NewAppRequest{

184
app.go
View File

@@ -9,14 +9,14 @@ import (
"strings" "strings"
"time" "time"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
sunriseLib "github.com/nathan-osman/go-sunrise" sunriseLib "github.com/nathan-osman/go-sunrise"
"github.com/Workiva/go-datastructures/queue" "github.com/Workiva/go-datastructures/queue"
internal "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/connect"
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")
@@ -24,10 +24,9 @@ var ErrInvalidArgs = errors.New("invalid arguments provided")
type App struct { type App struct {
ctx context.Context ctx context.Context
ctxCancel context.CancelFunc ctxCancel context.CancelFunc
conn *websocket.Conn
// Wraps the ws connection with added mutex locking // Wraps the ws connection with added mutex locking
wsWriter *ws.WebsocketWriter conn *connect.HAConnection
httpClient *internal.HttpClient httpClient *internal.HttpClient
@@ -41,10 +40,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 +51,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)
@@ -115,11 +66,9 @@ func validateHomeZone(state State, entityID string) error {
// Verify it has latitude and longitude // Verify it has latitude and longitude
if entity.Attributes == nil { if entity.Attributes == nil {
return fmt.Errorf("home zone entity '%s' has no attributes", entityID) return fmt.Errorf("home zone entity '%s' has no attributes", entityID)
} } else if entity.Attributes["latitude"] == nil {
if entity.Attributes["latitude"] == nil {
return fmt.Errorf("home zone entity '%s' missing latitude attribute", entityID) return fmt.Errorf("home zone entity '%s' missing latitude attribute", entityID)
} } else if entity.Attributes["longitude"] == nil {
if entity.Attributes["longitude"] == nil {
return fmt.Errorf("home zone entity '%s' missing longitude attribute", entityID) return fmt.Errorf("home zone entity '%s' missing longitude attribute", entityID)
} }
@@ -130,7 +79,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
@@ -151,18 +100,14 @@ func NewApp(request NewAppRequest) (*App, error) {
} }
} }
conn, ctx, ctxCancel, err := ws.ConnectionFromUri(baseURL, request.HAAuthToken) conn, ctx, ctxCancel, err := connect.ConnectionFromUri(baseURL, request.HAAuthToken)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if conn == nil {
return nil, err
}
httpClient := internal.NewHttpClient(baseURL, request.HAAuthToken) httpClient := internal.NewHttpClient(ctx, baseURL, request.HAAuthToken)
wsWriter := &ws.WebsocketWriter{Conn: conn} service := newService(conn)
service := newService(wsWriter)
state, err := newState(httpClient, request.HomeZoneEntityId) state, err := newState(httpClient, request.HomeZoneEntityId)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -175,7 +120,6 @@ func NewApp(request NewAppRequest) (*App, error) {
return &App{ return &App{
conn: conn, conn: conn,
wsWriter: wsWriter,
ctx: ctx, ctx: ctx,
ctxCancel: ctxCancel, ctxCancel: ctxCancel,
httpClient: httpClient, httpClient: httpClient,
@@ -188,27 +132,27 @@ func NewApp(request NewAppRequest) (*App, error) {
}, nil }, nil
} }
func (a *App) Cleanup() { func (app *App) Cleanup() {
if a.ctxCancel != nil { if app.ctxCancel != nil {
a.ctxCancel() app.ctxCancel()
} }
} }
// Close performs a clean shutdown of the application. // Close performs a clean shutdown of the application.
// It cancels the context, closes the websocket connection, // It cancels the context, closes the websocket connection,
// and ensures all background processes are properly terminated. // and ensures all background processes are properly terminated.
func (a *App) Close() error { func (app *App) Close() error {
// Close websocket connection if it exists // Close websocket connection if it exists
if a.conn != nil { if app.conn != nil {
deadline := time.Now().Add(10 * time.Second) deadline := time.Now().Add(10 * time.Second)
err := a.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline) err := app.conn.Conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline)
if err != nil { if err != nil {
slog.Warn("Error writing close message", "error", err) slog.Warn("Error writing close message", "error", err)
return err return err
} }
// Close the websocket connection // Close the websocket connection
err = a.conn.Close() err = app.conn.Conn.Close()
if err != nil { if err != nil {
slog.Warn("Error closing websocket connection", "error", err) slog.Warn("Error closing websocket connection", "error", err)
return err return err
@@ -219,8 +163,8 @@ func (a *App) Close() error {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
// Cancel context to signal all goroutines to stop // Cancel context to signal all goroutines to stop
if a.ctxCancel != nil { if app.ctxCancel != nil {
a.ctxCancel() app.ctxCancel()
} }
// Wait a short time for goroutines to finish // Wait a short time for goroutines to finish
@@ -230,12 +174,12 @@ func (a *App) Close() error {
return nil return nil
} }
func (a *App) RegisterSchedules(schedules ...DailySchedule) { func (app *App) RegisterSchedules(schedules ...DailySchedule) {
for _, s := range schedules { for _, s := range schedules {
// realStartTime already set for sunset/sunrise // realStartTime already set for sunset/sunrise
if s.isSunrise || s.isSunset { if s.isSunrise || s.isSunset {
s.nextRunTime = getNextSunRiseOrSet(a, s.isSunrise, s.sunOffset).Carbon2Time() s.nextRunTime = getNextSunRiseOrSet(app, s.isSunrise, s.sunOffset).StdTime()
a.schedules.Put() app.schedules.Put()
continue continue
} }
@@ -247,34 +191,34 @@ func (a *App) RegisterSchedules(schedules ...DailySchedule) {
startTime = startTime.AddDay() startTime = startTime.AddDay()
} }
s.nextRunTime = startTime.Carbon2Time() s.nextRunTime = startTime.StdTime()
a.schedules.Put(Item{ app.schedules.Put(Item{
Value: s, Value: s,
Priority: float64(startTime.Carbon2Time().Unix()), Priority: float64(startTime.StdTime().Unix()),
}) })
} }
} }
func (a *App) RegisterIntervals(intervals ...Interval) { func (app *App) RegisterIntervals(intervals ...Interval) {
for _, i := range intervals { for _, i := range intervals {
if i.frequency == 0 { if i.frequency == 0 {
slog.Error("A schedule must use either set frequency via Every()") slog.Error("A schedule must use either set frequency via Every()")
panic(ErrInvalidArgs) panic(ErrInvalidArgs)
} }
i.nextRunTime = parse.ParseTime(string(i.startTime)).Carbon2Time() i.nextRunTime = internal.ParseTime(string(i.startTime)).StdTime()
now := time.Now() now := time.Now()
for i.nextRunTime.Before(now) { for i.nextRunTime.Before(now) {
i.nextRunTime = i.nextRunTime.Add(i.frequency) i.nextRunTime = i.nextRunTime.Add(i.frequency)
} }
a.intervals.Put(Item{ app.intervals.Put(Item{
Value: i, Value: i,
Priority: float64(i.nextRunTime.Unix()), Priority: float64(i.nextRunTime.Unix()),
}) })
} }
} }
func (a *App) RegisterEntityListeners(etls ...EntityListener) { func (app *App) RegisterEntityListeners(etls ...EntityListener) {
for _, etl := range etls { for _, etl := range etls {
etl := etl etl := etl
if etl.delay != 0 && etl.toState == "" { if etl.delay != 0 && etl.toState == "" {
@@ -283,31 +227,31 @@ func (a *App) RegisterEntityListeners(etls ...EntityListener) {
} }
for _, entity := range etl.entityIds { for _, entity := range etl.entityIds {
if elList, ok := a.entityListeners[entity]; ok { if elList, ok := app.entityListeners[entity]; ok {
a.entityListeners[entity] = append(elList, &etl) app.entityListeners[entity] = append(elList, &etl)
} else { } else {
a.entityListeners[entity] = []*EntityListener{&etl} app.entityListeners[entity] = []*EntityListener{&etl}
} }
} }
} }
} }
func (a *App) RegisterEventListeners(evls ...EventListener) { func (app *App) RegisterEventListeners(evls ...EventListener) {
for _, evl := range evls { for _, evl := range evls {
evl := evl evl := evl
for _, eventType := range evl.eventTypes { for _, eventType := range evl.eventTypes {
if elList, ok := a.eventListeners[eventType]; ok { if elList, ok := app.eventListeners[eventType]; ok {
a.eventListeners[eventType] = append(elList, &evl) app.eventListeners[eventType] = append(elList, &evl)
} else { } else {
ws.SubscribeToEventType(eventType, a.wsWriter, a.ctx) connect.SubscribeToEventType(eventType, app.conn, app.ctx)
a.eventListeners[eventType] = []*EventListener{&evl} app.eventListeners[eventType] = []*EventListener{&evl}
} }
} }
} }
} }
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.StdTime()
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 +283,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
@@ -349,32 +293,32 @@ func getNextSunRiseOrSet(a *App, sunrise bool, offset ...DurationString) carbon.
return sunriseOrSunset return sunriseOrSunset
} }
func (a *App) Start() { func (app *App) Start() {
slog.Info("Starting", "schedules", a.schedules.Len()) slog.Info("Starting", "schedules", app.schedules.Len())
slog.Info("Starting", "entity listeners", len(a.entityListeners)) slog.Info("Starting", "entity listeners", len(app.entityListeners))
slog.Info("Starting", "event listeners", len(a.eventListeners)) slog.Info("Starting", "event listeners", len(app.eventListeners))
go runSchedules(a) go runSchedules(app)
go runIntervals(a) go runIntervals(app)
// subscribe to state_changed events // subscribe to state_changed events
id := internal.NextId() id := internal.NextId()
ws.SubscribeToStateChangedEvents(id, a.wsWriter, a.ctx) connect.SubscribeToStateChangedEvents(id, app.conn, app.ctx)
a.entityListenersId = id app.entityListenersId = id
// entity listeners runOnStartup // Run entity listeners startup
for eid, etls := range a.entityListeners { for eid, etls := range app.entityListeners {
for _, etl := range etls { for _, etl := range etls {
// ensure each ETL only runs once, even if // ensure each ETL only runs once, even if
// it listens to multiple entities // it listens to multiple entities
if etl.runOnStartup && !etl.runOnStartupCompleted { if etl.runOnStartup && !etl.runOnStartupCompleted {
entityState, err := a.state.Get(eid) entityState, err := app.state.Get(eid)
if err != nil { if err != nil {
slog.Warn("Failed to get entity state \"", eid, "\" during startup, skipping RunOnStartup") slog.Warn("Failed to get entity state \"", eid, "\" during startup, skipping RunOnStartup")
} }
etl.runOnStartupCompleted = true etl.runOnStartupCompleted = true
go etl.callback(a.service, a.state, EntityData{ go etl.callback(app.service, app.state, EntityData{
TriggerEntityId: eid, TriggerEntityId: eid,
FromState: entityState.State, FromState: entityState.State,
FromAttributes: entityState.Attributes, FromAttributes: entityState.Attributes,
@@ -387,8 +331,8 @@ func (a *App) Start() {
} }
// entity listeners and event listeners // entity listeners and event listeners
elChan := make(chan ws.ChanMsg, 100) // Add buffer to prevent channel overflow elChan := make(chan connect.ChannelMessage, 100) // Add buffer to prevent channel overflow
go ws.ListenWebsocket(a.conn, elChan) go connect.ListenWebsocket(app.conn.Conn, elChan)
for { for {
select { select {
@@ -397,22 +341,22 @@ func (a *App) Start() {
slog.Info("Websocket channel closed, stopping main loop") slog.Info("Websocket channel closed, stopping main loop")
return return
} }
if a.entityListenersId == msg.Id { if app.entityListenersId == msg.Id {
go callEntityListeners(a, msg.Raw) go callEntityListeners(app, msg.Raw)
} else { } else {
go callEventListeners(a, msg) go callEventListeners(app, msg)
} }
case <-a.ctx.Done(): case <-app.ctx.Done():
slog.Info("Context cancelled, stopping main loop") slog.Info("Context cancelled, stopping main loop")
return return
} }
} }
} }
func (a *App) GetService() *Service { func (app *App) GetService() *Service {
return a.service return app.service
} }
func (a *App) GetState() State { func (app *App) GetState() State {
return a.state return app.state
} }

View File

@@ -4,21 +4,21 @@ import (
"time" "time"
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/types"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
) )
type conditionCheck struct { type ConditionCheck struct {
fail bool fail bool
} }
func checkWithinTimeRange(startTime, endTime string) conditionCheck { func CheckWithinTimeRange(startTime, endTime string) ConditionCheck {
cc := conditionCheck{fail: false} cc := ConditionCheck{fail: false}
// if betweenStart and betweenEnd both set, first account for midnight // if betweenStart and betweenEnd both set, first account for midnight
// overlap, then check if between those times. // overlap, then check if between those times.
if startTime != "" && endTime != "" { if startTime != "" && endTime != "" {
parsedStart := parse.ParseTime(startTime) parsedStart := internal.ParseTime(startTime)
parsedEnd := parse.ParseTime(endTime) parsedEnd := internal.ParseTime(endTime)
// check for midnight overlap // check for midnight overlap
if parsedEnd.Lt(parsedStart) { // example turn on night lights when motion from 23:00 to 07:00 if parsedEnd.Lt(parsedStart) { // example turn on night lights when motion from 23:00 to 07:00
@@ -35,16 +35,16 @@ func checkWithinTimeRange(startTime, endTime string) conditionCheck {
} }
// otherwise just check individual before/after // otherwise just check individual before/after
} else if startTime != "" && parse.ParseTime(startTime).IsFuture() { } else if startTime != "" && internal.ParseTime(startTime).IsFuture() {
cc.fail = true cc.fail = true
} else if endTime != "" && parse.ParseTime(endTime).IsPast() { } else if endTime != "" && internal.ParseTime(endTime).IsPast() {
cc.fail = true cc.fail = true
} }
return cc return cc
} }
func checkStatesMatch(listenerState, s string) conditionCheck { func CheckStatesMatch(listenerState, s string) ConditionCheck {
cc := conditionCheck{fail: false} cc := ConditionCheck{fail: false}
// check if fromState or toState are set and don't match // check if fromState or toState are set and don't match
if listenerState != "" && listenerState != s { if listenerState != "" && listenerState != s {
cc.fail = true cc.fail = true
@@ -52,8 +52,8 @@ func checkStatesMatch(listenerState, s string) conditionCheck {
return cc return cc
} }
func checkThrottle(throttle time.Duration, lastRan carbon.Carbon) conditionCheck { func CheckThrottle(throttle time.Duration, lastRan *carbon.Carbon) ConditionCheck {
cc := conditionCheck{fail: false} cc := ConditionCheck{fail: false}
// check if Throttle is set and that duration hasn't passed since lastRan // check if Throttle is set and that duration hasn't passed since lastRan
if throttle.Seconds() > 0 && if throttle.Seconds() > 0 &&
lastRan.DiffAbsInSeconds(carbon.Now()) < int64(throttle.Seconds()) { lastRan.DiffAbsInSeconds(carbon.Now()) < int64(throttle.Seconds()) {
@@ -62,8 +62,8 @@ func checkThrottle(throttle time.Duration, lastRan carbon.Carbon) conditionCheck
return cc return cc
} }
func checkExceptionDates(eList []time.Time) conditionCheck { func CheckExceptionDates(eList []time.Time) ConditionCheck {
cc := conditionCheck{fail: false} cc := ConditionCheck{fail: false}
for _, e := range eList { for _, e := range eList {
y1, m1, d1 := e.Date() y1, m1, d1 := e.Date()
y2, m2, d2 := time.Now().Date() y2, m2, d2 := time.Now().Date()
@@ -75,11 +75,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
} }
@@ -87,8 +87,8 @@ func checkExceptionRanges(eList []timeRange) conditionCheck {
return cc return cc
} }
func checkEnabledEntity(s State, infos []internal.EnabledDisabledInfo) conditionCheck { func CheckEnabledEntity(s State, infos []internal.EnabledDisabledInfo) ConditionCheck {
cc := conditionCheck{fail: false} cc := ConditionCheck{fail: false}
if len(infos) == 0 { if len(infos) == 0 {
return cc return cc
} }
@@ -115,8 +115,8 @@ func checkEnabledEntity(s State, infos []internal.EnabledDisabledInfo) condition
return cc return cc
} }
func checkDisabledEntity(s State, infos []internal.EnabledDisabledInfo) conditionCheck { func CheckDisabledEntity(s State, infos []internal.EnabledDisabledInfo) ConditionCheck {
cc := conditionCheck{fail: false} cc := ConditionCheck{fail: false}
if len(infos) == 0 { if len(infos) == 0 {
return cc return cc
} }
@@ -144,12 +144,12 @@ func checkDisabledEntity(s State, infos []internal.EnabledDisabledInfo) conditio
return cc return cc
} }
func checkAllowlistDates(eList []time.Time) conditionCheck { func CheckAllowlistDates(eList []time.Time) ConditionCheck {
if len(eList) == 0 { if len(eList) == 0 {
return conditionCheck{fail: false} return ConditionCheck{fail: false}
} }
cc := conditionCheck{fail: true} cc := ConditionCheck{fail: true}
for _, e := range eList { for _, e := range eList {
y1, m1, d1 := e.Date() y1, m1, d1 := e.Date()
y2, m2, d2 := time.Now().Date() y2, m2, d2 := time.Now().Date()
@@ -161,15 +161,15 @@ 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" {
return cc return cc
} }
now := time.Now() now := time.Now()
parsedTime := parse.ParseTime(string(s)).Carbon2Time() parsedTime := internal.ParseTime(string(s)).StdTime()
if isStart { if isStart {
if parsedTime.After(now) { if parsedTime.After(now) {
cc.fail = true cc.fail = true

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) {
@@ -65,7 +66,7 @@ func TestEnabledEntity_StateEqual_Passes(t *testing.T) {
state := MockState{ state := MockState{
EqualsReturn: true, EqualsReturn: true,
} }
c := checkEnabledEntity(state, list(runOnError)) c := CheckEnabledEntity(state, list(runOnError))
assert.False(t, c.fail, "should pass") assert.False(t, c.fail, "should pass")
} }
@@ -73,7 +74,7 @@ func TestEnabledEntity_StateNotEqual_Fails(t *testing.T) {
state := MockState{ state := MockState{
EqualsReturn: false, EqualsReturn: false,
} }
c := checkEnabledEntity(state, list(runOnError)) c := CheckEnabledEntity(state, list(runOnError))
assert.True(t, c.fail, "should fail") assert.True(t, c.fail, "should fail")
} }
@@ -81,7 +82,7 @@ func TestEnabledEntity_NetworkError_DontRun_Fails(t *testing.T) {
state := MockState{ state := MockState{
EqualsError: true, EqualsError: true,
} }
c := checkEnabledEntity(state, list(dontRunOnError)) c := CheckEnabledEntity(state, list(dontRunOnError))
assert.True(t, c.fail, "should fail") assert.True(t, c.fail, "should fail")
} }
@@ -89,7 +90,7 @@ func TestEnabledEntity_NetworkError_StillRun_Passes(t *testing.T) {
state := MockState{ state := MockState{
EqualsError: true, EqualsError: true,
} }
c := checkEnabledEntity(state, list(runOnError)) c := CheckEnabledEntity(state, list(runOnError))
assert.False(t, c.fail, "should fail") assert.False(t, c.fail, "should fail")
} }
@@ -97,7 +98,7 @@ func TestDisabledEntity_StateEqual_Fails(t *testing.T) {
state := MockState{ state := MockState{
EqualsReturn: true, EqualsReturn: true,
} }
c := checkDisabledEntity(state, list(runOnError)) c := CheckDisabledEntity(state, list(runOnError))
assert.True(t, c.fail, "should pass") assert.True(t, c.fail, "should pass")
} }
@@ -105,7 +106,7 @@ func TestDisabledEntity_StateNotEqual_Passes(t *testing.T) {
state := MockState{ state := MockState{
EqualsReturn: false, EqualsReturn: false,
} }
c := checkDisabledEntity(state, list(runOnError)) c := CheckDisabledEntity(state, list(runOnError))
assert.False(t, c.fail, "should fail") assert.False(t, c.fail, "should fail")
} }
@@ -113,7 +114,7 @@ func TestDisabledEntity_NetworkError_DontRun_Fails(t *testing.T) {
state := MockState{ state := MockState{
EqualsError: true, EqualsError: true,
} }
c := checkDisabledEntity(state, list(dontRunOnError)) c := CheckDisabledEntity(state, list(dontRunOnError))
assert.True(t, c.fail, "should fail") assert.True(t, c.fail, "should fail")
} }
@@ -121,16 +122,16 @@ func TestDisabledEntity_NetworkError_StillRun_Passes(t *testing.T) {
state := MockState{ state := MockState{
EqualsError: true, EqualsError: true,
} }
c := checkDisabledEntity(state, list(runOnError)) c := CheckDisabledEntity(state, list(runOnError))
assert.False(t, c.fail, "should fail") assert.False(t, c.fail, "should fail")
} }
func TestStatesMatch(t *testing.T) { func TestStatesMatch(t *testing.T) {
c := checkStatesMatch("hey", "hey") c := CheckStatesMatch("hey", "hey")
assert.False(t, c.fail, "should pass") assert.False(t, c.fail, "should pass")
} }
func TestStatesDontMatch(t *testing.T) { func TestStatesDontMatch(t *testing.T) {
c := checkStatesMatch("hey", "bye") c := CheckStatesMatch("hey", "bye")
assert.True(t, c.fail, "should fail") assert.True(t, c.fail, "should fail")
} }

View File

@@ -9,7 +9,8 @@ import (
"strings" "strings"
"text/template" "text/template"
ga "github.com/Xevion/go-ha" ha "github.com/Xevion/go-ha"
"github.com/Xevion/go-ha/types"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -67,7 +68,7 @@ func toCamelCase(s string) string {
} }
// validateHomeZone verifies that the home zone entity exists and is valid // validateHomeZone verifies that the home zone entity exists and is valid
func validateHomeZone(state ga.State, entityID string) error { func validateHomeZone(state ha.State, entityID string) error {
entity, err := state.Get(entityID) entity, err := state.Get(entityID)
if err != nil { if err != nil {
return fmt.Errorf("home zone entity '%s' not found: %w", entityID, err) return fmt.Errorf("home zone entity '%s' not found: %w", entityID, err)
@@ -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 := ha.NewApp(types.NewAppRequest{
URL: config.URL, URL: config.URL,
HAAuthToken: config.HAAuthToken, HAAuthToken: config.HAAuthToken,
HomeZoneEntityId: config.HomeZoneEntityId, HomeZoneEntityId: config.HomeZoneEntityId,

View File

@@ -5,10 +5,10 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/types"
) )
type EntityListener struct { type EntityListener struct {
@@ -17,7 +17,7 @@ type EntityListener struct {
fromState string fromState string
toState string toState string
throttle time.Duration throttle time.Duration
lastRan carbon.Carbon lastRan *carbon.Carbon
betweenStart string betweenStart string
betweenEnd string betweenEnd string
@@ -26,7 +26,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,14 +127,14 @@ 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 := internal.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 := internal.ParseDuration(string(s))
b.entityListener.throttle = d b.entityListener.throttle = d
return b return b
} }
@@ -145,7 +145,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: start, End: end})
return b return b
} }
@@ -192,7 +192,6 @@ func (b elBuilder3) Build() EntityListener {
return b.entityListener return b.entityListener
} }
/* Functions */
func callEntityListeners(app *App, msgBytes []byte) { func callEntityListeners(app *App, msgBytes []byte) {
msg := stateChangedMsg{} msg := stateChangedMsg{}
_ = json.Unmarshal(msgBytes, &msg) _ = json.Unmarshal(msgBytes, &msg)
@@ -214,31 +213,31 @@ func callEntityListeners(app *App, msgBytes []byte) {
for _, l := range listeners { for _, l := range listeners {
// Check conditions // Check conditions
if c := checkWithinTimeRange(l.betweenStart, l.betweenEnd); c.fail { if c := CheckWithinTimeRange(l.betweenStart, l.betweenEnd); c.fail {
continue continue
} }
if c := checkStatesMatch(l.fromState, data.OldState.State); c.fail { if c := CheckStatesMatch(l.fromState, data.OldState.State); c.fail {
continue continue
} }
if c := checkStatesMatch(l.toState, data.NewState.State); c.fail { if c := CheckStatesMatch(l.toState, data.NewState.State); c.fail {
if l.delayTimer != nil { if l.delayTimer != nil {
l.delayTimer.Stop() l.delayTimer.Stop()
} }
continue continue
} }
if c := checkThrottle(l.throttle, l.lastRan); c.fail { if c := CheckThrottle(l.throttle, l.lastRan); c.fail {
continue continue
} }
if c := checkExceptionDates(l.exceptionDates); c.fail { if c := CheckExceptionDates(l.exceptionDates); c.fail {
continue continue
} }
if c := checkExceptionRanges(l.exceptionRanges); c.fail { if c := CheckExceptionRanges(l.exceptionRanges); c.fail {
continue continue
} }
if c := checkEnabledEntity(app.state, l.enabledEntities); c.fail { if c := CheckEnabledEntity(app.state, l.enabledEntities); c.fail {
continue continue
} }
if c := checkDisabledEntity(app.state, l.disabledEntities); c.fail { if c := CheckDisabledEntity(app.state, l.disabledEntities); c.fail {
continue continue
} }

View File

@@ -5,11 +5,11 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
"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/connect"
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/types"
) )
type EventListener struct { type EventListener struct {
@@ -18,10 +18,10 @@ type EventListener struct {
betweenStart string betweenStart string
betweenEnd string betweenEnd string
throttle time.Duration throttle time.Duration
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,8 +80,8 @@ 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 := internal.ParseDuration(string(s))
b.eventListener.throttle = d b.eventListener.throttle = d
return b return b
} }
@@ -92,7 +92,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: start, End: end})
return b return b
} }
@@ -141,7 +141,7 @@ type BaseEventMsg struct {
} }
/* Functions */ /* Functions */
func callEventListeners(app *App, msg ws.ChanMsg) { func callEventListeners(app *App, msg connect.ChannelMessage) {
baseEventMsg := BaseEventMsg{} baseEventMsg := BaseEventMsg{}
_ = json.Unmarshal(msg.Raw, &baseEventMsg) _ = json.Unmarshal(msg.Raw, &baseEventMsg)
listeners, ok := app.eventListeners[baseEventMsg.Event.EventType] listeners, ok := app.eventListeners[baseEventMsg.Event.EventType]
@@ -152,22 +152,22 @@ func callEventListeners(app *App, msg ws.ChanMsg) {
for _, l := range listeners { for _, l := range listeners {
// Check conditions // Check conditions
if c := checkWithinTimeRange(l.betweenStart, l.betweenEnd); c.fail { if c := CheckWithinTimeRange(l.betweenStart, l.betweenEnd); c.fail {
continue continue
} }
if c := checkThrottle(l.throttle, l.lastRan); c.fail { if c := CheckThrottle(l.throttle, l.lastRan); c.fail {
continue continue
} }
if c := checkExceptionDates(l.exceptionDates); c.fail { if c := CheckExceptionDates(l.exceptionDates); c.fail {
continue continue
} }
if c := checkExceptionRanges(l.exceptionRanges); c.fail { if c := CheckExceptionRanges(l.exceptionRanges); c.fail {
continue continue
} }
if c := checkEnabledEntity(app.state, l.enabledEntities); c.fail { if c := CheckEnabledEntity(app.state, l.enabledEntities); c.fail {
continue continue
} }
if c := checkDisabledEntity(app.state, l.disabledEntities); c.fail { if c := CheckDisabledEntity(app.state, l.disabledEntities); c.fail {
continue continue
} }

View File

@@ -7,14 +7,13 @@ import (
"time" "time"
// "example/entities" // Optional import generated entities // "example/entities" // Optional import generated entities
ha "github.com/Xevion/go-ha"
ga "github.com/Xevion/go-ha"
) )
//go:generate go run github.com/Xevion/go-ha/cmd/generate //go:generate go run github.com/Xevion/go-ha/cmd/generate
func main() { func main() {
app, err := ga.NewApp(ga.NewAppRequest{ app, err := ha.NewApp(ha.NewAppRequest{
URL: "http://192.168.86.67:8123", // Replace with your Home Assistant URL URL: "http://192.168.86.67:8123", // Replace with your Home Assistant URL
HAAuthToken: os.Getenv("HA_AUTH_TOKEN"), HAAuthToken: os.Getenv("HA_AUTH_TOKEN"),
HomeZoneEntityId: "zone.home", HomeZoneEntityId: "zone.home",
@@ -32,25 +31,25 @@ func main() {
slog.Info("Application shutdown complete") slog.Info("Application shutdown complete")
}() }()
pantryDoor := ga. pantryDoor := ha.
NewEntityListener(). NewEntityListener().
EntityIds(entities.BinarySensor.PantryDoor). // Use generated entity constant EntityIds(entities.BinarySensor.PantryDoor). // Use generated entity constant
Call(pantryLights). Call(pantryLights).
Build() Build()
_11pmSched := ga. _11pmSched := ha.
NewDailySchedule(). NewDailySchedule().
Call(lightsOut). Call(lightsOut).
At("23:00"). At("23:00").
Build() Build()
_30minsBeforeSunrise := ga. _30minsBeforeSunrise := ha.
NewDailySchedule(). NewDailySchedule().
Call(sunriseSched). Call(sunriseSched).
Sunrise("-30m"). Sunrise("-30m").
Build() Build()
zwaveEventListener := ga. zwaveEventListener := ha.
NewEventListener(). NewEventListener().
EventTypes("zwave_js_value_notification"). EventTypes("zwave_js_value_notification").
Call(onEvent). Call(onEvent).
@@ -63,7 +62,7 @@ func main() {
app.Start() app.Start()
} }
func pantryLights(service *ga.Service, state ga.State, sensor ga.EntityData) { func pantryLights(service *ha.Service, state ha.State, sensor ha.EntityData) {
l := "light.pantry" l := "light.pantry"
// l := entities.Light.Pantry // Or use generated entity constant // l := entities.Light.Pantry // Or use generated entity constant
if sensor.ToState == "on" { if sensor.ToState == "on" {
@@ -73,18 +72,18 @@ func pantryLights(service *ga.Service, state ga.State, sensor ga.EntityData) {
} }
} }
func onEvent(service *ga.Service, state ga.State, data ga.EventData) { func onEvent(service *ha.Service, state ha.State, data ha.EventData) {
// Since the structure of the event changes depending // Since the structure of the event changes depending
// on the event type, you can Unmarshal the raw json // on the event type, you can Unmarshal the raw json
// into a Go type. If a type for your event doesn't // into a Go type. If a type for your event doesn't
// exist, you can write it yourself! PR's welcome to // exist, you can write it yourself! PR's welcome to
// the eventTypes.go file :) // the eventTypes.go file :)
ev := ga.EventZWaveJSValueNotification{} ev := ha.EventZWaveJSValueNotification{}
json.Unmarshal(data.RawEventJSON, &ev) json.Unmarshal(data.RawEventJSON, &ev)
slog.Info("On event invoked", "event", ev) slog.Info("On event invoked", "event", ev)
} }
func lightsOut(service *ga.Service, state ga.State) { func lightsOut(service *ha.Service, state ha.State) {
// always turn off outside lights // always turn off outside lights
service.Light.TurnOff(entities.Light.OutsideLights) service.Light.TurnOff(entities.Light.OutsideLights)
s, err := state.Get(entities.BinarySensor.LivingRoomMotion) s, err := state.Get(entities.BinarySensor.LivingRoomMotion)
@@ -99,7 +98,7 @@ func lightsOut(service *ga.Service, state ga.State) {
} }
} }
func sunriseSched(service *ga.Service, state ga.State) { func sunriseSched(service *ha.Service, state ha.State) {
service.Light.TurnOn(entities.Light.LivingRoomLamps) service.Light.TurnOn(entities.Light.LivingRoomLamps)
service.Light.TurnOff(entities.Light.ChristmasLights) service.Light.TurnOff(entities.Light.ChristmasLights)
} }

View File

@@ -11,13 +11,13 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
ga "github.com/Xevion/go-ha" ha "github.com/Xevion/go-ha"
) )
type ( type (
MySuite struct { MySuite struct {
suite.Suite suite.Suite
app *ga.App app *ha.App
config *Config config *Config
suiteCtx map[string]any suiteCtx map[string]any
} }
@@ -62,7 +62,7 @@ func (s *MySuite) SetupSuite() {
slog.Error("Error unmarshalling config file", "error", err) slog.Error("Error unmarshalling config file", "error", err)
} }
s.app, err = ga.NewApp(ga.NewAppRequest{ s.app, err = ha.NewApp(ha.NewAppRequest{
HAAuthToken: s.config.Hass.HAAuthToken, HAAuthToken: s.config.Hass.HAAuthToken,
IpAddress: s.config.Hass.IpAddress, IpAddress: s.config.Hass.IpAddress,
HomeZoneEntityId: s.config.Hass.HomeZoneEntityId, HomeZoneEntityId: s.config.Hass.HomeZoneEntityId,
@@ -76,13 +76,13 @@ func (s *MySuite) SetupSuite() {
entityId := s.config.Entities.LightEntityId entityId := s.config.Entities.LightEntityId
if entityId != "" { if entityId != "" {
s.suiteCtx["entityCallbackInvoked"] = false s.suiteCtx["entityCallbackInvoked"] = false
etl := ga.NewEntityListener().EntityIds(entityId).Call(s.entityCallback).Build() etl := ha.NewEntityListener().EntityIds(entityId).Call(s.entityCallback).Build()
s.app.RegisterEntityListeners(etl) s.app.RegisterEntityListeners(etl)
} }
s.suiteCtx["dailyScheduleCallbackInvoked"] = false s.suiteCtx["dailyScheduleCallbackInvoked"] = false
runTime := time.Now().Add(1 * time.Minute).Format("15:04") runTime := time.Now().Add(1 * time.Minute).Format("15:04")
dailySchedule := ga.NewDailySchedule().Call(s.dailyScheduleCallback).At(runTime).Build() dailySchedule := ha.NewDailySchedule().Call(s.dailyScheduleCallback).At(runTime).Build()
s.app.RegisterSchedules(dailySchedule) s.app.RegisterSchedules(dailySchedule)
// start GA app // start GA app
@@ -122,13 +122,13 @@ func (s *MySuite) TestSchedule() {
} }
// Capture event after light entity state has changed // Capture event after light entity state has changed
func (s *MySuite) entityCallback(se *ga.Service, st ga.State, e ga.EntityData) { func (s *MySuite) entityCallback(se *ha.Service, st ha.State, e ha.EntityData) {
slog.Info("Entity callback called.", "entity id", e.TriggerEntityId, "from state", e.FromState, "to state", e.ToState) slog.Info("Entity callback called.", "entity id", e.TriggerEntityId, "from state", e.FromState, "to state", e.ToState)
s.suiteCtx["entityCallbackInvoked"] = true s.suiteCtx["entityCallbackInvoked"] = true
} }
// Capture planned daily schedule // Capture planned daily schedule
func (s *MySuite) dailyScheduleCallback(se *ga.Service, st ga.State) { func (s *MySuite) dailyScheduleCallback(se *ha.Service, st ha.State) {
slog.Info("Daily schedule callback called.") slog.Info("Daily schedule callback called.")
s.suiteCtx["dailyScheduleCallbackInvoked"] = true s.suiteCtx["dailyScheduleCallbackInvoked"] = true
} }

View File

@@ -3,22 +3,25 @@ module example
go 1.23 go 1.23
require ( require (
github.com/Xevion/go-ha v0.7.0
github.com/golang-cz/devslog v0.0.8 github.com/golang-cz/devslog v0.0.8
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
github.com/Xevion/gome-assistant v0.2.0
) )
require ( require (
github.com/Workiva/go-datastructures v1.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gobuffalo/envy v1.10.2 // indirect github.com/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect github.com/gobuffalo/packr v1.30.1 // indirect
github.com/golang-module/carbon v1.7.3 // indirect github.com/golang-module/carbon v1.7.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
github.com/nathan-osman/go-sunrise v1.1.0 // indirect github.com/nathan-osman/go-sunrise v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
golang.org/x/mod v0.9.0 // indirect golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.33.0 // indirect
resty.dev/v3 v3.0.0-beta.3 // indirect
) )

View File

@@ -1,4 +1,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4=
github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/Xevion/go-ha v0.7.0 h1:jf+ZVSDaw0xjY0TcCA/TodWmAehtm47hDQI5z8XJMQE=
github.com/Xevion/go-ha v0.7.0/go.mod h1:TN+40o0znxEdvR7GQgm5YWMiCEJvsoFbnro2oW38RVU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -20,8 +24,8 @@ github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIavi
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/golang-cz/devslog v0.0.8 h1:53ipA2rC5JzWBWr9qB8EfenvXppenNiF/8DwgtNT5Q4= github.com/golang-cz/devslog v0.0.8 h1:53ipA2rC5JzWBWr9qB8EfenvXppenNiF/8DwgtNT5Q4=
github.com/golang-cz/devslog v0.0.8/go.mod h1:bSe5bm0A7Nyfqtijf1OMNgVJHlWEuVSXnkuASiE1vV8= github.com/golang-cz/devslog v0.0.8/go.mod h1:bSe5bm0A7Nyfqtijf1OMNgVJHlWEuVSXnkuASiE1vV8=
github.com/golang-module/carbon v1.7.3 h1:p5mUZj7Tg62MblrkF7XEoxVPvhVs20N/kimqsZOQ+/U= github.com/golang-module/carbon v1.7.1 h1:EDPV0YjxeS2kE2cRedfGgDikU6l5D79HB/teHuZDLu8=
github.com/golang-module/carbon v1.7.3/go.mod h1:nUMnXq90Rv8a7h2+YOo2BGKS77Y0w/hMPm4/a8h19N8= github.com/golang-module/carbon v1.7.1/go.mod h1:M/TDTYPp3qWtW68u49dLDJOyGmls6L6BXdo/pyvkMaU=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -44,6 +48,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/nathan-osman/go-sunrise v1.1.0 h1:ZqZmtmtzs8Os/DGQYi0YMHpuUqR/iRoJK+wDO0wTCw8= github.com/nathan-osman/go-sunrise v1.1.0 h1:ZqZmtmtzs8Os/DGQYi0YMHpuUqR/iRoJK+wDO0wTCw8=
github.com/nathan-osman/go-sunrise v1.1.0/go.mod h1:RcWqhT+5ShCZDev79GuWLayetpJp78RSjSWxiDowmlM= github.com/nathan-osman/go-sunrise v1.1.0/go.mod h1:RcWqhT+5ShCZDev79GuWLayetpJp78RSjSWxiDowmlM=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -63,31 +68,49 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -96,5 +119,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/Xevion/gome-assistant v0.2.0 h1:Clo5DrziTdsYydVUTQfroeBVmToMnNHoObr+k6HhbMY= resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E=
github.com/Xevion/gome-assistant v0.2.0/go.mod h1:jsZUtnxANCP0zB2B7iyy4j7sZohMGop8g+5EB2MER3o= resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4=

17
go.mod
View File

@@ -1,11 +1,13 @@
module github.com/Xevion/go-ha module github.com/Xevion/go-ha
go 1.21 go 1.23.0
toolchain go1.24.2
require ( require (
github.com/Workiva/go-datastructures v1.1.5 github.com/Workiva/go-datastructures v1.1.5
github.com/golang-module/carbon v1.7.1 github.com/dromara/carbon/v2 v2.6.11
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.3
github.com/nathan-osman/go-sunrise v1.1.0 github.com/nathan-osman/go-sunrise v1.1.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
@@ -14,11 +16,8 @@ require (
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gobuffalo/envy v1.10.2 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/net v0.33.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
) )

71
go.sum
View File

@@ -1,103 +1,46 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4= github.com/Workiva/go-datastructures v1.1.5 h1:5YfhQ4ry7bZc2Mc7R0YZyYwpf5c6t1cEFvdAhd6Mkf4=
github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Workiva/go-datastructures v1.1.5/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/dromara/carbon/v2 v2.6.11 h1:wnAWZ+sbza1uXw3r05hExNSCaBPFaarWfUvYAX86png=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/dromara/carbon/v2 v2.6.11/go.mod h1:7GXqCUplwN1s1b4whGk2zX4+g4CMCoDIZzmjlyt0vLY=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/golang-module/carbon v1.7.1 h1:EDPV0YjxeS2kE2cRedfGgDikU6l5D79HB/teHuZDLu8=
github.com/golang-module/carbon v1.7.1/go.mod h1:M/TDTYPp3qWtW68u49dLDJOyGmls6L6BXdo/pyvkMaU=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nathan-osman/go-sunrise v1.1.0 h1:ZqZmtmtzs8Os/DGQYi0YMHpuUqR/iRoJK+wDO0wTCw8= github.com/nathan-osman/go-sunrise v1.1.0 h1:ZqZmtmtzs8Os/DGQYi0YMHpuUqR/iRoJK+wDO0wTCw8=
github.com/nathan-osman/go-sunrise v1.1.0/go.mod h1:RcWqhT+5ShCZDev79GuWLayetpJp78RSjSWxiDowmlM= github.com/nathan-osman/go-sunrise v1.1.0/go.mod h1:RcWqhT+5ShCZDev79GuWLayetpJp78RSjSWxiDowmlM=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -106,8 +49,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -2,7 +2,7 @@
// websocket API. All HA interaction is done via websocket // websocket API. All HA interaction is done via websocket
// except for cases explicitly called out in http package // except for cases explicitly called out in http package
// documentation. // documentation.
package websocket package connect
import ( import (
"context" "context"
@@ -14,53 +14,66 @@ 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")
type AuthMessage struct { // HAConnection is a wrapper around a websocket connection that provides a mutex for thread safety.
MsgType string `json:"type"` type HAConnection struct {
AccessToken string `json:"access_token"` Conn *websocket.Conn // Note: this is not thread safe except for Close() and WriteControl()
}
type WebsocketWriter struct {
Conn *websocket.Conn
mutex sync.Mutex mutex sync.Mutex
} }
func (w *WebsocketWriter) WriteMessage(msg any) error { // WriteMessage writes a message to the websocket connection.
func (w *HAConnection) WriteMessage(msg any) error {
w.mutex.Lock() w.mutex.Lock()
defer w.mutex.Unlock() defer w.mutex.Unlock()
return w.Conn.WriteJSON(msg) return w.Conn.WriteJSON(msg)
} }
func ReadMessage(conn *websocket.Conn) ([]byte, error) { // ReadMessageRaw reads a raw message from the websocket connection.
func ReadMessageRaw(conn *websocket.Conn) ([]byte, error) {
_, msg, err := conn.ReadMessage() _, msg, err := conn.ReadMessage()
if err != nil { if err != nil {
return []byte{}, err return nil, err
} }
return msg, nil return msg, nil
} }
func ConnectionFromUri(baseURL *url.URL, authToken string) (*websocket.Conn, context.Context, context.CancelFunc, error) { // ReadMessage reads a message from the websocket connection and unmarshals it into the given type.
func ReadMessage[T any](conn *websocket.Conn) (T, error) {
var result T
_, msg, err := conn.ReadMessage()
if err != nil {
return result, err
}
err = json.Unmarshal(msg, &result)
if err != nil {
return result, err
}
return result, nil
}
// ConnectionFromUri creates a new websocket connection from the given base URL and authentication token.
func ConnectionFromUri(baseUrl *url.URL, token string) (*HAConnection, context.Context, context.CancelFunc, error) {
// Build the websocket URL
urlWebsockets := *baseUrl
urlWebsockets.Path = "/api/websocket"
scheme, err := internal.GetEquivalentWebsocketScheme(baseUrl.Scheme)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to build websocket URL: %w", err)
}
urlWebsockets.Scheme = scheme
// Create a short timeout context for the connection only // Create a short timeout context for the connection only
connCtx, connCtxCancel := context.WithTimeout(context.Background(), time.Second*3) connCtx, connCtxCancel := context.WithTimeout(context.Background(), time.Second*3)
defer connCtxCancel() // Always cancel the connection context when we're done defer connCtxCancel() // Always cancel the connection context when we're done
// Shallow copy the URL to avoid modifying the original
urlWebsockets := *baseURL
urlWebsockets.Path = "/api/websocket"
if baseURL.Scheme == "http" {
urlWebsockets.Scheme = "ws"
}
if baseURL.Scheme == "https" {
urlWebsockets.Scheme = "wss"
}
// Init websocket connection // Init websocket connection
dialer := websocket.DefaultDialer dialer := websocket.DefaultDialer
conn, _, err := dialer.DialContext(connCtx, urlWebsockets.String(), nil) conn, _, err := dialer.DialContext(connCtx, urlWebsockets.String(), nil)
@@ -70,14 +83,19 @@ func ConnectionFromUri(baseURL *url.URL, authToken string) (*websocket.Conn, con
} }
// Read auth_required message // Read auth_required message
_, err = ReadMessage(conn) msg, err := ReadMessage[struct {
MsgType string `json:"type"`
}](conn)
if err != nil { if err != nil {
slog.Error("Unknown error creating websocket client\n") slog.Error("Unknown error creating websocket client\n")
return nil, nil, nil, err return nil, nil, nil, err
} else if msg.MsgType != "auth_required" {
slog.Error("Expected auth_required message, got", "msgType", msg.MsgType)
return nil, nil, nil, fmt.Errorf("expected auth_required message, got %s", msg.MsgType)
} }
// Send auth message // Send auth message
err = SendAuthMessage(conn, connCtx, authToken) err = SendAuthMessage(conn, connCtx, token)
if err != nil { if err != nil {
slog.Error("Unknown error creating websocket client\n") slog.Error("Unknown error creating websocket client\n")
return nil, nil, nil, err return nil, nil, nil, err
@@ -93,51 +111,54 @@ func ConnectionFromUri(baseURL *url.URL, authToken string) (*websocket.Conn, con
// Create a new background context for the application lifecycle (no timeout) // Create a new background context for the application lifecycle (no timeout)
appCtx, appCtxCancel := context.WithCancel(context.Background()) appCtx, appCtxCancel := context.WithCancel(context.Background())
return conn, appCtx, appCtxCancel, nil return &HAConnection{Conn: conn}, appCtx, appCtxCancel, nil
} }
// SendAuthMessage sends an auth message to the websocket connection.
func SendAuthMessage(conn *websocket.Conn, ctx context.Context, token string) error { func SendAuthMessage(conn *websocket.Conn, ctx context.Context, token string) error {
type AuthMessage struct {
MsgType string `json:"type"`
AccessToken string `json:"access_token"`
}
err := conn.WriteJSON(AuthMessage{MsgType: "auth", AccessToken: token}) err := conn.WriteJSON(AuthMessage{MsgType: "auth", AccessToken: token})
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
type authResponse struct { // VerifyAuthResponse verifies that the auth response is valid.
MsgType string `json:"type"`
Message string `json:"message"`
}
func VerifyAuthResponse(conn *websocket.Conn, ctx context.Context) error { func VerifyAuthResponse(conn *websocket.Conn, ctx context.Context) error {
msg, err := ReadMessage(conn) msg, err := ReadMessage[struct {
MsgType string `json:"type"`
Message string `json:"message"`
}](conn)
if err != nil { if err != nil {
return err return err
} }
var authResp authResponse if msg.MsgType != "auth_ok" {
err = json.Unmarshal(msg, &authResp)
if err != nil {
return err
}
if authResp.MsgType != "auth_ok" {
return ErrInvalidToken return ErrInvalidToken
} }
return nil return nil
} }
type SubEvent struct { func SubscribeToStateChangedEvents(id int64, conn *HAConnection, ctx context.Context) {
Id int64 `json:"id"`
Type string `json:"type"`
EventType string `json:"event_type"`
}
func SubscribeToStateChangedEvents(id int64, conn *WebsocketWriter, ctx context.Context) {
SubscribeToEventType("state_changed", conn, ctx, id) SubscribeToEventType("state_changed", conn, ctx, id)
} }
func SubscribeToEventType(eventType string, conn *WebsocketWriter, ctx context.Context, id ...int64) { // TODO: Instead of using variadic arguments, just use a nillable pointer for the id
func SubscribeToEventType(eventType string, conn *HAConnection, ctx context.Context, id ...int64) {
type SubEvent struct {
Id int64 `json:"id"`
Type string `json:"type"`
EventType string `json:"event_type"`
}
// If no id is provided, generate a new one
var finalId int64 var finalId int64
if len(id) == 0 { if len(id) == 0 {
finalId = internal.NextId() finalId = internal.NextId()
@@ -149,12 +170,12 @@ func SubscribeToEventType(eventType string, conn *WebsocketWriter, ctx context.C
Type: "subscribe_events", Type: "subscribe_events",
EventType: eventType, EventType: eventType,
} }
err := conn.WriteMessage(e) err := conn.WriteMessage(e)
// TODO: Handle errors better
if err != nil { if err != nil {
wrappedErr := fmt.Errorf("error writing to websocket: %w", err) wrappedErr := fmt.Errorf("error writing to websocket: %w", err)
slog.Error(wrappedErr.Error()) slog.Error(wrappedErr.Error())
panic(wrappedErr) panic(wrappedErr)
} }
// m, _ := ReadMessage(conn, ctx)
// log.Default().Println(string(m))
} }

View File

@@ -1,4 +1,4 @@
package websocket package connect
import ( import (
"encoding/json" "encoding/json"
@@ -7,22 +7,26 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
// BaseMessage is the base message type for all messages sent by the websocket server.
type BaseMessage struct { type BaseMessage struct {
Type string `json:"type"` Type string `json:"type"`
Id int64 `json:"id"` Id int64 `json:"id"`
Success bool `json:"success"` Success bool `json:"success"` // not present in all messages
} }
type ChanMsg struct { type ChannelMessage struct {
Id int64 Id int64
Type string Type string
Success bool Success bool
Raw []byte Raw []byte
} }
func ListenWebsocket(conn *websocket.Conn, c chan ChanMsg) { // ListenWebsocket reads messages from the websocket connection and sends them to the channel.
// It will close the channel if it encounters an error, or if the channel is full, and return.
// It ignores errors in deserialization.
func ListenWebsocket(conn *websocket.Conn, c chan ChannelMessage) {
for { for {
bytes, err := ReadMessage(conn) raw, err := ReadMessageRaw(conn)
if err != nil { if err != nil {
slog.Error("Error reading from websocket", "err", err) slog.Error("Error reading from websocket", "err", err)
close(c) close(c)
@@ -33,20 +37,26 @@ func ListenWebsocket(conn *websocket.Conn, c chan ChanMsg) {
// default to true for messages that don't include "success" at all // default to true for messages that don't include "success" at all
Success: true, Success: true,
} }
_ = json.Unmarshal(bytes, &base) err = json.Unmarshal(raw, &base)
if !base.Success { if err != nil {
slog.Warn("Received unsuccessful response", "response", string(bytes)) slog.Error("Error unmarshalling message", "err", err, "message", string(raw))
continue
} }
chanMsg := ChanMsg{ if !base.Success {
slog.Warn("Received unsuccessful response", "response", string(raw))
}
// Create a channel message from the raw message
channelMessage := ChannelMessage{
Type: base.Type, Type: base.Type,
Id: base.Id, Id: base.Id,
Success: base.Success, Success: base.Success,
Raw: bytes, Raw: raw,
} }
// Use non-blocking send to avoid hanging on closed channel // Use non-blocking send to avoid hanging on closed channel
select { select {
case c <- chanMsg: case c <- channelMessage:
// Message sent successfully // Message sent successfully
default: default:
// Channel is full or closed, break out of loop // Channel is full or closed, break out of loop

View File

@@ -4,6 +4,7 @@
package internal package internal
import ( import (
"context"
"errors" "errors"
"net/url" "net/url"
"time" "time"
@@ -12,40 +13,44 @@ import (
) )
type HttpClient struct { type HttpClient struct {
client *resty.Client client *resty.Client
baseRequest *resty.Request
} }
func NewHttpClient(url *url.URL, token string) *HttpClient { func NewHttpClient(ctx context.Context, baseUrl *url.URL, token string) *HttpClient {
// Shallow copy the URL to avoid modifying the original // Shallow copy the URL to avoid modifying the original
u := *url u := *baseUrl
u.Path = "/api" u.Path = "/api"
if u.Scheme == "ws" {
u.Scheme = "http"
}
if u.Scheme == "wss" {
u.Scheme = "https"
}
// Create resty client with configuration // Create resty client with configuration
client := resty.New(). client := resty.New().
SetBaseURL(u.String()). SetBaseURL(u.String()).
SetHeader("Authorization", "Bearer "+token). SetTimeout(30*time.Second).
SetTimeout(30 * time.Second).
SetRetryCount(3). SetRetryCount(3).
SetRetryWaitTime(1 * time.Second). SetRetryWaitTime(1*time.Second).
SetRetryMaxWaitTime(5 * time.Second). SetRetryMaxWaitTime(5*time.Second).
AddRetryConditions(func(r *resty.Response, err error) bool { AddRetryConditions(func(r *resty.Response, err error) bool {
return err != nil || r.StatusCode() >= 500 return err != nil || (r.StatusCode() >= 500 && r.StatusCode() != 403)
}) }).
SetHeader("User-Agent", "go-ha/"+currentVersion).
SetContext(ctx)
return &HttpClient{ return &HttpClient{
client: client, client: client,
baseRequest: client.R().
SetContentType("application/json").
SetHeader("Accept", "application/json").
SetAuthToken(token),
} }
} }
// getRequest returns a new request
func (c *HttpClient) getRequest() *resty.Request {
return c.baseRequest.Clone(c.client.Context())
}
func (c *HttpClient) GetState(entityId string) ([]byte, error) { func (c *HttpClient) GetState(entityId string) ([]byte, error) {
resp, err := c.client.R(). resp, err := c.getRequest().Get("/states/" + entityId)
Get("/states/" + entityId)
if err != nil { if err != nil {
return nil, errors.New("Error making HTTP request: " + err.Error()) return nil, errors.New("Error making HTTP request: " + err.Error())
@@ -58,9 +63,9 @@ func (c *HttpClient) GetState(entityId string) ([]byte, error) {
return resp.Bytes(), nil return resp.Bytes(), nil
} }
func (c *HttpClient) States() ([]byte, error) { // GetStates returns the states of all entities.
resp, err := c.client.R(). func (c *HttpClient) GetStates() ([]byte, error) {
Get("/states") resp, err := c.getRequest().Get("/states")
if err != nil { if err != nil {
return nil, errors.New("Error making HTTP request: " + err.Error()) return nil, errors.New("Error making HTTP request: " + err.Error())

View File

@@ -1,6 +1,7 @@
package internal package internal
import ( import (
"fmt"
"reflect" "reflect"
"runtime" "runtime"
"sync/atomic" "sync/atomic"
@@ -12,6 +13,10 @@ type EnabledDisabledInfo struct {
RunOnError bool RunOnError bool
} }
var (
currentVersion = "0.7.1"
)
var ( var (
id atomic.Int64 // default value is 0 id atomic.Int64 // default value is 0
) )
@@ -26,3 +31,20 @@ func NextId() int64 {
func GetFunctionName(i interface{}) string { func GetFunctionName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
} }
// GetEquivalentWebsocketScheme returns the equivalent websocket scheme for the given scheme.
// If the scheme is http or https, it returns ws or wss respectively.
// If the scheme is ws or wss, it returns the same scheme.
// If the scheme is not any of the above, it returns an error.
func GetEquivalentWebsocketScheme(scheme string) (string, error) {
switch scheme {
case "http":
return "ws", nil
case "https":
return "wss", nil
case "ws", "wss":
return scheme, nil
default:
return "", fmt.Errorf("unexpected scheme: %s", scheme)
}
}

View File

@@ -1,15 +1,15 @@
package parse package internal
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"time" "time"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
) )
// Parses a HH:MM string. // Parses a HH:MM string.
func ParseTime(s string) carbon.Carbon { func ParseTime(s string) *carbon.Carbon {
t, err := time.Parse("15:04", s) t, err := time.Parse("15:04", s)
if err != nil { if err != nil {
parsingErr := fmt.Errorf("failed to parse time string \"%s\"; format must be HH:MM.: %w", s, err) parsingErr := fmt.Errorf("failed to parse time string \"%s\"; format must be HH:MM.: %w", s, err)

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type AdaptiveLighting struct { type AdaptiveLighting struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Set manual control for an adaptive lighting entity. // Set manual control for an adaptive lighting entity.
func (al AdaptiveLighting) SetManualControl(entityId string, enabled bool) error { func (al AdaptiveLighting) SetManualControl(entityId string, enabled bool) error {
req := NewBaseServiceRequest("") req := NewBaseServiceRequest("")

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type AlarmControlPanel struct { type AlarmControlPanel struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Send the alarm the command for arm away. // Send the alarm the command for arm away.
// Takes an entityId and an optional // Takes an entityId and an optional
// map that is translated into service_data. // map that is translated into service_data.

View File

@@ -1,18 +1,14 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
"github.com/Xevion/go-ha/types" "github.com/Xevion/go-ha/types"
) )
/* Structs */
type Climate struct { type Climate struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (c Climate) SetFanMode(entityId string, fanMode string) error { func (c Climate) SetFanMode(entityId string, fanMode string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "climate" req.Domain = "climate"

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Cover struct { type Cover struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Close all or specified cover. Takes an entityId. // Close all or specified cover. Takes an entityId.
func (c Cover) Close(entityId string) error { func (c Cover) Close(entityId string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)

View File

@@ -2,11 +2,11 @@ package services
import ( import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
type Event struct { type Event struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
// Fire an event // Fire an event
@@ -17,8 +17,6 @@ type FireEventRequest struct {
EventData map[string]any `json:"event_data,omitempty"` EventData map[string]any `json:"event_data,omitempty"`
} }
/* Public API */
// Fire an event. Takes an event type and an optional map that is sent // Fire an event. Takes an event type and an optional map that is sent
// as `event_data`. // as `event_data`.
func (e Event) Fire(eventType string, eventData ...map[string]any) error { func (e Event) Fire(eventType string, eventData ...map[string]any) error {

View File

@@ -1,11 +1,11 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
type HomeAssistant struct { type HomeAssistant struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
// TurnOn a Home Assistant entity. Takes an entityId and an optional // TurnOn a Home Assistant entity. Takes an entityId and an optional

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type InputBoolean struct { type InputBoolean struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (ib InputBoolean) TurnOn(entityId string) error { func (ib InputBoolean) TurnOn(entityId string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "input_boolean" req.Domain = "input_boolean"

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type InputButton struct { type InputButton struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (ib InputButton) Press(entityId string) error { func (ib InputButton) Press(entityId string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "input_button" req.Domain = "input_button"

View File

@@ -4,17 +4,13 @@ import (
"fmt" "fmt"
"time" "time"
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type InputDatetime struct { type InputDatetime struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (ib InputDatetime) Set(entityId string, value time.Time) error { func (ib InputDatetime) Set(entityId string, value time.Time) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "input_datetime" req.Domain = "input_datetime"

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type InputNumber struct { type InputNumber struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (ib InputNumber) Set(entityId string, value float32) error { func (ib InputNumber) Set(entityId string, value float32) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "input_number" req.Domain = "input_number"

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type InputText struct { type InputText struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (ib InputText) Set(entityId string, value string) error { func (ib InputText) Set(entityId string, value string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "input_text" req.Domain = "input_text"

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Light struct { type Light struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// TurnOn a light entity. Takes an entityId and an optional // TurnOn a light entity. Takes an entityId and an optional
// map that is translated into service_data. // map that is translated into service_data.
func (l Light) TurnOn(entityId string, serviceData ...map[string]any) error { func (l Light) TurnOn(entityId string, serviceData ...map[string]any) error {

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Lock struct { type Lock struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Lock a lock entity. Takes an entityId and an optional // Lock a lock entity. Takes an entityId and an optional
// map that is translated into service_data. // map that is translated into service_data.
func (l Lock) Lock(entityId string, serviceData ...map[string]any) error { func (l Lock) Lock(entityId string, serviceData ...map[string]any) error {

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type MediaPlayer struct { type MediaPlayer struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Send the media player the command to clear players playlist. // Send the media player the command to clear players playlist.
// Takes an entityId. // Takes an entityId.
func (mp MediaPlayer) ClearPlaylist(entityId string) error { func (mp MediaPlayer) ClearPlaylist(entityId string) error {

View File

@@ -1,12 +1,12 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
"github.com/Xevion/go-ha/types" "github.com/Xevion/go-ha/types"
) )
type Notify struct { type Notify struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
// Notify sends a notification. Takes a types.NotifyRequest. // Notify sends a notification. Takes a types.NotifyRequest.

View File

@@ -1,11 +1,11 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
type Number struct { type Number struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
func (ib Number) SetValue(entityId string, value float32) error { func (ib Number) SetValue(entityId string, value float32) error {

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Scene struct { type Scene struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Apply a scene. Takes map that is translated into service_data. // Apply a scene. Takes map that is translated into service_data.
func (s Scene) Apply(serviceData ...map[string]any) error { func (s Scene) Apply(serviceData ...map[string]any) error {
req := NewBaseServiceRequest("") req := NewBaseServiceRequest("")

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Script struct { type Script struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Reload a script that was created in the HA UI. // Reload a script that was created in the HA UI.
func (s Script) Reload(entityId string) error { func (s Script) Reload(entityId string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)

View File

@@ -2,7 +2,7 @@ package services
import ( import (
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
func BuildService[ func BuildService[
@@ -29,7 +29,7 @@ func BuildService[
Timer | Timer |
Vacuum | Vacuum |
ZWaveJS, ZWaveJS,
](conn *ws.WebsocketWriter) *T { ](conn *connect.HAConnection) *T {
return &T{conn: conn} return &T{conn: conn}
} }

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Switch struct { type Switch struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
func (s Switch) TurnOn(entityId string) error { func (s Switch) TurnOn(entityId string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)
req.Domain = "switch" req.Domain = "switch"

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Timer struct { type Timer struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// See https://www.home-assistant.io/integrations/timer/#action-timerstart // See https://www.home-assistant.io/integrations/timer/#action-timerstart
func (t Timer) Start(entityId string, duration string) error { func (t Timer) Start(entityId string, duration string) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type TTS struct { type TTS struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Remove all text-to-speech cache files and RAM cache. // Remove all text-to-speech cache files and RAM cache.
func (tts TTS) ClearCache() error { func (tts TTS) ClearCache() error {
req := NewBaseServiceRequest("") req := NewBaseServiceRequest("")

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type Vacuum struct { type Vacuum struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// Tell the vacuum cleaner to do a spot clean-up. // Tell the vacuum cleaner to do a spot clean-up.
// Takes an entityId. // Takes an entityId.
func (v Vacuum) CleanSpot(entityId string) error { func (v Vacuum) CleanSpot(entityId string) error {

View File

@@ -1,17 +1,13 @@
package services package services
import ( import (
ws "github.com/Xevion/go-ha/internal/websocket" "github.com/Xevion/go-ha/internal/connect"
) )
/* Structs */
type ZWaveJS struct { type ZWaveJS struct {
conn *ws.WebsocketWriter conn *connect.HAConnection
} }
/* Public API */
// ZWaveJS bulk_set_partial_config_parameters service. // ZWaveJS bulk_set_partial_config_parameters service.
func (zw ZWaveJS) BulkSetPartialConfigParam(entityId string, parameter int, value any) error { func (zw ZWaveJS) BulkSetPartialConfigParam(entityId string, parameter int, value any) error {
req := NewBaseServiceRequest(entityId) req := NewBaseServiceRequest(entityId)

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"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 +14,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
@@ -58,12 +58,12 @@ func (i Interval) String() string {
return fmt.Sprintf("Interval{ call %q every %s%s%s }", return fmt.Sprintf("Interval{ call %q every %s%s%s }",
internal.GetFunctionName(i.callback), internal.GetFunctionName(i.callback),
i.frequency, i.frequency,
formatStartOrEndString(i.startTime /* isStart = */, true), formatStartOrEndString(i.startTime, true),
formatStartOrEndString(i.endTime /* isStart = */, false), formatStartOrEndString(i.endTime, false),
) )
} }
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 +80,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 := internal.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 +104,10 @@ 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: start, End: end},
)
return ib return ib
} }
@@ -185,22 +188,22 @@ func runIntervals(a *App) {
} }
func (i Interval) maybeRunCallback(a *App) { func (i Interval) maybeRunCallback(a *App) {
if c := checkStartEndTime(i.startTime /* isStart = */, true); c.fail { if c := CheckStartEndTime(i.startTime /* isStart = */, true); c.fail {
return return
} }
if c := checkStartEndTime(i.endTime /* isStart = */, false); c.fail { if c := CheckStartEndTime(i.endTime /* isStart = */, false); c.fail {
return return
} }
if c := checkExceptionDates(i.exceptionDates); c.fail { if c := CheckExceptionDates(i.exceptionDates); c.fail {
return return
} }
if c := checkExceptionRanges(i.exceptionRanges); c.fail { if c := CheckExceptionRanges(i.exceptionRanges); c.fail {
return return
} }
if c := checkEnabledEntity(a.state, i.enabledEntities); c.fail { if c := CheckEnabledEntity(a.state, i.enabledEntities); c.fail {
return return
} }
if c := checkDisabledEntity(a.state, i.disabledEntities); c.fail { if c := CheckDisabledEntity(a.state, i.disabledEntities); c.fail {
return return
} }
go i.callback(a.service, a.state) go i.callback(a.service, a.state)

View File

@@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/Xevion/go-ha/internal" "github.com/Xevion/go-ha/internal"
"github.com/Xevion/go-ha/internal/parse" "github.com/Xevion/go-ha/types"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
) )
type ScheduleCallback func(*Service, State) type ScheduleCallback func(*Service, State)
@@ -23,7 +23,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
@@ -76,7 +76,7 @@ func (sb scheduleBuilder) Call(callback ScheduleCallback) scheduleBuilderCall {
// At takes a string in 24hr format time like "15:30". // At takes a string in 24hr format time like "15:30".
func (sb scheduleBuilderCall) At(s string) scheduleBuilderEnd { func (sb scheduleBuilderCall) At(s string) scheduleBuilderEnd {
t := parse.ParseTime(s) t := internal.ParseTime(s)
sb.schedule.hour = t.Hour() sb.schedule.hour = t.Hour()
sb.schedule.minute = t.Minute() sb.schedule.minute = t.Minute()
return scheduleBuilderEnd(sb) return scheduleBuilderEnd(sb)
@@ -85,7 +85,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 +96,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]
@@ -193,16 +193,16 @@ func runSchedules(a *App) {
} }
func (s DailySchedule) maybeRunCallback(a *App) { func (s DailySchedule) maybeRunCallback(a *App) {
if c := checkExceptionDates(s.exceptionDates); c.fail { if c := CheckExceptionDates(s.exceptionDates); c.fail {
return return
} }
if c := checkAllowlistDates(s.allowlistDates); c.fail { if c := CheckAllowlistDates(s.allowlistDates); c.fail {
return return
} }
if c := checkEnabledEntity(a.state, s.enabledEntities); c.fail { if c := CheckEnabledEntity(a.state, s.enabledEntities); c.fail {
return return
} }
if c := checkDisabledEntity(a.state, s.disabledEntities); c.fail { if c := CheckDisabledEntity(a.state, s.disabledEntities); c.fail {
return return
} }
go s.callback(a.service, a.state) go s.callback(a.service, a.state)
@@ -215,7 +215,7 @@ func popSchedule(a *App) DailySchedule {
func requeueSchedule(a *App, s DailySchedule) { func requeueSchedule(a *App, s DailySchedule) {
if s.isSunrise || s.isSunset { if s.isSunrise || s.isSunset {
var nextSunTime carbon.Carbon var nextSunTime *carbon.Carbon
// "0s" is default value // "0s" is default value
if s.sunOffset != "0s" { if s.sunOffset != "0s" {
nextSunTime = getNextSunRiseOrSet(a, s.isSunrise, s.sunOffset) nextSunTime = getNextSunRiseOrSet(a, s.isSunrise, s.sunOffset)
@@ -223,9 +223,9 @@ func requeueSchedule(a *App, s DailySchedule) {
nextSunTime = getNextSunRiseOrSet(a, s.isSunrise) nextSunTime = getNextSunRiseOrSet(a, s.isSunrise)
} }
s.nextRunTime = nextSunTime.Carbon2Time() s.nextRunTime = nextSunTime.StdTime()
} else { } else {
s.nextRunTime = carbon.Time2Carbon(s.nextRunTime).AddDay().Carbon2Time() s.nextRunTime = carbon.CreateFromStdTime(s.nextRunTime).AddDay().StdTime()
} }
a.schedules.Put(Item{ a.schedules.Put(Item{

View File

@@ -1,8 +1,8 @@
package gomeassistant package gomeassistant
import ( import (
"github.com/Xevion/go-ha/internal/connect"
"github.com/Xevion/go-ha/internal/services" "github.com/Xevion/go-ha/internal/services"
ws "github.com/Xevion/go-ha/internal/websocket"
) )
type Service struct { type Service struct {
@@ -31,7 +31,7 @@ type Service struct {
ZWaveJS *services.ZWaveJS ZWaveJS *services.ZWaveJS
} }
func newService(conn *ws.WebsocketWriter) *Service { func newService(conn *connect.HAConnection) *Service {
return &Service{ return &Service{
AdaptiveLighting: services.BuildService[services.AdaptiveLighting](conn), AdaptiveLighting: services.BuildService[services.AdaptiveLighting](conn),
AlarmControlPanel: services.BuildService[services.AlarmControlPanel](conn), AlarmControlPanel: services.BuildService[services.AlarmControlPanel](conn),

View File

@@ -6,16 +6,17 @@ import (
"strings" "strings"
"time" "time"
"github.com/golang-module/carbon" "github.com/dromara/carbon/v2"
internal "github.com/Xevion/go-ha/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)
@@ -82,7 +83,7 @@ func (s *StateImpl) Get(entityId string) (EntityState, error) {
// ListEntities returns a list of all entities in Home Assistant. // ListEntities returns a list of all entities in Home Assistant.
// see rest documentation for more details: https://developers.home-assistant.io/docs/api/rest/#actions // see rest documentation for more details: https://developers.home-assistant.io/docs/api/rest/#actions
func (s *StateImpl) ListEntities() ([]EntityState, error) { func (s *StateImpl) ListEntities() ([]EntityState, error) {
resp, err := s.httpClient.States() resp, err := s.httpClient.GetStates()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -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

@@ -1,4 +1,4 @@
package gomeassistant package types
import "time" import "time"

View File