From 2597711973dae8ceb9eecb7e0853ad35b93045c5 Mon Sep 17 00:00:00 2001 From: Sam Lewis Date: Sun, 9 Oct 2022 23:35:01 -0400 Subject: [PATCH] websocket initialization done in App --- app.go | 147 +++++++++---------------------------- cmd/main/testing.go | 11 +-- gomeassistant.go | 53 ------------- internal/network/setup.go | 51 ------------- internal/services/light.go | 8 +- internal/setup/setup.go | 106 ++++++++++++++++++++++++++ schedule.go | 41 ++++++++--- 7 files changed, 181 insertions(+), 236 deletions(-) delete mode 100644 gomeassistant.go delete mode 100644 internal/network/setup.go create mode 100644 internal/setup/setup.go diff --git a/app.go b/app.go index 2fc5eda..7cc8caf 100644 --- a/app.go +++ b/app.go @@ -2,22 +2,48 @@ package gomeassistant import ( "context" + "fmt" "time" + + "github.com/saml-dev/gome-assistant/internal/setup" + "nhooyr.io/websocket" ) type app struct { - url string - ctx context.Context - schedules []Schedule - listeners []Listener + ctx context.Context + ctxCancel context.CancelFunc + conn *websocket.Conn + schedules []schedule + entityListeners []entityListener } -func App(url string) (app, error) { - // TODO: connect to websocket, return error if fails - return app{url: url}, nil +/* +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 + } + + return app{ + conn: conn, + ctx: ctx, + ctxCancel: ctxCancel, + schedules: []schedule{}, + entityListeners: []entityListener{}, + }, nil } -func (a app) RegisterSchedule(s Schedule) { +func (a *app) Cleanup() { + if a.ctxCancel != nil { + a.ctxCancel() + } +} + +func (a *app) RegisterSchedule(s schedule) { + fmt.Println(a.schedules) if s.err != nil { panic(s.err) // something wasn't configured properly when the schedule was built } @@ -40,7 +66,8 @@ func (a app) RegisterSchedule(s Schedule) { startTime = startTime.Add(s.frequency) } - // TODO: save realStartTime or _startTime to s, add to list of Schedules + s.realStartTime = startTime + a.schedules = append(a.schedules, s) } const ( @@ -50,105 +77,3 @@ const ( Hourly time.Duration = time.Hour Minutely time.Duration = time.Minute ) - -type Listener struct { -} - -const ( - _0000 string = "0000" - _0015 string = "0015" - _0030 string = "0030" - _0045 string = "0045" - _0100 string = "0100" - _0115 string = "0115" - _0130 string = "0130" - _0145 string = "0145" - _0200 string = "0200" - _0215 string = "0215" - _0230 string = "0230" - _0245 string = "0245" - _0300 string = "0300" - _0315 string = "0315" - _0330 string = "0330" - _0345 string = "0345" - _0400 string = "0400" - _0415 string = "0415" - _0430 string = "0430" - _0445 string = "0445" - _0500 string = "0500" - _0515 string = "0515" - _0530 string = "0530" - _0545 string = "0545" - _0600 string = "0600" - _0615 string = "0615" - _0630 string = "0630" - _0645 string = "0645" - _0700 string = "0700" - _0715 string = "0715" - _0730 string = "0730" - _0745 string = "0745" - _0800 string = "0800" - _0815 string = "0815" - _0830 string = "0830" - _0845 string = "0845" - _0900 string = "0900" - _0915 string = "0915" - _0930 string = "0930" - _0945 string = "0945" - _1000 string = "1000" - _1015 string = "1015" - _1030 string = "1030" - _1045 string = "1045" - _1100 string = "1100" - _1115 string = "1115" - _1130 string = "1130" - _1145 string = "1145" - _1200 string = "1200" - _1215 string = "1215" - _1230 string = "1230" - _1245 string = "1245" - _1300 string = "1300" - _1315 string = "1315" - _1330 string = "1330" - _1345 string = "1345" - _1400 string = "1400" - _1415 string = "1415" - _1430 string = "1430" - _1445 string = "1445" - _1500 string = "1500" - _1515 string = "1515" - _1530 string = "1530" - _1545 string = "1545" - _1600 string = "1600" - _1615 string = "1615" - _1630 string = "1630" - _1645 string = "1645" - _1700 string = "1700" - _1715 string = "1715" - _1730 string = "1730" - _1745 string = "1745" - _1800 string = "1800" - _1815 string = "1815" - _1830 string = "1830" - _1845 string = "1845" - _1900 string = "1900" - _1915 string = "1915" - _1930 string = "1930" - _1945 string = "1945" - _2000 string = "2000" - _2015 string = "2015" - _2030 string = "2030" - _2045 string = "2045" - _2100 string = "2100" - _2115 string = "2115" - _2130 string = "2130" - _2145 string = "2145" - _2200 string = "2200" - _2215 string = "2215" - _2230 string = "2230" - _2245 string = "2245" - _2300 string = "2300" - _2315 string = "2315" - _2330 string = "2330" - _2345 string = "2345" -) diff --git a/cmd/main/testing.go b/cmd/main/testing.go index 26e5e0c..3377c1e 100644 --- a/cmd/main/testing.go +++ b/cmd/main/testing.go @@ -9,11 +9,14 @@ import ( func main() { app, err := ga.App("192.168.86.67:8123") + defer app.Cleanup() if err != nil { - panic(err) + 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).Offset(ga.HourMinute(1, 0)).Build() + s2 := ga.ScheduleBuilder().Call(lightsOut).Every(time.Hour*4 + time.Minute*30).Offset(ga.HourMinute(1, 0)).Build() app.RegisterSchedule(s2) // err = app.Start() @@ -23,9 +26,7 @@ func main() { OnlyBetween(ga.HourMinute(22, 00), ga.HourMinute(07, 00)) fmt.Println(simpleListener) - // p := ga.NewPersonBuilder().Lives().At("lskdjflskf").WithPostalCode("kdjf").Works().As("SWE") - - fmt.Println(s, s2) + fmt.Println(s, "\n", s2) } func lightsOut(service ga.Service) { diff --git a/gomeassistant.go b/gomeassistant.go deleted file mode 100644 index d34999e..0000000 --- a/gomeassistant.go +++ /dev/null @@ -1,53 +0,0 @@ -package gomeassistant - -import ( - "context" - "fmt" - "time" - - "github.com/saml-dev/gome-assistant/internal/network" - "nhooyr.io/websocket" -) - -var ctx, ctxCancel = context.WithTimeout(context.Background(), time.Second*5) - -var conn, _, err = websocket.Dial(ctx, "ws://192.168.86.67:8123/api/websocket", nil) - -func main() { - // sched := Schedule{ - // RunEvery: Daily, - // } - defer ctxCancel() - if err != nil { - panic(err) - } - defer conn.Close(websocket.StatusInternalError, "the sky is falling") - // _, _, err = c.Reader(ctx) - // if err != nil { - // fmt.Println("err1") - // fmt.Println(err) - // } - msg, err := network.ReadMessage() - if err != nil { - fmt.Println("err2") - fmt.Println(err) - } - fmt.Println(string(msg)) - - err = network.SendAuthMessage() - if err != nil { - fmt.Println(err) - } - - msg, err = network.ReadMessage() - if err != nil { - fmt.Println(err) - } - fmt.Println(string(msg)) - err = network.WriteMessage(NewLightOnRequest("group.living_room_lamps")) - if err != nil { - fmt.Println(err) - } - - conn.Close(websocket.StatusNormalClosure, "") -} diff --git a/internal/network/setup.go b/internal/network/setup.go deleted file mode 100644 index eba6f8b..0000000 --- a/internal/network/setup.go +++ /dev/null @@ -1,51 +0,0 @@ -package network - -import ( - "context" - "encoding/json" - "fmt" - "os" - - "nhooyr.io/websocket" -) - -type AuthMessage struct { - MsgType string `json:"type"` - AccessToken string `json:"access_token"` -} - -func SendAuthMessage(conn websocket.Conn, ctx context.Context) error { - token := os.Getenv("AUTH_TOKEN") - msgJson, err := json.Marshal(AuthMessage{MsgType: "auth", AccessToken: token}) - if err != nil { - return err - } - err = conn.Write(ctx, websocket.MessageText, msgJson) - if err != nil { - return err - } - return nil -} - -func WriteMessage[T any](msg T, conn websocket.Conn, ctx context.Context) error { - msgJson, err := json.Marshal(msg) - fmt.Println(string(msgJson)) - if err != nil { - return err - } - - err = conn.Write(ctx, websocket.MessageText, msgJson) - if err != nil { - return err - } - - return nil -} - -func ReadMessage(conn websocket.Conn, ctx context.Context) (string, error) { - _, msg, err := conn.Read(ctx) - if err != nil { - return "", err - } - return string(msg), nil -} diff --git a/internal/services/light.go b/internal/services/light.go index 2800a27..e3b2d22 100644 --- a/internal/services/light.go +++ b/internal/services/light.go @@ -3,12 +3,12 @@ package services import ( "context" - "github.com/saml-dev/gome-assistant/internal/network" + "github.com/saml-dev/gome-assistant/internal/setup" "nhooyr.io/websocket" ) type Light struct { - conn websocket.Conn + conn *websocket.Conn ctx context.Context } @@ -41,10 +41,10 @@ func LightOffRequest(entityId string) LightRequest { func (l Light) TurnOn(entityId string) { req := LightOnRequest(entityId) - network.WriteMessage(req, l.conn, l.ctx) + setup.WriteMessage(req, l.conn, l.ctx) } func (l Light) TurnOff(entityId string) { req := LightOffRequest(entityId) - network.WriteMessage(req, l.conn, l.ctx) + setup.WriteMessage(req, l.conn, l.ctx) } diff --git a/internal/setup/setup.go b/internal/setup/setup.go new file mode 100644 index 0000000..6e38a51 --- /dev/null +++ b/internal/setup/setup.go @@ -0,0 +1,106 @@ +package setup + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "time" + + "nhooyr.io/websocket" +) + +type AuthMessage struct { + MsgType string `json:"type"` + AccessToken string `json:"access_token"` +} + +func WriteMessage[T any](msg T, conn *websocket.Conn, ctx context.Context) error { + msgJson, err := json.Marshal(msg) + fmt.Println(string(msgJson)) + if err != nil { + return err + } + + err = conn.Write(ctx, websocket.MessageText, msgJson) + if err != nil { + return err + } + + return nil +} + +func ReadMessage(conn *websocket.Conn, ctx context.Context) (string, error) { + _, msg, err := conn.Read(ctx) + if err != nil { + return "", err + } + return string(msg), nil +} + +func SetupConnection(connString string) (*websocket.Conn, context.Context, context.CancelFunc, error) { + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*5) + + // 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 + } + + // Read auth_required message + _, err = ReadMessage(conn, ctx) + if err != nil { + ctxCancel() + return nil, nil, nil, err + } + + // Send auth message + err = SendAuthMessage(conn, ctx) + if err != nil { + ctxCancel() + return nil, nil, nil, err + } + + // 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 + } + + return conn, ctx, ctxCancel, err +} + +func SendAuthMessage(conn *websocket.Conn, ctx context.Context) error { + token := os.Getenv("AUTH_TOKEN") + err := WriteMessage(AuthMessage{MsgType: "auth", AccessToken: token}, conn, ctx) + if err != nil { + return err + } + return nil +} + +type authResponse struct { + MsgType string `json:"type"` + Message string `json:"message"` +} + +func VerifyAuthResponse(conn *websocket.Conn, ctx context.Context) error { + _, msg, err := conn.Read(ctx) + if err != nil { + return err + } + + var authResp authResponse + json.Unmarshal(msg, &authResp) + fmt.Println(authResp) + if authResp.MsgType != "auth_ok" { + return errors.New("invalid auth token") + } + + return nil +} diff --git a/schedule.go b/schedule.go index 96a4c10..3ea7ccf 100644 --- a/schedule.go +++ b/schedule.go @@ -16,9 +16,13 @@ func HourMinute(Hour, Minute int) hourMinute { return hourMinute{Hour, Minute} } +func (hm hourMinute) String() string { + return fmt.Sprintf("%02d:%02d", hm.Hour, hm.Minute) +} + type scheduleCallback func(Service) -type Schedule struct { +type schedule struct { /* frequency is a time.Duration representing how often you want to run your function. @@ -47,35 +51,48 @@ type Schedule struct { 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. */ - err error + err error + realStartTime time.Time } type scheduleBuilder struct { - schedule Schedule + schedule schedule } type scheduleBuilderCall struct { - schedule Schedule + schedule schedule } type scheduleBuilderDaily struct { - schedule Schedule + schedule schedule } type scheduleBuilderCustom struct { - schedule Schedule + schedule schedule } type scheduleBuilderEnd struct { - schedule Schedule + schedule schedule } func ScheduleBuilder() scheduleBuilder { - return scheduleBuilder{Schedule{}} + return scheduleBuilder{schedule{}} } -func (s Schedule) String() string { - return fmt.Sprintf("Run %q every %v with offset %s", getFunctionName(s.callback), s.frequency, s.offset) +func (s schedule) String() string { + return fmt.Sprintf("Run %q %s %s", + getFunctionName(s.callback), + frequencyToString(s.frequency), + s.offset, + ) +} + +func frequencyToString(d time.Duration) string { + fmt.Println(d.Hours(), d.Minutes(), d.Seconds()) + if d.Hours() == 24 { + return "daily at" + } + return "every " + d.String() + " with offset" } func (sb scheduleBuilder) Call(callback scheduleCallback) scheduleBuilderCall { @@ -103,11 +120,11 @@ func (sb scheduleBuilderCustom) Offset(o hourMinute) scheduleBuilderEnd { return scheduleBuilderEnd(sb) } -func (sb scheduleBuilderCustom) Build() Schedule { +func (sb scheduleBuilderCustom) Build() schedule { return sb.schedule } -func (sb scheduleBuilderEnd) Build() Schedule { +func (sb scheduleBuilderEnd) Build() schedule { return sb.schedule }