Skip to content
This repository was archived by the owner on Mar 22, 2025. It is now read-only.

Commit affc8d7

Browse files
committed
Added function for switches & getting websocketport
1 parent cf530de commit affc8d7

File tree

3 files changed

+116
-50
lines changed

3 files changed

+116
-50
lines changed

ZigBeeValve/thing.go

Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import (
1313
"net/http"
1414
"time"
1515

16-
"github.com/coder/websocket"
17-
// "github.com/coder/websocket/wsjson"
16+
"github.com/gorilla/websocket"
1817
"github.com/sdoque/mbaigo/components"
1918
"github.com/sdoque/mbaigo/forms"
2019
"github.com/sdoque/mbaigo/usecases"
@@ -45,6 +44,7 @@ type UnitAsset struct {
4544
deviceIndex string
4645
Period time.Duration `json:"period"`
4746
Setpt float64 `json:"setpoint"`
47+
Slaves []string `json:"slaves"`
4848
Apikey string `json:"APIkey"`
4949
}
5050

@@ -98,6 +98,7 @@ func initTemplate() components.UnitAsset {
9898
deviceIndex: "",
9999
Period: 10,
100100
Setpt: 20,
101+
Slaves: []string{},
101102
Apikey: "1234",
102103
ServicesMap: components.Services{
103104
setPointService.SubPath: &setPointService,
@@ -132,6 +133,7 @@ func newResource(uac UnitAsset, sys *components.System, servs []components.Servi
132133
deviceIndex: uac.deviceIndex,
133134
Period: uac.Period,
134135
Setpt: uac.Setpt,
136+
Slaves: uac.Slaves,
135137
Apikey: uac.Apikey,
136138
CervicesMap: components.Cervices{
137139
t.Name: t,
@@ -146,33 +148,26 @@ func newResource(uac UnitAsset, sys *components.System, servs []components.Servi
146148
ua.CervicesMap["temperature"].Details = components.MergeDetails(ua.Details, ref.Details)
147149

148150
return ua, func() {
149-
if ua.Model == "ZHAThermostat" {
150-
/*
151-
// Get correct index in list returned by api/sensors to make sure we always change correct device
152-
err := ua.getConnectedUnits("sensors")
153-
if err != nil {
154-
log.Println("Error occured during startup, while calling getConnectedUnits:", err)
155-
}
156-
*/
151+
if websocketport == "startup" {
152+
ua.getWebsocketPort()
153+
}
154+
switch ua.Model {
155+
case "ZHAThermostat":
157156
err := ua.sendSetPoint()
158157
if err != nil {
159158
log.Println("Error occured during startup, while calling sendSetPoint():", err)
160-
// TODO: Turn off system if this startup() fails?
161159
}
162-
} else if ua.Model == "Smart plug" {
163-
/*
164-
// Get correct index in list returned by api/lights to make sure we always change correct device
165-
err := ua.getConnectedUnits("lights")
166-
if err != nil {
167-
log.Println("Error occured during startup, while calling getConnectedUnits:", err)
168-
}
169-
*/
160+
case "Smart plug":
170161
// Not all smart plugs should be handled by the feedbackloop, some should be handled by a switch
171-
if ua.Period != 0 {
162+
if ua.Period > 0 {
172163
// start the unit assets feedbackloop, this fetches the temperature from ds18b20 and and toggles
173164
// between on/off depending on temperature in the room and a set temperature in the unitasset
174165
go ua.feedbackLoop(ua.Owner.Ctx)
175166
}
167+
case "ZHASwitch":
168+
// Starts listening to the websocket to find buttonevents (button presses) and then
169+
// turns its controlled devices on/off
170+
go ua.initWebsocketClient(ua.Owner.Ctx)
176171
}
177172
}
178173
}
@@ -301,7 +296,6 @@ func (ua *UnitAsset) toggleState(state bool) (err error) {
301296
}
302297

303298
// Useless function? Noticed uniqueid can be used as "id" to send requests instead of the index while testing, wasn't clear from documentation. Will need to test this more though
304-
// TODO: Rewrite this to instead get the websocketport.
305299
func (ua *UnitAsset) getConnectedUnits(unitType string) (err error) {
306300
// --- Get all devices ---
307301
apiURL := fmt.Sprintf("http://%s/api/%s/%s", gateway, ua.Apikey, unitType)
@@ -369,40 +363,112 @@ func sendRequest(req *http.Request) (err error) {
369363
// Port 443, can be found by curl -v "http://localhost:8080/api/[apikey]/config", and getting the "websocketport". Will make a function to automatically get this port
370364
// https://dresden-elektronik.github.io/deconz-rest-doc/endpoints/websocket/
371365
// https://stackoverflow.com/questions/32745716/i-need-to-connect-to-an-existing-websocket-server-using-go-lang
372-
// https://pkg.go.dev/github.com/coder/websocket#Dial
373-
// https://pkg.go.dev/github.com/coder/websocket#Conn.Reader
374-
375-
// Not sure if this will work, still a work in progress.
376-
func initWebsocketClient(ctx context.Context) (err error) {
377-
fmt.Println("Starting Client")
378-
ws, _, err := websocket.Dial(ctx, "ws://localhost:443", nil) // Start listening to websocket
379-
defer ws.CloseNow() // Make sure connection is closed when returning from function
366+
// https://github.com/gorilla/websocket
367+
368+
var websocketport = "startup"
369+
370+
type eventJSON struct {
371+
State struct {
372+
Buttonevent int `json:"buttonevent"`
373+
} `json:"state"`
374+
UniqueID string `json:"uniqueid"`
375+
}
376+
377+
func (ua *UnitAsset) getWebsocketPort() (err error) {
378+
// --- Get config ---
379+
apiURL := fmt.Sprintf("http://%s/api/%s/config", gateway, ua.Apikey)
380+
// Create a new request (Get)
381+
// Put data into buffer
382+
req, err := http.NewRequest(http.MethodGet, apiURL, nil) // Put request is made
383+
req.Header.Set("Content-Type", "application/json") // Make sure it's JSON
384+
// Send the request
385+
resp, err := http.DefaultClient.Do(req) // Perform the http request
380386
if err != nil {
381-
fmt.Printf("Dial failed: %s\n", err)
382387
return err
383388
}
384-
_, body, err := ws.Reader(ctx) // Start reading from connection, returned body will be used to get buttonevents
389+
defer resp.Body.Close()
390+
resBody, err := io.ReadAll(resp.Body) // Read the response body, and check for errors/bad statuscodes
385391
if err != nil {
386-
log.Println("Error while reading from websocket:", err)
387-
return
392+
return err
388393
}
389-
data, err := io.ReadAll(body)
390-
if err != nil {
391-
log.Println("Error while converthing from io.Reader to []byte:", err)
392-
return
394+
if resp.StatusCode > 299 {
395+
return errStatusCode
393396
}
394-
var bodyString map[string]interface{}
395-
err = json.Unmarshal(data, &bodyString) // Unmarshal body into json, easier to be able to point to specific data with ".example"
397+
// How to access maps inside of maps below!
398+
// https://stackoverflow.com/questions/28806951/accessing-nested-map-of-type-mapstringinterface-in-golang
399+
var configMap map[string]interface{}
400+
err = json.Unmarshal([]byte(resBody), &configMap)
396401
if err != nil {
397-
log.Println("Error while unmarshaling data:", err)
398-
return
402+
return err
399403
}
400-
log.Println("Read from websocket:", bodyString)
401-
err = ws.Close(websocket.StatusNormalClosure, "No longer need to listen to websocket")
404+
websocketport = fmt.Sprint(configMap["websocketport"])
405+
// log.Println(configMap["websocketport"])
406+
return
407+
}
408+
409+
func (ua *UnitAsset) toggleSlaves(currentState bool) (err error) {
410+
for i := range ua.Slaves {
411+
// Add check if current slave is smart plug or a light, like philips hue
412+
413+
// API call to toggle smart plug on/off, PUT call should be sent to URL/api/apikey/lights/sensor_id/config
414+
apiURL := fmt.Sprintf("http://%s/api/%s/lights/%s/state", gateway, ua.Apikey, ua.Slaves[i])
415+
// Create http friendly payload
416+
s := fmt.Sprintf(`{"on":%t}`, currentState) // Create payload
417+
req, err := createRequest(s, apiURL)
418+
if err != nil {
419+
return err
420+
}
421+
sendRequest(req)
422+
}
423+
return err
424+
}
425+
426+
// Function starts listening to a websocket, every message received through websocket is read, and checked if it's what we're looking for
427+
// The uniqueid (UniqueID in systemconfig.json file) from the connected switch is used to filter out messages
428+
func (ua *UnitAsset) initWebsocketClient(ctx context.Context) error {
429+
dialer := websocket.Dialer{}
430+
wsURL := fmt.Sprintf("ws://localhost:%s", websocketport)
431+
conn, _, err := dialer.Dial(wsURL, nil)
402432
if err != nil {
403-
log.Println("Error while doing normal closure on websocket")
404-
return
433+
log.Fatal("Error occured while dialing:", err)
434+
}
435+
log.Println("Connected to websocket")
436+
defer conn.Close()
437+
currentState := false
438+
log.Println(currentState)
439+
for {
440+
select {
441+
case <-ctx.Done():
442+
return nil
443+
default:
444+
_, p, err := conn.ReadMessage()
445+
if err != nil {
446+
log.Println("Error occured while reading message:", err)
447+
return err
448+
}
449+
var message eventJSON
450+
//var message interface{}
451+
err = json.Unmarshal(p, &message)
452+
if err != nil {
453+
log.Println("Error unmarshalling message:", err)
454+
return err
455+
}
456+
457+
if message.UniqueID == ua.Uniqueid && (message.State.Buttonevent == 1002 || message.State.Buttonevent == 2002) {
458+
bEvent := message.State.Buttonevent
459+
if bEvent == 1002 {
460+
if currentState == true {
461+
currentState = false
462+
} else {
463+
currentState = true
464+
}
465+
ua.toggleSlaves(currentState)
466+
}
467+
if bEvent == 2002 {
468+
// Turn on the philips hue light
469+
// TODO: Find out how "long presses" works and if it can be used through websocket
470+
}
471+
}
472+
}
405473
}
406-
return
407-
// Have to do something fancy to make sure we update "connected" plugs/lights when Reader returns a body actually containing a buttonevent (something w/ channels?)
408474
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ go 1.23
44

55
require github.com/sdoque/mbaigo v0.0.0-20241019053937-4e5abf6a2df4
66

7-
require github.com/coder/websocket v1.8.12
7+
require github.com/gorilla/websocket v1.5.3
88

99
replace github.com/sdoque/mbaigo v0.0.0-20241019053937-4e5abf6a2df4 => github.com/lmas/mbaigo v0.0.0-20250123014631-ad869265483c

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
2-
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
1+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
2+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
33
github.com/lmas/mbaigo v0.0.0-20250123014631-ad869265483c h1:W+Jr5GQGKN4BiFOeAc6Uaq/Xc3k4/O5l+XzvsGlnlCQ=
44
github.com/lmas/mbaigo v0.0.0-20250123014631-ad869265483c/go.mod h1:Bfx9Uj0uiTT7BCzzlImMiRd6vMoPQdsZIHGMQOVjx80=

0 commit comments

Comments
 (0)