github.com/martinohmann/rfoutlet@v1.2.1-0.20220707195255-8a66aa411105/internal/controller/controller.go (about) 1 // Package controller provides a controller that handles all outlet commands 2 // sequentially. 3 package controller 4 5 import ( 6 "encoding/json" 7 "fmt" 8 9 "github.com/martinohmann/rfoutlet/internal/command" 10 "github.com/martinohmann/rfoutlet/internal/outlet" 11 "github.com/sirupsen/logrus" 12 ) 13 14 var log = logrus.WithField("component", "controller") 15 16 // Broadcaster can broadcast messages to all connected clients. 17 type Broadcaster interface { 18 // Broadcast broadcasts msg to all connected clients. 19 Broadcast(msg []byte) 20 } 21 22 // Controller controls the outlets registered to the registry. 23 type Controller struct { 24 // Registry contains all known outlets and outlet groups. 25 Registry *outlet.Registry 26 // Switcher switches outlets on or off based on commands from the 27 // CommandQueue. 28 Switcher outlet.Switcher 29 // Broadcaster broadcasts state updates to all connected clients. 30 Broadcaster Broadcaster 31 // CommandQueue is consumed sequentially by the controller. The commands 32 // may cause outlet and group state changes which are communicated back to 33 // one or more connected clients. 34 CommandQueue <-chan command.Command 35 } 36 37 // Run runs the main control loop until stopCh is closed. 38 func (c *Controller) Run(stopCh <-chan struct{}) { 39 for { 40 select { 41 case cmd, ok := <-c.CommandQueue: 42 if !ok { 43 log.Error("command queue was closed unexpectedly, shutting down controller") 44 return 45 } 46 47 err := c.handleCommand(cmd) 48 if err != nil { 49 log.WithField("command", fmt.Sprintf("%T", cmd)). 50 Errorf("error handling command: %v", err) 51 } 52 case <-stopCh: 53 log.Info("shutting down controller") 54 return 55 } 56 } 57 } 58 59 // commandContext creates a new command.Context. 60 func (c *Controller) commandContext() command.Context { 61 return command.Context{ 62 Registry: c.Registry, 63 Switcher: c.Switcher, 64 } 65 } 66 67 // handleCommand executes cmd and may trigger broadcasts of state changes back 68 // to the connected clients. 69 func (c *Controller) handleCommand(cmd command.Command) error { 70 log.WithField("command", fmt.Sprintf("%T", cmd)). 71 Debug("handling command") 72 73 ctx := c.commandContext() 74 75 broadcast, err := cmd.Execute(ctx) 76 if err != nil || !broadcast { 77 return err 78 } 79 80 return c.broadcastState() 81 } 82 83 // broadcastState broadcasts the current outlet group state back to connected 84 // clients. 85 func (c *Controller) broadcastState() error { 86 msg, err := json.Marshal(c.Registry.GetGroups()) 87 if err != nil { 88 return err 89 } 90 91 c.Broadcaster.Broadcast(msg) 92 93 return nil 94 }