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 }