mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-06 15:15:14 -06:00
listeners working, sunrise/sunset working with string offset
This commit is contained in:
123
app.go
123
app.go
@@ -2,10 +2,12 @@ package gomeassistant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang-module/carbon"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/saml-dev/gome-assistant/internal"
|
||||
"github.com/saml-dev/gome-assistant/internal/http"
|
||||
@@ -24,9 +26,15 @@ type app struct {
|
||||
|
||||
schedules pq.PriorityQueue
|
||||
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
|
||||
you can use to register schedules and listeners.
|
||||
@@ -41,15 +49,14 @@ func NewApp(connString string) app {
|
||||
state := NewState(httpClient)
|
||||
|
||||
return app{
|
||||
conn: conn,
|
||||
ctx: ctx,
|
||||
ctxCancel: ctxCancel,
|
||||
httpClient: httpClient,
|
||||
service: service,
|
||||
state: state,
|
||||
schedules: pq.New(),
|
||||
entityListeners: map[string][]entityListener{},
|
||||
entityListenerIds: map[int64]entityListenerCallback{},
|
||||
conn: conn,
|
||||
ctx: ctx,
|
||||
ctxCancel: ctxCancel,
|
||||
httpClient: httpClient,
|
||||
service: service,
|
||||
state: state,
|
||||
schedules: pq.New(),
|
||||
entityListeners: map[string][]entityListener{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +78,9 @@ func (a *app) RegisterSchedule(s schedule) {
|
||||
// TODO: consider moving all time stuff to carbon?
|
||||
now := time.Now()
|
||||
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) // start at midnight today
|
||||
|
||||
// apply offset if set
|
||||
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
|
||||
@@ -88,45 +94,94 @@ func (a *app) RegisterSchedule(s schedule) {
|
||||
|
||||
func (a *app) RegisterEntityListener(el entityListener) {
|
||||
for _, entity := range el.entityIds {
|
||||
id := internal.GetId()
|
||||
subscribeTriggerMsg := subscribeMsg{
|
||||
Id: id,
|
||||
Type: "subscribe_trigger",
|
||||
Trigger: subscribeMsgTrigger{
|
||||
Platform: "state",
|
||||
EntityId: entity,
|
||||
},
|
||||
if elList, ok := a.entityListeners[entity]; ok {
|
||||
a.entityListeners[entity] = append(elList, el)
|
||||
} else {
|
||||
a.entityListeners[entity] = []entityListener{el}
|
||||
}
|
||||
if el.fromState != "" {
|
||||
subscribeTriggerMsg.Trigger.From = el.fromState
|
||||
}
|
||||
}
|
||||
|
||||
// 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])
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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() {
|
||||
// schedules
|
||||
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
|
||||
elChan := make(chan ws.ChanMsg)
|
||||
go ws.ListenWebsocket(a.conn, a.ctx, elChan)
|
||||
|
||||
log.Default().Println(a.entityListenerIds)
|
||||
var msg ws.ChanMsg
|
||||
for {
|
||||
msg = <-elChan
|
||||
log.Default().Println(string(msg.Raw))
|
||||
if callback, ok := a.entityListenerIds[msg.Id]; ok {
|
||||
log.Default().Println(msg, callback)
|
||||
if a.entityListenersId == msg.Id {
|
||||
go callEntityListeners(a, msg.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
ga "github.com/saml-dev/gome-assistant"
|
||||
)
|
||||
@@ -10,31 +9,25 @@ import (
|
||||
func main() {
|
||||
app := ga.NewApp("192.168.86.67:8123")
|
||||
defer app.Cleanup()
|
||||
s := ga.ScheduleBuilder().Call(lightsOut).Every(time.Second * 5).Build()
|
||||
s2 := ga.ScheduleBuilder().Call(cool).Every(time.Millisecond * 500).Build()
|
||||
s3 := ga.ScheduleBuilder().Call(c).Every(time.Minute * 1).Build()
|
||||
s := ga.ScheduleBuilder().Call(lightsOut).Daily().At(app.Sunset("1h")).Build()
|
||||
app.RegisterSchedule(s)
|
||||
app.RegisterSchedule(s2)
|
||||
app.RegisterSchedule(s3)
|
||||
|
||||
simpleListener := ga.EntityListenerBuilder().
|
||||
EntityIds("light.entryway_lamp").
|
||||
EntityIds("group.office_ceiling_lights").
|
||||
Call(listenerCB).
|
||||
// OnlyBetween(ga.TimeOfDay(22, 00), ga.TimeOfDay(07, 00)).
|
||||
// OnlyBetween("07:00", "14:00").
|
||||
Build()
|
||||
app.RegisterEntityListener(simpleListener)
|
||||
|
||||
app.Start()
|
||||
|
||||
log.Println(s)
|
||||
log.Println(s2)
|
||||
}
|
||||
|
||||
func lightsOut(service *ga.Service, state *ga.State) {
|
||||
// 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.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")
|
||||
// log.Default().Println("A")
|
||||
}
|
||||
@@ -48,4 +41,8 @@ func c(service *ga.Service, state *ga.State) {
|
||||
// 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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-module/carbon"
|
||||
i "github.com/saml-dev/gome-assistant/internal"
|
||||
)
|
||||
|
||||
type entityListener struct {
|
||||
@@ -10,12 +14,12 @@ type entityListener struct {
|
||||
callback entityListenerCallback
|
||||
fromState string
|
||||
toState string
|
||||
betweenStart time.Duration
|
||||
betweenEnd time.Duration
|
||||
betweenStart Time
|
||||
betweenEnd Time
|
||||
err error
|
||||
}
|
||||
|
||||
type entityListenerCallback func(*Service, *EntityData)
|
||||
type entityListenerCallback func(*Service, EntityData)
|
||||
|
||||
// TODO: use this to flatten json sent from HA for trigger event
|
||||
type EntityData struct {
|
||||
@@ -27,6 +31,27 @@ type EntityData struct {
|
||||
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 {
|
||||
Id int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
@@ -60,7 +85,7 @@ type subscribeMsgTrigger struct {
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
/* Builders */
|
||||
/* Methods */
|
||||
|
||||
func EntityListenerBuilder() elBuilder1 {
|
||||
return elBuilder1{entityListener{}}
|
||||
@@ -94,7 +119,7 @@ type elBuilder3 struct {
|
||||
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.betweenEnd = end
|
||||
return b
|
||||
@@ -113,3 +138,64 @@ func (b elBuilder3) ToState(s string) elBuilder3 {
|
||||
func (b elBuilder3) Build() 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
|
||||
|
||||
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/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
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/golang-module/carbon"
|
||||
)
|
||||
|
||||
var id int64 = 0
|
||||
|
||||
func GetId() int64 {
|
||||
id += 1
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/saml-dev/gome-assistant/internal"
|
||||
@@ -36,7 +35,6 @@ type BaseServiceRequest struct {
|
||||
|
||||
func NewBaseServiceRequest(entityId string) BaseServiceRequest {
|
||||
id := internal.GetId()
|
||||
log.Default().Println("service id", id)
|
||||
bsr := BaseServiceRequest{
|
||||
Id: fmt.Sprint(id),
|
||||
RequestType: "call_service",
|
||||
|
||||
@@ -20,7 +20,6 @@ type ChanMsg struct {
|
||||
|
||||
func ListenWebsocket(conn *websocket.Conn, ctx context.Context, c chan ChanMsg) {
|
||||
for {
|
||||
// log.Default().Println("reading message")
|
||||
bytes, _ := ReadMessage(conn, ctx)
|
||||
base := BaseMessage{}
|
||||
json.Unmarshal(bytes, &base)
|
||||
|
||||
87
schedule.go
87
schedule.go
@@ -2,66 +2,13 @@ package gomeassistant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"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 schedule struct {
|
||||
@@ -125,7 +72,7 @@ func ScheduleBuilder() scheduleBuilder {
|
||||
return scheduleBuilder{
|
||||
schedule{
|
||||
frequency: 0,
|
||||
offset: TimeOfDay(0, 0),
|
||||
offset: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -162,8 +109,10 @@ func (sb scheduleBuilderCall) Daily() scheduleBuilderDaily {
|
||||
return scheduleBuilderDaily(sb)
|
||||
}
|
||||
|
||||
func (sb scheduleBuilderDaily) At(t timeOfDay) scheduleBuilderEnd {
|
||||
sb.schedule.offset = convertTimeOfDayToActualOffset(t)
|
||||
// At takes a string 24hr format time like "15:30".
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -189,28 +138,6 @@ func getFunctionName(i interface{}) string {
|
||||
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
|
||||
func RunSchedules(a *app) {
|
||||
if a.schedules.Len() == 0 {
|
||||
|
||||
23
state.go
23
state.go
@@ -1,20 +1,35 @@
|
||||
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.
|
||||
type State struct {
|
||||
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 {
|
||||
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)
|
||||
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