mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-06 05:15:15 -06:00
good progress yay:
- impl http client - create http client in App() - generic builder for Service.* - set Service on app to pass to callbacks later - impl State - set State on app to pass to callbacks later - change panic to log.Fatalln
This commit is contained in:
59
app.go
59
app.go
@@ -2,42 +2,51 @@ package gomeassistant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/saml-dev/gome-assistant/internal/http"
|
||||
"github.com/saml-dev/gome-assistant/internal/setup"
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
type app struct {
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
conn *websocket.Conn
|
||||
httpClient *http.HttpClient
|
||||
|
||||
service *Service
|
||||
state *State
|
||||
|
||||
schedules []schedule
|
||||
entityListeners []entityListener
|
||||
}
|
||||
|
||||
var (
|
||||
Sunrise hourMinute = hourMinute{1000, 0}
|
||||
Sunset hourMinute = hourMinute{1001, 0}
|
||||
)
|
||||
|
||||
/*
|
||||
App establishes the websocket connection and returns an object
|
||||
you can use to register schedules and listeners.
|
||||
*/
|
||||
func App(connString string) (app, error) {
|
||||
conn, ctx, ctxCancel, err := setup.SetupConnection(connString)
|
||||
if err != nil {
|
||||
return app{}, err
|
||||
}
|
||||
func App(connString string) app {
|
||||
token := os.Getenv("AUTH_TOKEN")
|
||||
conn, ctx, ctxCancel := setup.SetupConnection(connString)
|
||||
|
||||
httpClient := http.NewHttpClient(connString, token)
|
||||
|
||||
service := NewService(conn, ctx, httpClient)
|
||||
state := NewState(httpClient)
|
||||
|
||||
return app{
|
||||
conn: conn,
|
||||
ctx: ctx,
|
||||
ctxCancel: ctxCancel,
|
||||
httpClient: httpClient,
|
||||
service: service,
|
||||
state: state,
|
||||
schedules: []schedule{},
|
||||
entityListeners: []entityListener{},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) Cleanup() {
|
||||
@@ -48,27 +57,20 @@ func (a *app) Cleanup() {
|
||||
|
||||
func (a *app) RegisterSchedule(s schedule) {
|
||||
if s.err != nil {
|
||||
panic(s.err) // something wasn't configured properly when the schedule was built
|
||||
log.Fatalln(s.err) // something wasn't configured properly when the schedule was built
|
||||
}
|
||||
|
||||
if s.frequency == 0 {
|
||||
panic("A schedule must call either Daily() or Every() when built.")
|
||||
log.Fatalln("A schedule must call either Daily() or Every() when built.")
|
||||
}
|
||||
|
||||
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.int() != 0 {
|
||||
if s.offset.int() == Sunrise.int() {
|
||||
// TODO: same as sunset w/ sunrise
|
||||
} else if s.offset.int() == Sunset.int() {
|
||||
// TODO: add an http client (w/ token) to *app, use it to get state of sun.sun
|
||||
// to get next sunset time
|
||||
} else {
|
||||
startTime.Add(time.Hour * time.Duration(s.offset.Hour))
|
||||
startTime.Add(time.Minute * time.Duration(s.offset.Minute))
|
||||
}
|
||||
if s.offset.minutes() != 0 {
|
||||
startTime.Add(time.Hour * time.Duration(s.offset.hour))
|
||||
startTime.Add(time.Minute * time.Duration(s.offset.minute))
|
||||
}
|
||||
|
||||
// advance first scheduled time by frequency until it is in the future
|
||||
@@ -81,6 +83,11 @@ func (a *app) RegisterSchedule(s schedule) {
|
||||
}
|
||||
|
||||
func (a *app) Start() {
|
||||
// NOTE:should the prio queue and websocket listener both write to a channel or something?
|
||||
// then select from that and spawn new goroutine to call callback?
|
||||
|
||||
// TODO: loop through schedules and create heap priority queue
|
||||
|
||||
// TODO: figure out looping listening to messages for
|
||||
// listeners
|
||||
}
|
||||
|
||||
@@ -8,28 +8,24 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
app, err := ga.App("192.168.86.67:8123")
|
||||
app := ga.App("192.168.86.67:8123")
|
||||
defer app.Cleanup()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
s := ga.ScheduleBuilder().Call(lightsOut).Daily().At(ga.HourMinute(22, 00)).Build()
|
||||
s2 := ga.ScheduleBuilder().Call(lightsOut).Every(time.Hour*4 + time.Minute*30).Offset(ga.HourMinute(1, 0)).Build()
|
||||
s := ga.ScheduleBuilder().Call(lightsOut).Daily().At(ga.Sunset.Subtract(ga.TimeOfDay(0, 30))).Build()
|
||||
s2 := ga.ScheduleBuilder().Call(lightsOut).Every(time.Hour*4 + time.Minute*30).Offset(ga.TimeOfDay(1, 0)).Build()
|
||||
app.RegisterSchedule(s2)
|
||||
// err = app.Start()
|
||||
|
||||
simpleListener := ga.EntityListenerBuilder().
|
||||
EntityId("light.lights").
|
||||
Call(cool).
|
||||
OnlyBetween(ga.HourMinute(22, 00), ga.HourMinute(07, 00))
|
||||
OnlyBetween(ga.TimeOfDay(22, 00), ga.TimeOfDay(07, 00))
|
||||
fmt.Println(simpleListener)
|
||||
|
||||
fmt.Println(s, "\n", s2)
|
||||
}
|
||||
|
||||
func lightsOut(service ga.Service) {
|
||||
func lightsOut(service ga.Service, state ga.State) {
|
||||
// ga.TurnOff("light.all_lights")
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ type entityListener struct {
|
||||
callback entityListenerCallback
|
||||
fromState string
|
||||
toState string
|
||||
betweenStart hourMinute
|
||||
betweenEnd hourMinute
|
||||
betweenStart timeOfDay
|
||||
betweenEnd timeOfDay
|
||||
}
|
||||
|
||||
type entityListenerCallback func(Service, Data)
|
||||
|
||||
type Data struct{}
|
||||
|
||||
func (b elBuilder3) OnlyBetween(start hourMinute, end hourMinute) elBuilder3 {
|
||||
func (b elBuilder3) OnlyBetween(start timeOfDay, end timeOfDay) elBuilder3 {
|
||||
b.entityListener.betweenStart = start
|
||||
b.entityListener.betweenEnd = end
|
||||
return b
|
||||
|
||||
1
go.mod
1
go.mod
@@ -5,5 +5,6 @@ go 1.19
|
||||
require nhooyr.io/websocket v1.8.7
|
||||
|
||||
require (
|
||||
github.com/golang-module/carbon/v2 v2.1.9 // indirect
|
||||
github.com/klauspost/compress v1.10.3 // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -17,6 +17,8 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang-module/carbon/v2 v2.1.9 h1:OWkhYzTTPe+jPOUEL2JkvGwf6bKNQJoh4LVT1LUay80=
|
||||
github.com/golang-module/carbon/v2 v2.1.9/go.mod h1:NF5unWf838+pyRY0o+qZdIwBMkFf7w0hmLIguLiEpzU=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
@@ -47,6 +49,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
@@ -68,5 +71,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
|
||||
@@ -1,6 +1,83 @@
|
||||
// http is used to interact with the home assistant
|
||||
// REST API, currently only for retrieving state for
|
||||
// REST API. Currently only used to retrieve state for
|
||||
// a single entity_id
|
||||
package http
|
||||
|
||||
// TODO: impl http struct, should be initialized as part of App()
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HttpClient struct {
|
||||
url string
|
||||
token string
|
||||
}
|
||||
|
||||
func NewHttpClient(url, token string) *HttpClient {
|
||||
url = fmt.Sprintf("http://%s/api", url)
|
||||
return &HttpClient{url, token}
|
||||
}
|
||||
|
||||
func (c *HttpClient) GetState(entityId string) ([]byte, error) {
|
||||
resp, err := get(c.url+"/states/"+entityId, c.token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func get(url, token string) ([]byte, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("Error creating HTTP request: " + err.Error())
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+token)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.New("Error on response.\n[ERROR] -" + err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New("Error while reading the response bytes:" + err.Error())
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// func post(url string, token string, data any) ([]byte, error) {
|
||||
// postBody, err := json.Marshal(data)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// req, err := http.NewRequest("GET", url, bytes.NewBuffer(postBody))
|
||||
// if err != nil {
|
||||
// return nil, errors.New("Error building post request: " + err.Error())
|
||||
// }
|
||||
|
||||
// req.Header.Add("Authorization", "Bearer "+token)
|
||||
|
||||
// client := &http.Client{}
|
||||
// resp, err := client.Do(req)
|
||||
// if err != nil {
|
||||
// return nil, errors.New("Error in post response: " + err.Error())
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
// if resp.StatusCode == 401 {
|
||||
// log.Fatalln("ERROR: Auth token is invalid. Please double check it or create a new token in your Home Assistant profile")
|
||||
// }
|
||||
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// log.Fatalln(err)
|
||||
// }
|
||||
|
||||
// return body, nil
|
||||
// }
|
||||
|
||||
11
internal/services/builder.go
Normal file
11
internal/services/builder.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
func BuildService[T Light | HomeAssistant](conn *websocket.Conn, ctx context.Context) *T {
|
||||
return &T{conn: conn, ctx: ctx}
|
||||
}
|
||||
18
internal/services/homeassistant.go
Normal file
18
internal/services/homeassistant.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/saml-dev/gome-assistant/internal/http"
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
type HomeAssistant struct {
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
httpClient *http.HttpClient
|
||||
}
|
||||
|
||||
// TODO: design how much reuse I can get between request types. E.g.
|
||||
// only difference between light.turnon and homeassistant.turnon is
|
||||
// domain and extra data
|
||||
@@ -3,13 +3,15 @@ package services
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/saml-dev/gome-assistant/internal/http"
|
||||
"github.com/saml-dev/gome-assistant/internal/setup"
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
type Light struct {
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
httpClient *http.HttpClient
|
||||
}
|
||||
|
||||
type LightRequest struct {
|
||||
@@ -22,7 +24,21 @@ type LightRequest struct {
|
||||
} `json:"target"`
|
||||
}
|
||||
|
||||
func LightOnRequest(entityId string) LightRequest {
|
||||
/* Public API */
|
||||
|
||||
func (l Light) TurnOn(entityId string) {
|
||||
req := newLightOnRequest(entityId)
|
||||
setup.WriteMessage(req, l.conn, l.ctx)
|
||||
}
|
||||
|
||||
func (l Light) TurnOff(entityId string) {
|
||||
req := newLightOffRequest(entityId)
|
||||
setup.WriteMessage(req, l.conn, l.ctx)
|
||||
}
|
||||
|
||||
/* Internal */
|
||||
|
||||
func newLightOnRequest(entityId string) LightRequest {
|
||||
req := LightRequest{
|
||||
Id: 5,
|
||||
Type: "call_service",
|
||||
@@ -33,18 +49,8 @@ func LightOnRequest(entityId string) LightRequest {
|
||||
return req
|
||||
}
|
||||
|
||||
func LightOffRequest(entityId string) LightRequest {
|
||||
req := LightOnRequest(entityId)
|
||||
func newLightOffRequest(entityId string) LightRequest {
|
||||
req := newLightOnRequest(entityId)
|
||||
req.Service = "turn_off"
|
||||
return req
|
||||
}
|
||||
|
||||
func (l Light) TurnOn(entityId string) {
|
||||
req := LightOnRequest(entityId)
|
||||
setup.WriteMessage(req, l.conn, l.ctx)
|
||||
}
|
||||
|
||||
func (l Light) TurnOff(entityId string) {
|
||||
req := LightOffRequest(entityId)
|
||||
setup.WriteMessage(req, l.conn, l.ctx)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -39,40 +40,38 @@ func ReadMessage(conn *websocket.Conn, ctx context.Context) (string, error) {
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
func SetupConnection(connString string) (*websocket.Conn, context.Context, context.CancelFunc, error) {
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
func SetupConnection(connString string) (*websocket.Conn, context.Context, context.CancelFunc) {
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||
|
||||
// Init websocket connection
|
||||
conn, _, err := websocket.Dial(ctx, fmt.Sprintf("ws://%s/api/websocket", connString), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Failed to connect to websocket at ws://%s/api/websocket. Check IP address and port\n", connString)
|
||||
ctxCancel()
|
||||
return nil, nil, nil, err
|
||||
log.Fatalf("ERROR: Failed to connect to websocket at ws://%s/api/websocket. Check IP address and port\n", connString)
|
||||
}
|
||||
|
||||
// Read auth_required message
|
||||
_, err = ReadMessage(conn, ctx)
|
||||
if err != nil {
|
||||
ctxCancel()
|
||||
return nil, nil, nil, err
|
||||
log.Fatalln("Unknown error creating websocket client")
|
||||
}
|
||||
|
||||
// Send auth message
|
||||
err = SendAuthMessage(conn, ctx)
|
||||
if err != nil {
|
||||
ctxCancel()
|
||||
return nil, nil, nil, err
|
||||
log.Fatalln("Unknown error creating websocket client")
|
||||
}
|
||||
|
||||
// Verify auth message
|
||||
err = VerifyAuthResponse(conn, ctx)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR: Auth token is invalid. Please double check it or create a new token in your Home Assistant profile")
|
||||
ctxCancel()
|
||||
return nil, nil, nil, err
|
||||
log.Fatalln("ERROR: Auth token is invalid. Please double check it or create a new token in your Home Assistant profile")
|
||||
}
|
||||
|
||||
return conn, ctx, ctxCancel, err
|
||||
return conn, ctx, ctxCancel
|
||||
}
|
||||
|
||||
func SendAuthMessage(conn *websocket.Conn, ctx context.Context) error {
|
||||
|
||||
104
schedule.go
104
schedule.go
@@ -2,29 +2,76 @@ package gomeassistant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type hourMinute struct {
|
||||
Hour int
|
||||
Minute int
|
||||
type sunriseSunset struct {
|
||||
base timeOfDay
|
||||
addition timeOfDay
|
||||
subtraction timeOfDay
|
||||
}
|
||||
|
||||
func (hm hourMinute) int() int {
|
||||
return hm.Hour + hm.Minute
|
||||
var Sunrise *sunriseSunset = &sunriseSunset{
|
||||
base: TimeOfDay(0, 10000),
|
||||
addition: TimeOfDay(0, 0),
|
||||
subtraction: TimeOfDay(0, 0),
|
||||
}
|
||||
|
||||
func HourMinute(Hour, Minute int) hourMinute {
|
||||
return hourMinute{Hour, Minute}
|
||||
var Sunset *sunriseSunset = &sunriseSunset{
|
||||
base: TimeOfDay(0, 20000),
|
||||
addition: TimeOfDay(0, 0),
|
||||
subtraction: TimeOfDay(0, 0),
|
||||
}
|
||||
|
||||
func (hm hourMinute) String() string {
|
||||
return fmt.Sprintf("%02d:%02d", hm.Hour, hm.Minute)
|
||||
func (ss *sunriseSunset) Add(hm timeOfDay) *sunriseSunset {
|
||||
ss.addition = hm
|
||||
return ss
|
||||
}
|
||||
|
||||
type scheduleCallback func(Service)
|
||||
func (ss *sunriseSunset) Subtract(hm timeOfDay) *sunriseSunset {
|
||||
ss.subtraction = hm
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss *sunriseSunset) minutes() int {
|
||||
return ss.base.minute +
|
||||
(ss.addition.hour*60 + ss.addition.minute) -
|
||||
(ss.subtraction.hour*60 + ss.subtraction.minute)
|
||||
}
|
||||
|
||||
// HourMinute is used to express a time of day
|
||||
// but it shouldn't be used directly. Use
|
||||
// HourMinute(), Sunset(), or Sunrise() to
|
||||
// create one. Add() and Subtract() can be
|
||||
// called on Sunset and Sunrise to offset
|
||||
// from that time.
|
||||
type timeOfDay struct {
|
||||
hour int
|
||||
minute int
|
||||
}
|
||||
|
||||
type timeOfDayInterface interface {
|
||||
// Time represented as number of minutes
|
||||
// after midnight. E.g. 02:00 would be 120.
|
||||
minutes() int
|
||||
}
|
||||
|
||||
func (hm timeOfDay) minutes() int {
|
||||
return hm.hour*60 + hm.minute
|
||||
}
|
||||
|
||||
func TimeOfDay(Hour, Minute int) timeOfDay {
|
||||
return timeOfDay{Hour, Minute}
|
||||
}
|
||||
|
||||
func (hm timeOfDay) String() string {
|
||||
return fmt.Sprintf("%02d:%02d", hm.hour, hm.minute)
|
||||
}
|
||||
|
||||
type scheduleCallback func(Service, State)
|
||||
|
||||
type schedule struct {
|
||||
/*
|
||||
@@ -50,10 +97,10 @@ type schedule struct {
|
||||
offset: "0003"
|
||||
}
|
||||
*/
|
||||
offset hourMinute
|
||||
offset timeOfDay
|
||||
/*
|
||||
This will be set rather than returning an error to avoid checking err for nil on every schedule :)
|
||||
RegisterSchedule will panic if the error is set.
|
||||
RegisterSchedule will exit if the error is set.
|
||||
*/
|
||||
err error
|
||||
realStartTime time.Time
|
||||
@@ -83,7 +130,7 @@ func ScheduleBuilder() scheduleBuilder {
|
||||
return scheduleBuilder{
|
||||
schedule{
|
||||
frequency: 0,
|
||||
offset: hourMinute{0, 0},
|
||||
offset: timeOfDay{0, 0},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -113,18 +160,18 @@ func (sb scheduleBuilderCall) Daily() scheduleBuilderDaily {
|
||||
return scheduleBuilderDaily(sb)
|
||||
}
|
||||
|
||||
func (sb scheduleBuilderDaily) At(t hourMinute) scheduleBuilderEnd {
|
||||
sb.schedule.offset = t
|
||||
func (sb scheduleBuilderDaily) At(t timeOfDayInterface) scheduleBuilderEnd {
|
||||
sb.schedule.offset = convertTimeOfDayToActualOffset(t)
|
||||
return scheduleBuilderEnd(sb)
|
||||
}
|
||||
|
||||
func (sb scheduleBuilderCall) Every(d time.Duration) scheduleBuilderCustom {
|
||||
sb.schedule.frequency = d
|
||||
func (sb scheduleBuilderCall) Every(duration time.Duration) scheduleBuilderCustom {
|
||||
sb.schedule.frequency = duration
|
||||
return scheduleBuilderCustom(sb)
|
||||
}
|
||||
|
||||
func (sb scheduleBuilderCustom) Offset(o hourMinute) scheduleBuilderEnd {
|
||||
sb.schedule.offset = o
|
||||
func (sb scheduleBuilderCustom) Offset(t timeOfDay) scheduleBuilderEnd {
|
||||
sb.schedule.offset = t
|
||||
return scheduleBuilderEnd(sb)
|
||||
}
|
||||
|
||||
@@ -139,3 +186,22 @@ func (sb scheduleBuilderEnd) Build() schedule {
|
||||
func getFunctionName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
||||
|
||||
func convertTimeOfDayToActualOffset(t timeOfDayInterface) timeOfDay {
|
||||
if t.minutes() > 15000 {
|
||||
// TODO: same as below but w/ sunset
|
||||
return TimeOfDay(0, 0)
|
||||
} else if t.minutes() > 5000 {
|
||||
// TODO: use httpClient to get state of sun.sun
|
||||
// to get next sunrise time
|
||||
|
||||
// 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 t.minutes() >= 1440 {
|
||||
log.Fatalln("Offset (set via At() or Offset()) cannot be more than 1 day (23h59m)")
|
||||
}
|
||||
return TimeOfDay(0, t.minutes())
|
||||
}
|
||||
|
||||
18
service.go
18
service.go
@@ -3,21 +3,19 @@ package gomeassistant
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/saml-dev/gome-assistant/internal/http"
|
||||
"github.com/saml-dev/gome-assistant/internal/services"
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
HomeAssistant homeAssistant
|
||||
Light services.Light
|
||||
HomeAssistant *services.HomeAssistant
|
||||
Light *services.Light
|
||||
}
|
||||
|
||||
type homeAssistant struct {
|
||||
conn websocket.Conn
|
||||
ctx context.Context
|
||||
func NewService(conn *websocket.Conn, ctx context.Context, httpClient *http.HttpClient) *Service {
|
||||
return &Service{
|
||||
Light: services.BuildService[services.Light](conn, ctx),
|
||||
HomeAssistant: services.BuildService[services.HomeAssistant](conn, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
// type light struct {
|
||||
// conn websocket.Conn
|
||||
// ctx context.Context
|
||||
// }
|
||||
|
||||
20
state.go
Normal file
20
state.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package gomeassistant
|
||||
|
||||
import "github.com/saml-dev/gome-assistant/internal/http"
|
||||
|
||||
// State is used to retrieve state from Home Assistant.
|
||||
type State struct {
|
||||
httpClient *http.HttpClient
|
||||
}
|
||||
|
||||
func NewState(c *http.HttpClient) *State {
|
||||
return &State{httpClient: c}
|
||||
}
|
||||
|
||||
func (s *State) Get(entityId string) (string, error) {
|
||||
resp, err := s.httpClient.GetState(entityId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(resp), nil
|
||||
}
|
||||
Reference in New Issue
Block a user