Merge pull request #7 from LubosD/feature/avoid-panic-on-disconnect

Avoid panic on disconnect, provide connection errors
This commit is contained in:
saml-dev
2023-03-12 14:40:49 -04:00
committed by GitHub
5 changed files with 46 additions and 15 deletions

View File

@@ -40,7 +40,7 @@ The general flow is
import ga "saml.dev/gome-assistant" import ga "saml.dev/gome-assistant"
// replace with IP and port of your Home Assistant installation // replace with IP and port of your Home Assistant installation
app := ga.NewApp("0.0.0.0:8123") app, err := ga.NewApp("0.0.0.0:8123")
// create automations here (see next sections) // create automations here (see next sections)

20
app.go
View File

@@ -15,6 +15,9 @@ import (
ws "saml.dev/gome-assistant/internal/websocket" ws "saml.dev/gome-assistant/internal/websocket"
) )
// Returned by NewApp() if authentication fails
var ErrInvalidToken = ws.ErrInvalidToken
type App struct { type App struct {
ctx context.Context ctx context.Context
ctxCancel context.CancelFunc ctxCancel context.CancelFunc
@@ -77,7 +80,7 @@ type NewAppRequest struct {
NewApp establishes the websocket connection and returns an object NewApp establishes the websocket connection and returns an object
you can use to register schedules and listeners. you can use to register schedules and listeners.
*/ */
func NewApp(request NewAppRequest) *App { func NewApp(request NewAppRequest) (*App, error) {
if request.IpAddress == "" || request.HAAuthToken == "" || request.HomeZoneEntityId == "" { if request.IpAddress == "" || request.HAAuthToken == "" || request.HomeZoneEntityId == "" {
log.Fatalln("IpAddress, HAAuthToken, and HomeZoneEntityId are all required arguments in NewAppRequest.") log.Fatalln("IpAddress, HAAuthToken, and HomeZoneEntityId are all required arguments in NewAppRequest.")
} }
@@ -85,7 +88,11 @@ func NewApp(request NewAppRequest) *App {
if port == "" { if port == "" {
port = "8123" port = "8123"
} }
conn, ctx, ctxCancel := ws.SetupConnection(request.IpAddress, port, request.HAAuthToken) conn, ctx, ctxCancel, err := ws.SetupConnection(request.IpAddress, port, request.HAAuthToken)
if conn == nil {
return nil, err
}
httpClient := http.NewHttpClient(request.IpAddress, port, request.HAAuthToken) httpClient := http.NewHttpClient(request.IpAddress, port, request.HAAuthToken)
@@ -105,7 +112,7 @@ func NewApp(request NewAppRequest) *App {
intervals: pq.New(), intervals: pq.New(),
entityListeners: map[string][]*EntityListener{}, entityListeners: map[string][]*EntityListener{},
eventListeners: map[string][]*EventListener{}, eventListeners: map[string][]*EventListener{},
} }, nil
} }
func (a *App) Cleanup() { func (a *App) Cleanup() {
@@ -263,9 +270,12 @@ func (a *App) Start() {
// entity listeners and event listeners // entity listeners and event 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)
var msg ws.ChanMsg
for { for {
msg = <-elChan msg, ok := <-elChan
if !ok {
break
}
if a.entityListenersId == msg.Id { if a.entityListenersId == msg.Id {
go callEntityListeners(a, msg.Raw) go callEntityListeners(a, msg.Raw)
} else { } else {

View File

@@ -10,11 +10,16 @@ import (
) )
func main() { func main() {
app := ga.NewApp(ga.NewAppRequest{ app, err := ga.NewApp(ga.NewAppRequest{
IpAddress: "192.168.86.67", // Replace with your Home Assistant IP Address IpAddress: "192.168.86.67", // Replace with your Home Assistant IP Address
HAAuthToken: os.Getenv("HA_AUTH_TOKEN"), HAAuthToken: os.Getenv("HA_AUTH_TOKEN"),
HomeZoneEntityId: "zone.home", HomeZoneEntityId: "zone.home",
}) })
if err != nil {
log.Fatalln("Error connecting to HASS:", err)
}
defer app.Cleanup() defer app.Cleanup()
pantryDoor := ga. pantryDoor := ga.

View File

@@ -3,6 +3,7 @@ package websocket
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"log"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@@ -20,7 +21,14 @@ 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 {
bytes, _ := ReadMessage(conn, ctx) bytes, err := ReadMessage(conn, ctx)
if err != nil {
log.Default().Println("Error reading from websocket:", err)
close(c)
break
}
base := BaseMessage{} base := BaseMessage{}
json.Unmarshal(bytes, &base) json.Unmarshal(bytes, &base)
chanMsg := ChanMsg{ chanMsg := ChanMsg{

View File

@@ -17,6 +17,10 @@ import (
i "saml.dev/gome-assistant/internal" i "saml.dev/gome-assistant/internal"
) )
var (
ErrInvalidToken = errors.New("invalid authentication token")
)
type AuthMessage struct { type AuthMessage struct {
MsgType string `json:"type"` MsgType string `json:"type"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
@@ -47,7 +51,7 @@ func ReadMessage(conn *websocket.Conn, ctx context.Context) ([]byte, error) {
return msg, nil return msg, nil
} }
func SetupConnection(ip, port, authToken string) (*websocket.Conn, context.Context, context.CancelFunc) { func SetupConnection(ip, port, authToken string) (*websocket.Conn, context.Context, context.CancelFunc, error) {
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*3) ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*3)
// Init websocket connection // Init websocket connection
@@ -55,31 +59,35 @@ func SetupConnection(ip, port, authToken string) (*websocket.Conn, context.Conte
conn, _, err := dialer.DialContext(ctx, fmt.Sprintf("ws://%s:%s/api/websocket", ip, port), nil) conn, _, err := dialer.DialContext(ctx, fmt.Sprintf("ws://%s:%s/api/websocket", ip, port), nil)
if err != nil { if err != nil {
ctxCancel() ctxCancel()
log.Fatalf("ERROR: Failed to connect to websocket at ws://%s:%s/api/websocket. Check IP address and port\n", ip, port) log.Printf("ERROR: Failed to connect to websocket at ws://%s:%s/api/websocket. Check IP address and port\n", ip, port)
return nil, nil, nil, err
} }
// Read auth_required message // Read auth_required message
_, err = ReadMessage(conn, ctx) _, err = ReadMessage(conn, ctx)
if err != nil { if err != nil {
ctxCancel() ctxCancel()
log.Fatalf("Unknown error creating websocket client\n") log.Printf("Unknown error creating websocket client\n")
return nil, nil, nil, err
} }
// Send auth message // Send auth message
err = SendAuthMessage(conn, ctx, authToken) err = SendAuthMessage(conn, ctx, authToken)
if err != nil { if err != nil {
ctxCancel() ctxCancel()
log.Fatalf("Unknown error creating websocket client\n") log.Printf("Unknown error creating websocket client\n")
return nil, nil, nil, err
} }
// Verify auth message was successful // Verify auth message was successful
err = VerifyAuthResponse(conn, ctx) err = VerifyAuthResponse(conn, ctx)
if err != nil { if err != nil {
ctxCancel() ctxCancel()
log.Fatalf("ERROR: Auth token is invalid. Please double check it or create a new token in your Home Assistant profile\n") log.Printf("ERROR: Auth token is invalid. Please double check it or create a new token in your Home Assistant profile\n")
return nil, nil, nil, err
} }
return conn, ctx, ctxCancel return conn, ctx, ctxCancel, nil
} }
func SendAuthMessage(conn *websocket.Conn, ctx context.Context, token string) error { func SendAuthMessage(conn *websocket.Conn, ctx context.Context, token string) error {
@@ -105,7 +113,7 @@ func VerifyAuthResponse(conn *websocket.Conn, ctx context.Context) error {
json.Unmarshal(msg, &authResp) json.Unmarshal(msg, &authResp)
// log.Println(authResp.MsgType) // log.Println(authResp.MsgType)
if authResp.MsgType != "auth_ok" { if authResp.MsgType != "auth_ok" {
return errors.New("invalid auth token") return ErrInvalidToken
} }
return nil return nil