-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add handler to notify when controller starts up
- This will allow detecting if the controller unexpectedly reboots which would cause interruption to lighting or watering
- Loading branch information
1 parent
28c2aec
commit b67dcb3
Showing
6 changed files
with
190 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package worker | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"log/slog" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/calvinmclean/automated-garden/garden-app/pkg" | ||
) | ||
|
||
const notificationClientIDLogField = "notification_client_id" | ||
|
||
func (w *Worker) sendNotificationForGarden(garden *pkg.Garden, title, message string, logger *slog.Logger) error { | ||
if garden.GetNotificationClientID() == "" { | ||
logger.Info("garden does not have notification client", "garden_id", garden.GetID()) | ||
return nil | ||
} | ||
logger = logger.With(notificationClientIDLogField, garden.GetNotificationClientID()) | ||
|
||
notificationClient, err := w.storageClient.NotificationClientConfigs.Get(context.Background(), garden.GetNotificationClientID()) | ||
if err != nil { | ||
return fmt.Errorf("error getting all notification clients: %w", err) | ||
} | ||
|
||
err = notificationClient.SendMessage(title, message) | ||
if err != nil { | ||
logger.Error("error sending message", "error", err) | ||
return err | ||
} | ||
|
||
logger.Info("successfully send notification") | ||
return nil | ||
} | ||
|
||
func (w *Worker) getGardenForTopic(topic string) (*pkg.Garden, error) { | ||
splitTopic := strings.SplitN(topic, "/", 2) | ||
if len(splitTopic) != 2 { | ||
return nil, fmt.Errorf("unexpected short topic: %q", topic) | ||
} | ||
|
||
topicPrefix := splitTopic[0] | ||
if topicPrefix == "" { | ||
return nil, errors.New("received message on empty topic") | ||
} | ||
|
||
garden, err := w.getGarden(topicPrefix) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting garden with topic-prefix %q: %w", topicPrefix, err) | ||
} | ||
return garden, nil | ||
} | ||
|
||
func (w *Worker) getGarden(topicPrefix string) (*pkg.Garden, error) { | ||
gardens, err := w.storageClient.Gardens.GetAll(context.Background(), nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting all gardens: %w", err) | ||
} | ||
var garden *pkg.Garden | ||
for _, g := range gardens { | ||
if g.TopicPrefix == topicPrefix { | ||
garden = g | ||
break | ||
} | ||
} | ||
if garden == nil { | ||
return nil, errors.New("no garden found") | ||
} | ||
|
||
return garden, nil | ||
} | ||
|
||
func (w *Worker) getZone(gardenID string, zonePosition int) (*pkg.Zone, error) { | ||
zones, err := w.storageClient.Zones.GetAll(context.Background(), nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting all zones: %w", err) | ||
} | ||
var zone *pkg.Zone | ||
for _, z := range zones { | ||
if z.GardenID.String() == gardenID && | ||
z.Position != nil && | ||
*z.Position == uint(zonePosition) { | ||
zone = z | ||
break | ||
} | ||
} | ||
if zone == nil { | ||
return nil, errors.New("no zone found") | ||
} | ||
|
||
return zone, nil | ||
} | ||
|
||
type parser struct { | ||
data []byte | ||
i int | ||
} | ||
|
||
func (p *parser) readNextInt() (int, error) { | ||
reading := false | ||
var n []byte | ||
for ; p.i < len(p.data); p.i++ { | ||
c := p.data[p.i] | ||
if c == ' ' { | ||
p.i++ | ||
break | ||
} | ||
if reading { | ||
n = append(n, c) | ||
continue | ||
} | ||
if c == '=' { | ||
reading = true | ||
continue | ||
} | ||
} | ||
|
||
result, err := strconv.Atoi(string(n)) | ||
if err != nil { | ||
return 0, fmt.Errorf("invalid integer: %w", err) | ||
} | ||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package worker | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
mqtt "github.com/eclipse/paho.mqtt.golang" | ||
) | ||
|
||
func (w *Worker) handleGardenStartupMessage(_ mqtt.Client, msg mqtt.Message) { | ||
err := w.doGardenStartupMessage(msg.Topic(), msg.Payload()) | ||
if err != nil { | ||
w.logger.With("topic", msg.Topic(), "error", err).Error("error handling message") | ||
} | ||
} | ||
|
||
func (w *Worker) doGardenStartupMessage(topic string, payload []byte) error { | ||
logger := w.logger.With("topic", topic) | ||
|
||
msg := parseStartupMessage(payload) | ||
if msg != "garden-controller setup complete" { | ||
logger.Warn("unexpected message from controller", "message", string(payload)) | ||
return nil | ||
} | ||
logger.Info("received message", "message", string(payload)) | ||
|
||
garden, err := w.getGardenForTopic(topic) | ||
if err != nil { | ||
return err | ||
} | ||
logger = logger.With("garden_id", garden.GetID()) | ||
logger.Info("found garden with topic-prefix") | ||
|
||
title := fmt.Sprintf("%s connected", garden.Name) | ||
return w.sendNotificationForGarden(garden, title, msg, logger) | ||
} | ||
|
||
func parseStartupMessage(msg []byte) string { | ||
return strings.TrimSuffix(strings.TrimPrefix(string(msg), "logs message=\""), "\"") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package worker | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestParseStartupMessage(t *testing.T) { | ||
input := "logs message=\"garden-controller setup complete\"" | ||
msg := parseStartupMessage([]byte(input)) | ||
require.Equal(t, "garden-controller setup complete", msg) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters