mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-07 18:07:09 -06:00
listeners working, sunrise/sunset working with string offset
This commit is contained in:
107
app.go
107
app.go
@@ -2,10 +2,12 @@ package gomeassistant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-module/carbon"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/saml-dev/gome-assistant/internal"
|
"github.com/saml-dev/gome-assistant/internal"
|
||||||
"github.com/saml-dev/gome-assistant/internal/http"
|
"github.com/saml-dev/gome-assistant/internal/http"
|
||||||
@@ -24,9 +26,15 @@ type app struct {
|
|||||||
|
|
||||||
schedules pq.PriorityQueue
|
schedules pq.PriorityQueue
|
||||||
entityListeners map[string][]entityListener
|
entityListeners map[string][]entityListener
|
||||||
entityListenerIds map[int64]entityListenerCallback
|
entityListenersId int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Time is a 24-hr format string with hour and minute,
|
||||||
|
e.g. "07:00" for 7AM or "23:00" for 11PM.
|
||||||
|
*/
|
||||||
|
type Time string
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
@@ -49,7 +57,6 @@ func NewApp(connString string) app {
|
|||||||
state: state,
|
state: state,
|
||||||
schedules: pq.New(),
|
schedules: pq.New(),
|
||||||
entityListeners: map[string][]entityListener{},
|
entityListeners: map[string][]entityListener{},
|
||||||
entityListenerIds: map[int64]entityListenerCallback{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,10 +78,9 @@ func (a *app) RegisterSchedule(s schedule) {
|
|||||||
// TODO: consider moving all time stuff to carbon?
|
// TODO: consider moving all time stuff to carbon?
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) // start at midnight today
|
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) // start at midnight today
|
||||||
|
|
||||||
// apply offset if set
|
// apply offset if set
|
||||||
if s.offset.Minutes() > 0 {
|
if s.offset.Minutes() > 0 {
|
||||||
startTime.Add(s.offset)
|
startTime = startTime.Add(s.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance first scheduled time by frequency until it is in the future
|
// advance first scheduled time by frequency until it is in the future
|
||||||
@@ -88,45 +94,94 @@ func (a *app) RegisterSchedule(s schedule) {
|
|||||||
|
|
||||||
func (a *app) RegisterEntityListener(el entityListener) {
|
func (a *app) RegisterEntityListener(el entityListener) {
|
||||||
for _, entity := range el.entityIds {
|
for _, entity := range el.entityIds {
|
||||||
id := internal.GetId()
|
if elList, ok := a.entityListeners[entity]; ok {
|
||||||
subscribeTriggerMsg := subscribeMsg{
|
a.entityListeners[entity] = append(elList, el)
|
||||||
Id: id,
|
} else {
|
||||||
Type: "subscribe_trigger",
|
a.entityListeners[entity] = []entityListener{el}
|
||||||
Trigger: subscribeMsgTrigger{
|
|
||||||
Platform: "state",
|
|
||||||
EntityId: entity,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if el.fromState != "" {
|
|
||||||
subscribeTriggerMsg.Trigger.From = el.fromState
|
|
||||||
}
|
}
|
||||||
if el.toState != "" {
|
|
||||||
subscribeTriggerMsg.Trigger.To = el.toState
|
|
||||||
}
|
|
||||||
log.Default().Println(subscribeTriggerMsg)
|
|
||||||
ws.WriteMessage(subscribeTriggerMsg, a.conn, a.ctx)
|
|
||||||
msg, _ := ws.ReadMessage(a.conn, a.ctx)
|
|
||||||
log.Default().Println(string(msg))
|
|
||||||
a.entityListenerIds[id] = el.callback
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sunrise take an optional string that is passed to time.ParseDuration.
|
||||||
|
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
|
||||||
|
// for full list.
|
||||||
|
func (a *app) Sunrise(offset ...string) Time {
|
||||||
|
return getSunriseSunset(a, true, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sunset take an optional string that is passed to time.ParseDuration.
|
||||||
|
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
|
||||||
|
// for full list.
|
||||||
|
func (a *app) Sunset(offset ...string) Time {
|
||||||
|
return getSunriseSunset(a, false, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSunriseSunset(a *app, sunrise bool, offset []string) Time {
|
||||||
|
printString := "Sunset"
|
||||||
|
attrKey := "next_setting"
|
||||||
|
if sunrise {
|
||||||
|
printString = "Sunrise"
|
||||||
|
attrKey = "next_rising"
|
||||||
|
}
|
||||||
|
var t time.Duration
|
||||||
|
var err error
|
||||||
|
if len(offset) == 1 {
|
||||||
|
t, err = time.ParseDuration(offset[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not parse offset passed to %s: \"%s\"", printString, offset[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get next sunrise/sunset time from HA
|
||||||
|
state, err := a.state.Get("sun.sun")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Couldn't get sun.sun state from HA to calculate", printString)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSetOrRise := carbon.Parse(state.Attributes[attrKey].(string))
|
||||||
|
log.Default().Println(nextSetOrRise)
|
||||||
|
|
||||||
|
// add offset if set, this code works for negative values too
|
||||||
|
if t.Microseconds() != 0 {
|
||||||
|
nextSetOrRise = nextSetOrRise.AddMinutes(int(t.Minutes()))
|
||||||
|
log.Default().Println(nextSetOrRise)
|
||||||
|
}
|
||||||
|
|
||||||
|
return carbon2TimeString(nextSetOrRise)
|
||||||
|
}
|
||||||
|
|
||||||
|
func carbon2TimeString(c carbon.Carbon) Time {
|
||||||
|
return Time(fmt.Sprintf("%02d:%02d", c.Hour(), c.Minute()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type subEvent struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
EventType string `json:"event_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) Start() {
|
func (a *app) Start() {
|
||||||
// schedules
|
// schedules
|
||||||
go RunSchedules(a)
|
go RunSchedules(a)
|
||||||
|
|
||||||
|
// subscribe to state_changed events
|
||||||
|
id := internal.GetId()
|
||||||
|
e := subEvent{
|
||||||
|
Id: id,
|
||||||
|
Type: "subscribe_events",
|
||||||
|
EventType: "state_changed",
|
||||||
|
}
|
||||||
|
ws.WriteMessage(e, a.conn, a.ctx)
|
||||||
|
a.entityListenersId = id
|
||||||
|
|
||||||
// entity listeners
|
// entity listeners
|
||||||
elChan := make(chan ws.ChanMsg)
|
elChan := make(chan ws.ChanMsg)
|
||||||
go ws.ListenWebsocket(a.conn, a.ctx, elChan)
|
go ws.ListenWebsocket(a.conn, a.ctx, elChan)
|
||||||
|
|
||||||
log.Default().Println(a.entityListenerIds)
|
|
||||||
var msg ws.ChanMsg
|
var msg ws.ChanMsg
|
||||||
for {
|
for {
|
||||||
msg = <-elChan
|
msg = <-elChan
|
||||||
log.Default().Println(string(msg.Raw))
|
if a.entityListenersId == msg.Id {
|
||||||
if callback, ok := a.entityListenerIds[msg.Id]; ok {
|
go callEntityListeners(a, msg.Raw)
|
||||||
log.Default().Println(msg, callback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
|
||||||
|
|
||||||
ga "github.com/saml-dev/gome-assistant"
|
ga "github.com/saml-dev/gome-assistant"
|
||||||
)
|
)
|
||||||
@@ -10,31 +9,25 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
app := ga.NewApp("192.168.86.67:8123")
|
app := ga.NewApp("192.168.86.67:8123")
|
||||||
defer app.Cleanup()
|
defer app.Cleanup()
|
||||||
s := ga.ScheduleBuilder().Call(lightsOut).Every(time.Second * 5).Build()
|
s := ga.ScheduleBuilder().Call(lightsOut).Daily().At(app.Sunset("1h")).Build()
|
||||||
s2 := ga.ScheduleBuilder().Call(cool).Every(time.Millisecond * 500).Build()
|
|
||||||
s3 := ga.ScheduleBuilder().Call(c).Every(time.Minute * 1).Build()
|
|
||||||
app.RegisterSchedule(s)
|
app.RegisterSchedule(s)
|
||||||
app.RegisterSchedule(s2)
|
|
||||||
app.RegisterSchedule(s3)
|
|
||||||
|
|
||||||
simpleListener := ga.EntityListenerBuilder().
|
simpleListener := ga.EntityListenerBuilder().
|
||||||
EntityIds("light.entryway_lamp").
|
EntityIds("group.office_ceiling_lights").
|
||||||
Call(listenerCB).
|
Call(listenerCB).
|
||||||
// OnlyBetween(ga.TimeOfDay(22, 00), ga.TimeOfDay(07, 00)).
|
// OnlyBetween("07:00", "14:00").
|
||||||
Build()
|
Build()
|
||||||
app.RegisterEntityListener(simpleListener)
|
app.RegisterEntityListener(simpleListener)
|
||||||
|
|
||||||
app.Start()
|
app.Start()
|
||||||
|
|
||||||
log.Println(s)
|
|
||||||
log.Println(s2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func lightsOut(service *ga.Service, state *ga.State) {
|
func lightsOut(service *ga.Service, state *ga.State) {
|
||||||
// service.InputDatetime.Set("input_datetime.garage_last_triggered_ts", time.Now())
|
// service.InputDatetime.Set("input_datetime.garage_last_triggered_ts", time.Now())
|
||||||
// service.HomeAssistant.Toggle("group.living_room_lamps", map[string]any{"brightness_pct": 100})
|
// service.HomeAssistant.Toggle("group.living_room_lamps", map[string]any{"brightness_pct": 100})
|
||||||
// service.Light.Toggle("light.entryway_lamp", map[string]any{"brightness_pct": 100})
|
// service.Light.Toggle("light.entryway_lamp", map[string]any{"brightness_pct": 100})
|
||||||
service.HomeAssistant.Toggle("light.el_gato_key_lights")
|
service.HomeAssistant.Toggle("light.entryway_lamp")
|
||||||
|
log.Default().Println("running lightsOut")
|
||||||
// service.HomeAssistant.Toggle("light.entryway_lamp")
|
// service.HomeAssistant.Toggle("light.entryway_lamp")
|
||||||
// log.Default().Println("A")
|
// log.Default().Println("A")
|
||||||
}
|
}
|
||||||
@@ -48,4 +41,8 @@ func c(service *ga.Service, state *ga.State) {
|
|||||||
// log.Default().Println("C")
|
// log.Default().Println("C")
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenerCB(service *ga.Service, data *ga.EntityData) {}
|
func listenerCB(service *ga.Service, data ga.EntityData) {
|
||||||
|
log.Default().Println("hi katie")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: randomly placed, add .Throttle to Listener
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package gomeassistant
|
package gomeassistant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-module/carbon"
|
||||||
|
i "github.com/saml-dev/gome-assistant/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entityListener struct {
|
type entityListener struct {
|
||||||
@@ -10,12 +14,12 @@ type entityListener struct {
|
|||||||
callback entityListenerCallback
|
callback entityListenerCallback
|
||||||
fromState string
|
fromState string
|
||||||
toState string
|
toState string
|
||||||
betweenStart time.Duration
|
betweenStart Time
|
||||||
betweenEnd time.Duration
|
betweenEnd Time
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type entityListenerCallback func(*Service, *EntityData)
|
type entityListenerCallback func(*Service, EntityData)
|
||||||
|
|
||||||
// TODO: use this to flatten json sent from HA for trigger event
|
// TODO: use this to flatten json sent from HA for trigger event
|
||||||
type EntityData struct {
|
type EntityData struct {
|
||||||
@@ -27,6 +31,27 @@ type EntityData struct {
|
|||||||
LastChanged time.Time
|
LastChanged time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stateChangedMsg struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Event struct {
|
||||||
|
Data struct {
|
||||||
|
EntityID string `json:"entity_id"`
|
||||||
|
NewState msgState `json:"new_state"`
|
||||||
|
OldState msgState `json:"old_state"`
|
||||||
|
} `json:"data"`
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
Origin string `json:"origin"`
|
||||||
|
} `json:"event"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type msgState struct {
|
||||||
|
EntityID string `json:"entity_id"`
|
||||||
|
LastChanged time.Time `json:"last_changed"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Attributes map[string]any `json:"attributes"`
|
||||||
|
}
|
||||||
|
|
||||||
type triggerMsg struct {
|
type triggerMsg struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -60,7 +85,7 @@ type subscribeMsgTrigger struct {
|
|||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Builders */
|
/* Methods */
|
||||||
|
|
||||||
func EntityListenerBuilder() elBuilder1 {
|
func EntityListenerBuilder() elBuilder1 {
|
||||||
return elBuilder1{entityListener{}}
|
return elBuilder1{entityListener{}}
|
||||||
@@ -94,7 +119,7 @@ type elBuilder3 struct {
|
|||||||
entityListener
|
entityListener
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b elBuilder3) OnlyBetween(start time.Duration, end time.Duration) elBuilder3 {
|
func (b elBuilder3) OnlyBetween(start Time, end Time) elBuilder3 {
|
||||||
b.entityListener.betweenStart = start
|
b.entityListener.betweenStart = start
|
||||||
b.entityListener.betweenEnd = end
|
b.entityListener.betweenEnd = end
|
||||||
return b
|
return b
|
||||||
@@ -113,3 +138,64 @@ func (b elBuilder3) ToState(s string) elBuilder3 {
|
|||||||
func (b elBuilder3) Build() entityListener {
|
func (b elBuilder3) Build() entityListener {
|
||||||
return b.entityListener
|
return b.entityListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
func callEntityListeners(app *app, msgBytes []byte) {
|
||||||
|
msg := stateChangedMsg{}
|
||||||
|
json.Unmarshal(msgBytes, &msg)
|
||||||
|
data := msg.Event.Data
|
||||||
|
eid := data.EntityID
|
||||||
|
listeners, ok := app.entityListeners[eid]
|
||||||
|
if !ok {
|
||||||
|
// no listeners registered for this id
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range listeners {
|
||||||
|
// if betweenStart and betweenEnd both set, first account for midnight
|
||||||
|
// overlap, then only run if between those times.
|
||||||
|
if l.betweenStart != "" && l.betweenEnd != "" {
|
||||||
|
start := i.ParseTime(l.betweenStart)
|
||||||
|
end := i.ParseTime(l.betweenEnd)
|
||||||
|
|
||||||
|
// check for midnight overlap
|
||||||
|
if end.Lt(start) { // example turn on night lights when motion from 23:00 to 07:00
|
||||||
|
if end.IsPast() { // such as at 15:00, 22:00
|
||||||
|
end = end.AddDay()
|
||||||
|
} else {
|
||||||
|
start = start.SubDay() // such as at 03:00, 05:00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip callback if not inside the range
|
||||||
|
if !carbon.Now().BetweenIncludedStart(start, end) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise, just check if before/after the individual times
|
||||||
|
if l.betweenStart != "" && i.ParseTime(l.betweenStart).IsFuture() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if l.betweenEnd != "" && i.ParseTime(l.betweenEnd).IsPast() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't run callback if fromState or toState are set and don't match
|
||||||
|
if l.fromState != "" && l.fromState != data.OldState.State {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if l.toState != "" && l.toState != data.NewState.State {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entityData := EntityData{
|
||||||
|
TriggerEntityId: eid,
|
||||||
|
FromState: data.OldState.State,
|
||||||
|
FromAttributes: data.OldState.Attributes,
|
||||||
|
ToState: data.NewState.State,
|
||||||
|
ToAttributes: data.NewState.Attributes,
|
||||||
|
LastChanged: data.OldState.LastChanged,
|
||||||
|
}
|
||||||
|
l.callback(app.service, entityData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
13
go.mod
13
go.mod
@@ -2,4 +2,15 @@ module github.com/saml-dev/gome-assistant
|
|||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.5.0
|
require (
|
||||||
|
github.com/golang-module/carbon v1.6.9
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gobuffalo/envy v1.7.0 // indirect
|
||||||
|
github.com/gobuffalo/packd v0.3.0 // indirect
|
||||||
|
github.com/gobuffalo/packr v1.30.1 // indirect
|
||||||
|
github.com/joho/godotenv v1.3.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
74
go.sum
74
go.sum
@@ -1,2 +1,76 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
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/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||||
|
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||||
|
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||||
|
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||||
|
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||||
|
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.6.9 h1:fobotpw4zUvU1ZPXLOe6qn5l5zSbiKeJNJSIBeUHgJo=
|
||||||
|
github.com/golang-module/carbon v1.6.9/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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
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/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
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=
|
||||||
|
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-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
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/sync v0.0.0-20190423024810-112230192c58/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-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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-module/carbon"
|
||||||
|
)
|
||||||
|
|
||||||
var id int64 = 0
|
var id int64 = 0
|
||||||
|
|
||||||
func GetId() int64 {
|
func GetId() int64 {
|
||||||
id += 1
|
id += 1
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseTime[T ~string](s T) carbon.Carbon {
|
||||||
|
t, err := time.Parse("15:04", string(s))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to parse time string \"%s\"; format must be HH:MM.", s)
|
||||||
|
}
|
||||||
|
return carbon.Now().StartOfDay().SetHour(t.Hour()).SetMinute(t.Minute())
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/saml-dev/gome-assistant/internal"
|
"github.com/saml-dev/gome-assistant/internal"
|
||||||
@@ -36,7 +35,6 @@ type BaseServiceRequest struct {
|
|||||||
|
|
||||||
func NewBaseServiceRequest(entityId string) BaseServiceRequest {
|
func NewBaseServiceRequest(entityId string) BaseServiceRequest {
|
||||||
id := internal.GetId()
|
id := internal.GetId()
|
||||||
log.Default().Println("service id", id)
|
|
||||||
bsr := BaseServiceRequest{
|
bsr := BaseServiceRequest{
|
||||||
Id: fmt.Sprint(id),
|
Id: fmt.Sprint(id),
|
||||||
RequestType: "call_service",
|
RequestType: "call_service",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ type ChanMsg struct {
|
|||||||
|
|
||||||
func ListenWebsocket(conn *websocket.Conn, ctx context.Context, c chan ChanMsg) {
|
func ListenWebsocket(conn *websocket.Conn, ctx context.Context, c chan ChanMsg) {
|
||||||
for {
|
for {
|
||||||
// log.Default().Println("reading message")
|
|
||||||
bytes, _ := ReadMessage(conn, ctx)
|
bytes, _ := ReadMessage(conn, ctx)
|
||||||
base := BaseMessage{}
|
base := BaseMessage{}
|
||||||
json.Unmarshal(bytes, &base)
|
json.Unmarshal(bytes, &base)
|
||||||
|
|||||||
87
schedule.go
87
schedule.go
@@ -2,66 +2,13 @@ package gomeassistant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/saml-dev/gome-assistant/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sunriseSunset struct {
|
|
||||||
base time.Duration
|
|
||||||
addition time.Duration
|
|
||||||
subtraction time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sunrise() *sunriseSunset {
|
|
||||||
return &sunriseSunset{
|
|
||||||
base: TimeOfDay(0, 10000),
|
|
||||||
addition: TimeOfDay(0, 0),
|
|
||||||
subtraction: TimeOfDay(0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sunset() *sunriseSunset {
|
|
||||||
return &sunriseSunset{
|
|
||||||
base: TimeOfDay(0, 20000),
|
|
||||||
addition: TimeOfDay(0, 0),
|
|
||||||
subtraction: TimeOfDay(0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *sunriseSunset) Add(hm time.Duration) *sunriseSunset {
|
|
||||||
ss.addition = hm
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *sunriseSunset) Subtract(hm time.Duration) *sunriseSunset {
|
|
||||||
ss.subtraction = hm
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *sunriseSunset) Minutes() float64 {
|
|
||||||
return ss.base.Minutes() + ss.addition.Minutes() - ss.subtraction.Minutes()
|
|
||||||
}
|
|
||||||
|
|
||||||
type timeOfDay interface {
|
|
||||||
// Time represented as number of Minutes
|
|
||||||
// after midnight. E.g. 02:00 would be 120.
|
|
||||||
Minutes() float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeOfDay is a helper function to easily represent
|
|
||||||
// a time of day as a time.Duration since midnight.
|
|
||||||
func TimeOfDay(hour, minute int) time.Duration {
|
|
||||||
return time.Hour*time.Duration(hour) + time.Minute*time.Duration(minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration is a wrapper for TimeOfDay that makes
|
|
||||||
// semantic sense when used with Every()
|
|
||||||
func Duration(hour, minute int) time.Duration {
|
|
||||||
return TimeOfDay(hour, minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
type scheduleCallback func(*Service, *State)
|
type scheduleCallback func(*Service, *State)
|
||||||
|
|
||||||
type schedule struct {
|
type schedule struct {
|
||||||
@@ -125,7 +72,7 @@ func ScheduleBuilder() scheduleBuilder {
|
|||||||
return scheduleBuilder{
|
return scheduleBuilder{
|
||||||
schedule{
|
schedule{
|
||||||
frequency: 0,
|
frequency: 0,
|
||||||
offset: TimeOfDay(0, 0),
|
offset: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,8 +109,10 @@ func (sb scheduleBuilderCall) Daily() scheduleBuilderDaily {
|
|||||||
return scheduleBuilderDaily(sb)
|
return scheduleBuilderDaily(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb scheduleBuilderDaily) At(t timeOfDay) scheduleBuilderEnd {
|
// At takes a string 24hr format time like "15:30".
|
||||||
sb.schedule.offset = convertTimeOfDayToActualOffset(t)
|
func (sb scheduleBuilderDaily) At(s Time) scheduleBuilderEnd {
|
||||||
|
t := internal.ParseTime(s)
|
||||||
|
sb.schedule.offset = time.Duration(t.Hour())*time.Hour + time.Duration(t.Minute())*time.Minute
|
||||||
return scheduleBuilderEnd(sb)
|
return scheduleBuilderEnd(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,28 +138,6 @@ func getFunctionName(i interface{}) string {
|
|||||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertTimeOfDayToActualOffset(t timeOfDay) time.Duration {
|
|
||||||
mins := t.Minutes()
|
|
||||||
if mins > 15000 {
|
|
||||||
// TODO: same as below but w/ sunset
|
|
||||||
// don't forget to subtract 20000 here
|
|
||||||
return TimeOfDay(0, 0)
|
|
||||||
} else if mins > 5000 {
|
|
||||||
// TODO: use httpClient to get state of sun.sun
|
|
||||||
// to get next sunrise time
|
|
||||||
// don't forget to subtract 10000 here to get +- from sunrise that user requested
|
|
||||||
|
|
||||||
// retrieve next sunrise time
|
|
||||||
|
|
||||||
// use carbon.Parse() to create time.Time of that time
|
|
||||||
|
|
||||||
// return Time() of that many hours and minutes to set offset from midnight
|
|
||||||
} else if mins >= 1440 {
|
|
||||||
log.Fatalln("Offset (set via At() or Offset()) cannot be more than 1 day (23h59m)")
|
|
||||||
}
|
|
||||||
return TimeOfDay(0, int(mins))
|
|
||||||
}
|
|
||||||
|
|
||||||
// app.Start() functions
|
// app.Start() functions
|
||||||
func RunSchedules(a *app) {
|
func RunSchedules(a *app) {
|
||||||
if a.schedules.Len() == 0 {
|
if a.schedules.Len() == 0 {
|
||||||
|
|||||||
23
state.go
23
state.go
@@ -1,20 +1,35 @@
|
|||||||
package gomeassistant
|
package gomeassistant
|
||||||
|
|
||||||
import "github.com/saml-dev/gome-assistant/internal/http"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/saml-dev/gome-assistant/internal/http"
|
||||||
|
)
|
||||||
|
|
||||||
// State is used to retrieve state from Home Assistant.
|
// State is used to retrieve state from Home Assistant.
|
||||||
type State struct {
|
type State struct {
|
||||||
httpClient *http.HttpClient
|
httpClient *http.HttpClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EntityState struct {
|
||||||
|
EntityID string `json:"entity_id"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Attributes map[string]any `json:"attributes"`
|
||||||
|
LastChanged time.Time `json:"last_changed"`
|
||||||
|
LastUpdated time.Time `json:"last_updated"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewState(c *http.HttpClient) *State {
|
func NewState(c *http.HttpClient) *State {
|
||||||
return &State{httpClient: c}
|
return &State{httpClient: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Get(entityId string) (string, error) {
|
func (s *State) Get(entityId string) (EntityState, error) {
|
||||||
resp, err := s.httpClient.GetState(entityId)
|
resp, err := s.httpClient.GetState(entityId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return EntityState{}, err
|
||||||
}
|
}
|
||||||
return string(resp), nil
|
es := EntityState{}
|
||||||
|
json.Unmarshal(resp, &es)
|
||||||
|
return es, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user