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  }