summaryrefslogtreecommitdiff
path: root/shelly.go
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2026-03-25 21:37:20 +0100
committerxengineering <me@xengineering.eu>2026-03-25 21:37:20 +0100
commit4bc67b734dc8c90dd4679877e8825da32e67b7eb (patch)
treefc4b97bdb6b91caff22b771bb9d8f5ca64791772 /shelly.go
parent7afbc98e6d715eef8809beb9793ccf5096104e26 (diff)
parent6001997a66c4c4b12e9d8b0853fef0fc0ff14768 (diff)
downloadsia-server-4bc67b734dc8c90dd4679877e8825da32e67b7eb.tar
sia-server-4bc67b734dc8c90dd4679877e8825da32e67b7eb.tar.zst
sia-server-4bc67b734dc8c90dd4679877e8825da32e67b7eb.zip
Merge branch 'shelly'
This adds basic support for Shelly 2PM Gen3 devices.
Diffstat (limited to 'shelly.go')
-rw-r--r--shelly.go90
1 files changed, 90 insertions, 0 deletions
diff --git a/shelly.go b/shelly.go
new file mode 100644
index 0000000..508b393
--- /dev/null
+++ b/shelly.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "strings"
+
+ "github.com/gorilla/websocket"
+)
+
+func ShellyRun(config ShellyConfigs, route Route) {
+ for message := range route.Destination {
+ ip, command, err := parseMessage(config, message)
+ if err != nil {
+ log.Println(err)
+ continue
+ }
+
+ err = shellySendCommand(ip, command)
+ if err != nil {
+ log.Printf("Could not send command '%s' to %v: %v", command, ip, err)
+ }
+ }
+}
+
+func parseMessage(config ShellyConfigs, m MQTTMessage) (ip *net.IP, command string, err error) {
+ elements := strings.Split(m.Topic, "/")
+
+ if len(elements) != 3 {
+ return nil, "", fmt.Errorf(
+ "Expected three topic levels but got %d in '%s'.",
+ len(elements), m.Topic,
+ )
+ }
+
+ if elements[0] != "cover" || elements[2] != "movement" {
+ return nil, "", fmt.Errorf("Expected cover/<id>/movement but got: %s", m.Topic)
+ }
+
+ switch string(m.Payload) {
+ case "extend":
+ command = "Cover.Close"
+ case "retract":
+ command = "Cover.Open"
+ case "stop":
+ command = "Cover.Stop"
+ default:
+ return nil, "", fmt.Errorf("Invalid payload '%s'.", m.Payload)
+ }
+
+ id := elements[1]
+
+ for _, c := range config {
+ if c.ID == id {
+ ip := net.ParseIP(c.IP)
+ return &ip, command, nil
+ }
+ }
+
+ return nil, "", fmt.Errorf("Got message for unknown cover '%s'", id)
+}
+
+func shellySendCommand(ip *net.IP, command string) error {
+ template := `
+{
+ "jsonrpc":"2.0",
+ "id": 1,
+ "src":"user_1",
+ "method":"%s",
+ "params": {
+ "id":0
+ }
+}
+`
+ message := fmt.Appendf([]byte{}, template, command)
+
+ c, _, err := websocket.DefaultDialer.Dial("ws://" + ip.String() + "/rpc", nil)
+ if err != nil {
+ return fmt.Errorf("Could not connect to Shelly: %w", err)
+ }
+ defer c.Close()
+
+ err = c.WriteMessage(websocket.TextMessage, message)
+ if err != nil {
+ return fmt.Errorf("Failed writing websocket message to Shelly: %w", err)
+ }
+
+ return nil
+}