github.com/martinohmann/rfoutlet@v1.2.1-0.20220707195255-8a66aa411105/internal/command/commands.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "fmt" 6 7 "github.com/martinohmann/rfoutlet/internal/outlet" 8 "github.com/martinohmann/rfoutlet/internal/schedule" 9 ) 10 11 // StatusCommand is sent by a connected client to retrieve the list of current 12 // outlet groups. This usually happens when the client first connects. 13 type StatusCommand struct { 14 sender Sender 15 } 16 17 // Execute implements Command. 18 // 19 // It sends the registered outlet groups back to the sender. 20 func (c StatusCommand) Execute(context Context) (bool, error) { 21 groups := context.GetGroups() 22 23 msg, err := json.Marshal(groups) 24 if err != nil { 25 return false, err 26 } 27 28 c.sender.Send(msg) 29 30 return false, nil 31 } 32 33 // SetSender implements SenderAwareCommand. 34 func (c *StatusCommand) SetSender(sender Sender) { 35 c.sender = sender 36 } 37 38 // OutletCommand switches a specific outlet based on the action. 39 type OutletCommand struct { 40 // OutletID is the ID of the outlet that the action should be performed on. 41 OutletID string `json:"outletID"` 42 // Action defines the action type that should be performed on the outlet. 43 Action OutletAction `json:"action"` 44 } 45 46 // Execute implements Command. 47 // 48 // It switches an outlet based on the transmitted action. 49 func (c OutletCommand) Execute(context Context) (bool, error) { 50 outlet, ok := context.GetOutlet(c.OutletID) 51 if !ok { 52 return false, fmt.Errorf("outlet %q does not exist", c.OutletID) 53 } 54 55 if outlet.Schedule.Enabled() { 56 return false, nil 57 } 58 59 targetState, err := getTargetState(outlet, c.Action) 60 if err != nil { 61 return false, err 62 } 63 64 err = context.Switch(outlet, targetState) 65 if err != nil { 66 return false, err 67 } 68 69 return true, nil 70 } 71 72 // GroupCommand switches a group of outlets based on the action. 73 type GroupCommand struct { 74 // GroupID is the ID of the outlet group that the action should be 75 // performed on. 76 GroupID string `json:"groupID"` 77 // Action defines the action type that should be performed on the outlet 78 // group. 79 Action OutletAction `json:"action"` 80 } 81 82 // Execute implements Command. 83 // 84 // It switches a group of outlets based on the transmitted action. 85 func (c GroupCommand) Execute(context Context) (bool, error) { 86 group, ok := context.GetGroup(c.GroupID) 87 if !ok { 88 return false, fmt.Errorf("outlet group %q does not exist", c.GroupID) 89 } 90 91 var modified bool 92 93 for _, outlet := range group.Outlets { 94 if outlet.Schedule.Enabled() { 95 continue 96 } 97 98 targetState, err := getTargetState(outlet, c.Action) 99 if err != nil { 100 return modified, err 101 } 102 103 err = context.Switch(outlet, targetState) 104 if err != nil { 105 return modified, err 106 } 107 108 modified = true 109 } 110 111 return modified, nil 112 } 113 114 // IntervalCommand changes the intervals of an outlet based on the action. 115 type IntervalCommand struct { 116 // OutletID is the ID of the outlet where the intervals of the schedule 117 // should be changed. 118 OutletID string `json:"outletID"` 119 // Action defines the action type that should be performed on the interval. 120 Action IntervalAction `json:"action"` 121 // Interval is the configuration of the interval. 122 Interval schedule.Interval `json:"interval"` 123 } 124 125 // Execute implements Command. 126 // 127 // It add, updates or deletes an interval from the outlet's schedule based on 128 // the transmitted action. 129 func (c IntervalCommand) Execute(context Context) (bool, error) { 130 outlet, ok := context.GetOutlet(c.OutletID) 131 if !ok { 132 return false, fmt.Errorf("outlet %q does not exist", c.OutletID) 133 } 134 135 err := c.handle(outlet) 136 if err != nil { 137 return false, err 138 } 139 140 return true, nil 141 } 142 143 func (c IntervalCommand) handle(outlet *outlet.Outlet) error { 144 switch c.Action { 145 case CreateIntervalAction: 146 return outlet.Schedule.AddInterval(c.Interval) 147 case UpdateIntervalAction: 148 return outlet.Schedule.UpdateInterval(c.Interval) 149 case DeleteIntervalAction: 150 return outlet.Schedule.DeleteInterval(c.Interval) 151 default: 152 return fmt.Errorf("invalid interval action %q", c.Action) 153 } 154 } 155 156 func getTargetState(o *outlet.Outlet, action OutletAction) (outlet.State, error) { 157 switch action { 158 case OnOutletAction: 159 return outlet.StateOn, nil 160 case OffOutletAction: 161 return outlet.StateOff, nil 162 case ToggleOutletAction: 163 if o.GetState() == outlet.StateOn { 164 return outlet.StateOff, nil 165 } 166 167 return outlet.StateOn, nil 168 default: 169 return 0, fmt.Errorf("invalid outlet action %q", action) 170 } 171 } 172 173 // StateCorrectionCommand is sent out whenever an outlet should change its state 174 // based on detected rf codes. 175 type StateCorrectionCommand struct { 176 // Outlet is the outlet that should be brought into the desired state. 177 Outlet *outlet.Outlet 178 // DesiredState is the state that the outlet should be in. 179 DesiredState outlet.State 180 } 181 182 // Execute implements Command. 183 // 184 // It switch an outlet to the detected state. 185 func (c StateCorrectionCommand) Execute(context Context) (bool, error) { 186 // If the outlet was already switched to the desired state after we 187 // submitted the command, we can bail out early. 188 if c.Outlet.GetState() == c.DesiredState { 189 return false, nil 190 } 191 192 err := context.Switch(c.Outlet, c.DesiredState) 193 if err != nil { 194 return false, err 195 } 196 197 return true, nil 198 }