"github.com/go-logr/logr"
"unpiege.net/rabbit_go.git/helper"
+ "unpiege.net/rabbit_go.git/mattersocket"
)
var (
// config superstructure
type RGBConfigYaml struct {
- RGBRelays []RGBRelay `yaml:"RGBRelays"`
- ESPRelays []ESPRelay `yaml:"ESPRelays"`
- RGBZigs []RGBZig `yaml:"RGBZigs"`
- RGBPicos []RGBPico `yaml:"RGBPicos"`
- Switches []Switch `yaml:"Switches"`
+ RGBMQRelays []RGBMQRelay `yaml:"RGBMQRelays"`
+ RGBMatterRelays []RGBMatterRelay `yaml:"RGBMatterRelays"`
+ ESPRelays []ESPRelay `yaml:"ESPRelays"`
+ RGBZigs []RGBZig `yaml:"RGBZigs"`
+ RGBPicos []RGBPico `yaml:"RGBPicos"`
+ Switches []Switch `yaml:"Switches"`
}
type Switch struct {
// interface defs for relays
type IRGBRelay interface {
- Init(sendChannel chan helper.RabbitSend)
setLastMotion()
addSwitch(newSwitch *Switch)
setPWM(red, green, blue float64)
Dummy bool `yaml:"Dummy"`
NightOnly bool `yaml:"NightOnly"`
logger logr.Logger
- sendChannel chan helper.RabbitSend
switches []*Switch `yaml:"Switches"`
relayData IRelayData
startupFinishTime time.Time
startupComplete bool
}
-/*func NewRGBRelay(Location string, DimmingTimeout int, sendChannel chan helper.RabbitSend) *RGBRelay {
- retval := RGBRelay{Location: Location, DimmingTimeout: DimmingTimeout}
- retval.sendChannel = sendChannel
- retval.UpdateStaleness = 10
- return &retval
-}*/
-
-func (relay *RGBRelay) Init(sendChannel chan helper.RabbitSend) {
+func (relay *RGBRelay) Init() {
relay.logger = logger.WithValues("relay", relay)
- relay.sendChannel = sendChannel
// keep checking until we exceed this value, then we're ready to change some states
relay.startupFinishTime = time.Now().UTC().Add(time.Duration(300) * time.Second)
relay.relayData = RelayData{parent: relay}
}
}
-func (relay *RGBRelay) setPWM(red, green, blue float64) {
- if relay.shouldUpdate() && !relay.Dummy {
- relay.sendUpdate(red, green, blue)
- }
-}
-
-func (relay *RGBRelay) sendUpdate(red, green, blue float64) {
- relay.logger.V(3).Info("Sending update for relay")
- // now, override for any switch that's active
- for _, curSwitch := range relay.switches {
- isActive, _ := curSwitch.getColor()
- if isActive {
- red, green, blue = 1.0, 1.0, 1.0
- break
- }
- }
-
- rgbData, routingKey := relay.relayData.getRelayData(red, green, blue)
- relay.logger.V(2).Info("Sending data to rabbitmq", "Data", rgbData, "routingKey", routingKey)
-
- sendItem := helper.RabbitSend{Data: rgbData, RoutingKey: routingKey, EmptySource: true}
- relay.sendChannel <- sendItem
-}
-
func (relay *RGBRelay) shouldUpdate() bool {
// start with no update needed
var retval bool = false
return retval
}
+type RGBMatterRelay struct {
+ RGBRelay `yaml:"RGBRelay"`
+ matterSocket *mattersocket.MatterSocket
+ Serials []string `yaml:"Serials"`
+}
+
+func (relay *RGBMatterRelay) Init(matterSocket *mattersocket.MatterSocket) {
+ relay.RGBRelay.Init()
+ relay.logger = logger.WithValues("relay", relay)
+ relay.matterSocket = matterSocket
+}
+
+func (relay *RGBMatterRelay) setPWM(red, green, blue float64) {
+ if relay.shouldUpdate() && !relay.Dummy {
+ relay.sendUpdate(red, green, blue)
+ }
+}
+
+func (relay *RGBMatterRelay) getDestination() string {
+ return fmt.Sprintf("Matter: %s", relay.Location)
+}
+
+func (relay *RGBMatterRelay) sendUpdate(red, green, blue float64) {
+ relay.logger.V(3).Info("Sending update for relay")
+ // now, override for any switch that's active
+ for _, curSwitch := range relay.switches {
+ isActive, _ := curSwitch.getColor()
+ if isActive {
+ red, green, blue = 1.0, 1.0, 1.0
+ break
+ }
+ }
+
+ // just reuse the same function, and ignore the routing key as the second return
+ rgbData, _ := relay.relayData.getRelayData(red, green, blue)
+
+ relay.logger.V(2).Info("Sending PWM data to matter", "Serials", relay.Serials,
+ "red", rgbData["red"], "green", rgbData["green"], "blue", rgbData["blue"])
+ for _, serial := range relay.Serials {
+ relay.matterSocket.NodePWM(serial, rgbData["red"].(float64), rgbData["green"].(float64), rgbData["blue"].(float64))
+ }
+}
+
+type RGBMQRelay struct {
+ RGBRelay `yaml:"RGBRelay"`
+ sendChannel chan helper.RabbitSend
+}
+
+func (relay *RGBMQRelay) Init(sendChannel chan helper.RabbitSend) {
+ relay.RGBRelay.Init()
+ relay.logger = logger.WithValues("relay", relay)
+ relay.sendChannel = sendChannel
+}
+
+func (relay *RGBMQRelay) setPWM(red, green, blue float64) {
+ if relay.shouldUpdate() && !relay.Dummy {
+ relay.sendUpdate(red, green, blue)
+ }
+}
+
+func (relay *RGBMQRelay) sendUpdate(red, green, blue float64) {
+ relay.logger.V(3).Info("Sending update for relay")
+ // now, override for any switch that's active
+ for _, curSwitch := range relay.switches {
+ isActive, _ := curSwitch.getColor()
+ if isActive {
+ red, green, blue = 1.0, 1.0, 1.0
+ break
+ }
+ }
+
+ rgbData, routingKey := relay.relayData.getRelayData(red, green, blue)
+ relay.logger.V(2).Info("Sending data to rabbitmq", "Data", rgbData, "routingKey", routingKey)
+
+ sendItem := helper.RabbitSend{Data: rgbData, RoutingKey: routingKey, EmptySource: true}
+ relay.sendChannel <- sendItem
+}
+
// RGBZig Defs
type ZigRelayData struct {
parent *RGBZig
}
type RGBZig struct {
- RGBRelay `yaml:"RGBRelay"`
+ RGBMQRelay `yaml:"RGBMQRelay"`
FriendlyName string `yaml:"FriendlyName"`
Sengled bool `yaml:"Sengled"`
}
func (zig *RGBZig) Init(sendChannel chan helper.RabbitSend) {
- zig.RGBRelay.Init(sendChannel)
+ zig.RGBMQRelay.Init(sendChannel)
zig.logger = logger.WithValues("relay", zig)
- zig.RGBRelay.relayData = ZigRelayData{parent: zig}
+ zig.relayData = ZigRelayData{parent: zig}
}
// RGBZig added function
// ESPRelay defs
type ESPRelay struct {
- RGBRelay `yaml:"RGBRelay"`
- Topic string `yaml:"Topic"`
+ RGBMQRelay `yaml:"RGBMQRelay"`
+ Topic string `yaml:"Topic"`
}
func (relay *ESPRelay) Init(sendChannel chan helper.RabbitSend) {
- relay.RGBRelay.Init(sendChannel)
+ relay.RGBMQRelay.Init(sendChannel)
relay.logger = logger.WithValues("relay", relay)
- relay.RGBRelay.relayData = ESPRelayData{parent: relay}
+ relay.relayData = ESPRelayData{parent: relay}
}
func (relay *ESPRelay) getRoutingKey() string {
func (pico *RGBPico) Init(sendChannel chan helper.RabbitSend) {
pico.RGBZig.Init(sendChannel)
pico.logger = logger.WithValues("relay", pico)
- pico.RGBZig.RGBRelay.relayData = PicoRelayData{parent: pico}
+ pico.relayData = PicoRelayData{parent: pico}
}
type RGBManager struct {
logger.V(1).Info("parsed config as follows", "relayDefs", relayDefs)
}
+ // init mattersocket connection
+ matter := &mattersocket.MatterSocket{}
+ matter.Init()
+
rabbit, err := helper.SetupRabbit(configFilename, "", "lights") // config file, default routing key, source
if err != nil {
logger.Error(err, "unable to setup rabbit")
manager := RGBManager{}
manager.init(&rabbit)
- for i, _ := range relayDefs.RGBRelays {
- relayDefs.RGBRelays[i].Init(channel)
- logger.V(1).Info("Adding relay to manager", "relay", relayDefs.RGBRelays[i])
- manager.addRelay(&relayDefs.RGBRelays[i])
+ for i, _ := range relayDefs.RGBMQRelays {
+ relayDefs.RGBMQRelays[i].Init(channel)
+ logger.V(1).Info("Adding relay to manager", "relay", relayDefs.RGBMQRelays[i])
+ manager.addRelay(&relayDefs.RGBMQRelays[i])
+ }
+
+ for i, _ := range relayDefs.RGBMatterRelays {
+ relayDefs.RGBMatterRelays[i].Init(matter)
+ logger.V(1).Info("Adding relay to manager", "relay", relayDefs.RGBMatterRelays[i])
+ manager.addRelay(&relayDefs.RGBMatterRelays[i])
}
for i, _ := range relayDefs.ESPRelays {
--- /dev/null
+// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mattersocket
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "math"
+ "net/url"
+ "os"
+ "os/signal"
+ "strconv"
+ "sync"
+
+ "github.com/go-logr/logr"
+ "github.com/gorilla/websocket"
+ "github.com/lucasb-eyer/go-colorful"
+ "k8s.io/klog/v2/klogr"
+ //logging things
+)
+
+var logger logr.Logger
+var sendMutex sync.Mutex
+
+func init() {
+ // our package logger
+ logger = klogr.New()
+}
+
+type MatterSocket struct {
+ Address string
+ allNodes map[string]string
+ writeChan chan []byte
+ doneChan chan struct{}
+ interruptChan chan os.Signal
+ conn *websocket.Conn
+ messageID int64
+}
+
+func (matter *MatterSocket) Init() {
+ matter.Address = "iotbridge:5580"
+ flag.Parse()
+ log.SetFlags(0)
+
+ matter.interruptChan = make(chan os.Signal, 1)
+ matter.doneChan = make(chan struct{})
+ matter.writeChan = make(chan []byte)
+
+ signal.Notify(matter.interruptChan, os.Interrupt)
+
+ u := url.URL{Scheme: "ws", Host: matter.Address, Path: "/ws"}
+ logger.Info("Connecting to url", "url", u.String())
+
+ conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
+ matter.conn = conn
+ if err != nil {
+ log.Fatal("dial:", err)
+ }
+
+ // get initial serial -> node id mapping data
+ matter.fetchNodes()
+
+ // these will loop until an interrupt is hit, then close and exit
+ go matter.ReadLoop()
+ go matter.WriteLoop()
+
+}
+
+func (matter *MatterSocket) WriteMap(data map[string]interface{}) {
+ // starts at 0, so first message sent out should be "1"
+ matter.messageID += 1
+ data["message_id"] = strconv.FormatInt(matter.messageID, 10)
+
+ jsonBytes, err := json.Marshal(data)
+ if err != nil {
+ logger.Error(err, "error encoding json")
+ }
+ matter.writeChan <- jsonBytes
+}
+
+func (matter *MatterSocket) fetchNodes() {
+ var sendData map[string]interface{} = make(map[string]interface{}, 0)
+ sendData["command"] = "get_nodes"
+ go matter.WriteMap(sendData)
+
+}
+
+func (matter *MatterSocket) ReadLoop() {
+ defer close(matter.doneChan)
+ for {
+ messageType, message, err := matter.conn.ReadMessage()
+ if err != nil {
+ logger.Error(err, "error on read")
+ os.Exit(1)
+ }
+ var dataObj map[string]interface{}
+ if messageType == websocket.TextMessage {
+ err := json.Unmarshal(message, &dataObj)
+ if err != nil {
+ log.Printf("unable to parse json text for msg: %s", message)
+ }
+ }
+ messageID, ok := dataObj["message_id"]
+ if ok {
+ if messageID == "1" {
+ matter.allNodes = matter.parseNodeData(dataObj)
+ log.Printf("all nodes %+v\n", matter.allNodes)
+ }
+ }
+ }
+}
+
+func (matter *MatterSocket) WriteLoop() {
+ for {
+ select {
+ case <-matter.doneChan:
+ return
+ case msg := <-matter.writeChan:
+ err := matter.conn.WriteMessage(websocket.TextMessage, msg)
+ if err != nil {
+ logger.Error(err, "write error")
+ os.Exit(1)
+ } else {
+ logger.V(4).Info("wrote to websocket", "msg", msg)
+ }
+ case <-matter.interruptChan:
+ log.Println("interrupt")
+
+ // Cleanly close the connection by sending a close message and then
+ // waiting (with timeout) for the server to close the connection.
+ err := matter.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ if err != nil {
+ logger.Error(err, "write close")
+ os.Exit(1)
+ }
+ select {
+ case <-matter.doneChan:
+ }
+ return
+ }
+ }
+}
+
+func (matter *MatterSocket) NodePWM(serial string, red, green, blue float64) error {
+ sendData := make(map[string]interface{}, 0)
+ nodeID, ok := matter.allNodes[serial]
+ if !ok {
+ logger.Error(nil, "Unable to find node!", "serial", serial)
+ return fmt.Errorf("Unable to find node serial %s", serial)
+ }
+
+ // we have a node ID to send our request to!
+ // now to find out if we're turning off, or changing color
+ if red != 0 || green != 0 || blue != 0 {
+ // superfluous "on"
+ powerData := make(map[string]interface{}, 0)
+ powerArgs := make(map[string]interface{}, 0)
+ powerData["command"] = "device_command"
+ powerArgs["endpoint_id"] = 1
+ powerArgs["node_id"] = nodeID
+ powerArgs["cluster_id"] = 6
+ powerArgs["payload"] = make(map[string]string, 0)
+ powerArgs["command_name"] = "On"
+
+ powerData["args"] = powerArgs
+
+ matter.WriteMap(powerData)
+
+ // color conversions
+ c := colorful.Color{R: red, G: green, B: blue}
+ h, s, v := c.Hsv()
+
+ newColor := colorful.Hsv(h, s, v)
+ x, y, z := newColor.Xyz()
+
+ // finally, boil down to the X Y values only
+ xyz_x := x / (x + y + z)
+ xyz_y := y / (x + y + z)
+
+ args := make(map[string]interface{}, 0)
+ payload := make(map[string]interface{}, 0)
+
+ //payload["hue"] = int64(h * 255 / 360)
+ //payload["saturation"] = int64((math.Pow(2, 8)-1)*s - 1)
+ payload["colorX"] = int64(math.Pow(2, 16) * xyz_x)
+ payload["colorY"] = int64(math.Pow(2, 16) * xyz_y)
+ payload["transitionTime"] = 0
+ payload["optionsMask"] = 0
+ payload["optionsOverride"] = 0
+
+ // args["command_name"] = "MoveToHueAndSaturation"
+ args["command_name"] = "MoveToColor"
+ args["endpoint_id"] = 1
+ args["node_id"] = nodeID
+ args["payload"] = payload
+ args["cluster_id"] = 768
+
+ sendData["command"] = "device_command"
+ sendData["args"] = args
+
+ logger.V(3).Info("Final PWM data", "sendData", sendData)
+ matter.WriteMap(sendData)
+
+ } else {
+ logger.V(2).Info(" need to turn off")
+ // superfluous "on"
+ powerData := make(map[string]interface{}, 0)
+ powerArgs := make(map[string]interface{}, 0)
+ powerData["command"] = "device_command"
+ powerArgs["endpoint_id"] = 1
+ powerArgs["node_id"] = nodeID
+ powerArgs["cluster_id"] = 6
+ powerArgs["payload"] = make(map[string]string, 0)
+ powerArgs["command_name"] = "Off"
+
+ powerData["args"] = powerArgs
+
+ logger.V(3).Info("Final power data", "powerData", powerData)
+ matter.WriteMap(powerData)
+ }
+
+ return nil
+}
+
+func (matter *MatterSocket) parseNodeData(data map[string]interface{}) map[string]string {
+ var retval map[string]string = make(map[string]string, 0)
+ result, resultOK := data["result"]
+ if resultOK {
+ resultArray, _ := result.([]interface{})
+ for _, node := range resultArray {
+ nodeMap, _ := node.(map[string]interface{})
+ nodeID, nodeOK := nodeMap["node_id"]
+ if nodeOK {
+ nodeFloat, _ := nodeID.(float64)
+ attributes, attrOK := nodeMap["attributes"]
+ if attrOK {
+ attribMap, _ := attributes.(map[string]interface{})
+ serial, serialOK := attribMap["0/40/15"]
+ if serialOK {
+ logger.V(2).Info("Found a node", "nodeID", nodeID, "serial", serial)
+ retval[serial.(string)] = strconv.FormatInt(int64(nodeFloat), 10)
+ }
+ }
+ }
+ }
+ }
+ return retval
+
+}