Skip to content

Commit 3872ec9

Browse files
committed
feat(service): Adds HomeAssistant service
1 parent 999c6ff commit 3872ec9

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed

service/homeassistant/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# HomeAssistant service
2+
3+
[HomeAssistant](https://www.home-assistant.io) is an application that handles home automation and integration
4+
5+
## Usage
6+
7+
[HomeAssistant webhook trigger](https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger)
8+
9+
```go
10+
// Create a HomeAssistant service
11+
haService := homeassistant.New()
12+
13+
// Add webhook
14+
haService.AddWebhook("https://url-to-home-assistant", "<webhook_id>", "<http_method>")
15+
16+
// Tell our notifier to use the service.
17+
notify.UseServices(haService)
18+
19+
// Send a test message.
20+
_ = notify.Send(
21+
context.Background(),
22+
"Subject/Title",
23+
"The actual message - Hello, you awesome gophers! :)",
24+
)
25+
```
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package homeassistant
2+
3+
import (
4+
"context"
5+
stdhttp "net/http"
6+
"strings"
7+
8+
"github.com/nikoksr/notify/service/http"
9+
)
10+
11+
// HomeAssistant struct holds necessary data to communicate with the HomeAssistant API.
12+
type HomeAssistant struct {
13+
httpClient http.Service
14+
}
15+
16+
// New returns a new instance of a HomeAssistant notification service.
17+
func New() (*HomeAssistant, error) {
18+
h := HomeAssistant{
19+
httpClient: *http.New(),
20+
}
21+
return &h, nil
22+
}
23+
24+
// AddWebhook takes HomeAssistant automation webhooks and adds them. The Send method will send to all these
25+
// baseUrl should be the home assistant base url for example https://your-home-assistant:8123
26+
// hookId should be what was set at the trigger of the webhook
27+
// method should be the method selected at the trigger of the webhook
28+
// For more information read HomeAssistant documentation at:
29+
// - https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger
30+
func (h *HomeAssistant) AddWebhook(baseUrl string, hookId string, method string) {
31+
baseUrl = strings.TrimRight(baseUrl, "/")
32+
u := baseUrl + "/api/webhook/" + hookId
33+
34+
// From example:
35+
// curl -X POST -d 'key=value&key2=value2' https://your-home-assistant:8123/api/webhook/some_hook_id
36+
37+
hook := &http.Webhook{
38+
URL: u,
39+
Header: stdhttp.Header{},
40+
ContentType: "application/json",
41+
Method: strings.ToUpper(method),
42+
BuildPayload: func(subject, message string) (payload any) {
43+
dataMap := make(map[string]string)
44+
dataMap["subject"] = subject
45+
dataMap["message"] = message
46+
47+
return dataMap
48+
},
49+
}
50+
51+
h.httpClient.AddReceivers(hook)
52+
}
53+
54+
// Send takes a subject and a message and sends them to all previously set webhooks
55+
func (h HomeAssistant) Send(ctx context.Context, subject, message string) error {
56+
// TODO: should setup mqtt automation integration as well
57+
// https://www.home-assistant.io/docs/automation/trigger/#mqtt-trigger
58+
59+
return h.httpClient.Send(ctx, subject, message)
60+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package homeassistant
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"strings"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestNew(t *testing.T) {
15+
t.Parallel()
16+
17+
s1, err := New()
18+
assert.NotNil(t, s1, "service should not be nil")
19+
assert.Nil(t, err, "error should not exist")
20+
21+
s2, err := New()
22+
assert.NotNil(t, s2, "service should not be nil")
23+
assert.Equal(t, s1, s2, "services should be equal")
24+
assert.Nil(t, err, "error should not exist")
25+
}
26+
27+
func TestService_Send(t *testing.T) {
28+
t.Parallel()
29+
30+
ctx, cancel := context.WithCancel(context.Background())
31+
defer cancel()
32+
33+
hookId := "very_long_hook_id_to_test_out"
34+
subject := "test subject"
35+
message := "test message"
36+
37+
sent := false
38+
svr := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
39+
if !strings.Contains(r.URL.Path, "/api/webhook/"+hookId) {
40+
return
41+
}
42+
43+
var m map[string]string
44+
err := json.NewDecoder(r.Body).Decode(&m)
45+
if err != nil || m == nil {
46+
return
47+
}
48+
49+
if s, ok := m["subject"]; !ok || s != subject {
50+
return
51+
}
52+
53+
if s, ok := m["message"]; !ok || s != message {
54+
return
55+
}
56+
57+
sent = true
58+
}))
59+
defer svr.Close()
60+
61+
// Create service with local server as receiver
62+
service, _ := New()
63+
service.AddWebhook(svr.URL, hookId, "POST")
64+
65+
// Sending this notification should work without any issues
66+
err := service.Send(ctx, subject, message)
67+
assert.NoError(t, err, "error should be nil")
68+
assert.True(t, sent, "message was not received")
69+
}

0 commit comments

Comments
 (0)