github.com/martinohmann/rfoutlet@v1.2.1-0.20220707195255-8a66aa411105/internal/timeswitch/timeswitch.go (about)

     1  // Package timeswitch implements the time switch logic for outlets. Outlets can
     2  // be configured to automatically be turned on or off during certain periods of
     3  // the day or week. The time switch will emit state correction commands based
     4  // on the schedule that may be defined on an outlet.
     5  package timeswitch
     6  
     7  import (
     8  	"time"
     9  
    10  	"github.com/jonboulle/clockwork"
    11  	"github.com/martinohmann/rfoutlet/internal/command"
    12  	"github.com/martinohmann/rfoutlet/internal/outlet"
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  var log = logrus.WithField("component", "timeswitch")
    17  
    18  // TimeSwitch checks if outlets should be enabled or disabled based on their
    19  // schedule and send out commands to bring them to the desired state.
    20  type TimeSwitch struct {
    21  	Registry     *outlet.Registry
    22  	CommandQueue chan<- command.Command
    23  	Clock        clockwork.Clock
    24  }
    25  
    26  // New creates a new *TimeSwitch which will observe the outlets in the registry
    27  // and eventually push commands to the queue if a state change is required.
    28  func New(registry *outlet.Registry, queue chan<- command.Command) *TimeSwitch {
    29  	return &TimeSwitch{
    30  		Registry:     registry,
    31  		CommandQueue: queue,
    32  		Clock:        clockwork.NewRealClock(),
    33  	}
    34  }
    35  
    36  // Run runs the time switch control loop which periodically checks if an outlet
    37  // should be enabled or disabled. Whenever an outlet should change its state,
    38  // it will push a TimeSwitchCommand into the CommandQueue.
    39  func (s *TimeSwitch) Run(stopCh <-chan struct{}) {
    40  	for {
    41  		select {
    42  		case <-s.Clock.After(s.secondsUntilNextMinute()):
    43  			s.check()
    44  		case <-stopCh:
    45  			log.Info("shutting down time switch")
    46  			return
    47  		}
    48  	}
    49  }
    50  
    51  // secondsUntilNextMinute returns the seconds until the next minute starts.
    52  func (s *TimeSwitch) secondsUntilNextMinute() time.Duration {
    53  	return time.Second * time.Duration(60-s.Clock.Now().Second())
    54  }
    55  
    56  func (s *TimeSwitch) check() {
    57  	for _, outlet := range s.Registry.GetOutlets() {
    58  		if !outlet.Schedule.Enabled() {
    59  			continue
    60  		}
    61  
    62  		desiredState := getDesiredState(outlet)
    63  
    64  		// We only send out commands if the outlet is not in the desired state
    65  		// to avoid spamming the command queue.
    66  		if outlet.GetState() != desiredState {
    67  			s.CommandQueue <- command.StateCorrectionCommand{
    68  				Outlet:       outlet,
    69  				DesiredState: desiredState,
    70  			}
    71  		}
    72  	}
    73  }
    74  
    75  func getDesiredState(o *outlet.Outlet) outlet.State {
    76  	if o.Schedule.Contains(time.Now()) {
    77  		return outlet.StateOn
    78  	}
    79  
    80  	return outlet.StateOff
    81  }