-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 31aff0a
Showing
8 changed files
with
474 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package scheduling | ||
|
||
// ComparableSlice : would be used to keep relay ids | ||
// we would want to know if there are common ids and some context we would need if all are matching | ||
type ComparableSlice []string | ||
|
||
// Intersection : tries to get the intersecting items from 2 slices | ||
// items that are common to both | ||
// items that are unique to cmpsl | ||
// items that are unique to other | ||
func (cmpsl ComparableSlice) Intersection(other ComparableSlice) (int, int, int) { | ||
comm := 0 | ||
for _, item := range cmpsl { | ||
for _, oitem := range other { | ||
if item == oitem { | ||
comm++ | ||
} | ||
} | ||
} | ||
return comm, len(cmpsl) - comm, len(other) - comm | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module github.com/eensymachines-in/scheduling | ||
|
||
go 1.15 | ||
|
||
require ( | ||
github.com/sirupsen/logrus v1.7.0 | ||
github.com/stretchr/testify v1.2.2 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= | ||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | ||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= | ||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package scheduling | ||
|
||
import "fmt" | ||
|
||
// RelayState : this is just to hold the state of relay with the identification of the relay | ||
// relay should be identified with the same name as required by srvrelay | ||
// Storing this as just a byte is also possible, but that is when we want the relay module to work as a block, not when we want to operate on individual relays | ||
type RelayState struct { | ||
state byte | ||
id string | ||
} | ||
|
||
// Status : Gets the state of the relay with ID | ||
func (rs *RelayState) Status() map[string]byte { | ||
return map[string]byte{rs.id: rs.state} | ||
} | ||
|
||
// Flip : flips the state of the relay | ||
func (rs *RelayState) Flip() { | ||
rs.state = byte(1) - rs.state | ||
} | ||
|
||
// State : sets the state of the relay | ||
func (rs *RelayState) State(new byte) *RelayState { | ||
if new > 0 { | ||
rs.state = byte(1) | ||
} | ||
rs.state = byte(0) | ||
return rs | ||
} | ||
|
||
// ID : spits out the id of the relay state | ||
// this is generally the relay ID on the actual relay, IN1, IN2, IN3.. | ||
func (rs *RelayState) ID() string { | ||
return rs.id | ||
} | ||
|
||
// NewRelayState : quick way to make a new relay state | ||
func NewRelayState(id string) *RelayState { | ||
return &RelayState{byte(0), id} | ||
} | ||
|
||
// ================================== Json Relay state is for file reads ============================ | ||
// Making a relay state from a json file | ||
|
||
// JSONRelayState : relaystate but in json format | ||
type JSONRelayState struct { | ||
ON int `json:"on"` | ||
OFF int `json:"off"` | ||
IDs []string `json:"ids"` | ||
Primary bool `json:"primary"` | ||
} | ||
|
||
// ToSchedule : reads from json and pumps up a schedule | ||
// this saves you the trouble of making a schedule via code, | ||
// from a json file it can read up a relaystate and convert that to schedule | ||
func (jrs *JSONRelayState) ToSchedule() (Schedule, error) { | ||
offs := []*RelayState{} | ||
ons := []*RelayState{} | ||
for _, id := range jrs.IDs { | ||
offs = append(offs, &RelayState{byte(0), id}) | ||
ons = append(ons, &RelayState{byte(1), id}) | ||
} | ||
trg1, trg2 := NewTrg(jrs.OFF, offs...), NewTrg(jrs.ON, ons...) | ||
if jrs.Primary { | ||
return NewPrimarySchedule(trg1, trg2) | ||
} | ||
return nil, fmt.Errorf("Schedule type unknown") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package scheduling | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestComparableSlice(t *testing.T) { | ||
sl1 := ComparableSlice{"IN1", "IN2", "IN3"} | ||
sl2 := ComparableSlice{"IN2", "IN3", "IN4", "IN1"} | ||
matches, mismatch1, mismatch2 := sl1.Intersection(sl2) | ||
assert.Equal(t, 3, matches, "Was expecting 2 matches in the slices above") | ||
assert.Equal(t, 0, mismatch1, "Incorrect mismatches on the first") | ||
assert.Equal(t, 1, mismatch2, "Incorrect mismatches on the second") | ||
|
||
t.Log("--------------------------\n") | ||
sl1 = ComparableSlice{} | ||
sl2 = ComparableSlice{"IN2", "IN3", "IN4", "IN1"} | ||
matches, mismatch1, mismatch2 = sl1.Intersection(sl2) | ||
assert.Equal(t, 0, matches, "Was expecting 2 matches in the slices above") | ||
assert.Equal(t, 0, mismatch1, "Incorrect mismatches on the first") | ||
assert.Equal(t, 4, mismatch2, "Incorrect mismatches on the second") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package scheduling | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
// Schedule : is the handle for external packages | ||
type Schedule interface { | ||
Triggers() (Trigger, Trigger) | ||
Duration() int | ||
NearFarTrigger(elapsed int) (Trigger, Trigger, int, int) | ||
ConflictsWith(another Schedule) bool | ||
Midpoint() int | ||
Close() | ||
Apply(ok, cancel chan interface{}, send chan []byte, err chan error) | ||
} | ||
|
||
// primarySched : this schedule is the longer schedule and in all the cases there is only one of this | ||
// primarySched is circular and beyond the triggers applies the last valid state of the trigger | ||
type primarySched struct { | ||
lower Trigger | ||
higher Trigger | ||
} | ||
|
||
func (ps *primarySched) Triggers() (Trigger, Trigger) { | ||
return ps.lower, ps.higher | ||
} | ||
func (ps *primarySched) Duration() int { | ||
return ps.higher.At() - ps.lower.At() | ||
} | ||
func (ps *primarySched) Midpoint() int { | ||
return (ps.Duration() / 2) + ps.lower.At() | ||
} | ||
func (ps *primarySched) Close() { | ||
// For now all what the schedule does when closing is just ouput a log message | ||
log.Infof("%s Schedule is now closing", ps) | ||
} | ||
func (ps *primarySched) String() string { | ||
return fmt.Sprintf("%s - %s", tmStrFromUnixSecs(ps.lower.At()), tmStrFromUnixSecs(ps.higher.At())) | ||
} | ||
|
||
// NearFarTrigger : in context of the current time, this helps to get the triggers that are near or far | ||
// For any schedule when its applied - pre sleep - nr state apply - post sleep - fr state apply | ||
// For a primary schedule its thought to be circular, meaning to say : if beyond the trigger bounds the higher trigger is applied | ||
func (ps *primarySched) NearFarTrigger(elapsed int) (Trigger, Trigger, int, int) { | ||
// for primary schedule nr trigger will be applied then, sleep, then fr state | ||
// for primary schedule there is no pre sleep - since its circular and applies beyond the 2 triggers as well | ||
var nr, fr Trigger | ||
var post int | ||
if elapsed >= ps.lower.At() && elapsed < ps.higher.At() { | ||
nr, fr = ps.lower, ps.higher | ||
post = ps.higher.At() - elapsed | ||
} else { | ||
nr, fr = ps.higher, ps.lower | ||
if elapsed < ps.lower.At() { | ||
post = ps.lower.At() - elapsed | ||
} | ||
if elapsed > ps.higher.At() { | ||
post = 86400 - elapsed + ps.lower.At() | ||
} | ||
} | ||
return nr, fr, 0, post // for primary schedules pre sleep is always 0 | ||
} | ||
|
||
func (ps *primarySched) overlapsWith(another Schedule) bool { | ||
// Midpoints are distance of the half time since midnight for any schedule | ||
mdpt1, mdpt2 := ps.Midpoint(), another.Midpoint() | ||
// half duration of each schedule | ||
hfdur1, hfdur2 := ps.Duration()/2, another.Duration()/2 | ||
// getting the absolute of the midpoint distance | ||
mdptdis := mdpt1 - mdpt2 | ||
if mdptdis < 0 { | ||
mdptdis = -mdptdis | ||
} | ||
// Getting the larger of the 2 schedules | ||
var min, max int | ||
if hfdur1 <= hfdur2 { | ||
min, max = hfdur1, hfdur2 | ||
} else { | ||
min, max = hfdur2, hfdur1 | ||
} | ||
if (mdptdis > (hfdur1 + hfdur2)) || ((mdptdis + min) < max) { | ||
// case when the schedules are clearing and not interferring with one another | ||
// either one schedule is inside the other or on one side | ||
return false | ||
} | ||
// all other cases the schedules are either partially/exactly overlapping | ||
return true | ||
} | ||
|
||
// ConflictsWith : checks to see partial overlapping of schedules | ||
func (ps *primarySched) ConflictsWith(another Schedule) bool { | ||
_, ok := another.(*primarySched) | ||
if ok { | ||
// Always conflicts with other primary schedule | ||
// overlaps are checked for circular and non-cicrular schedule | ||
return true | ||
} | ||
return ps.overlapsWith(another) | ||
} | ||
|
||
func (ps *primarySched) Apply(ok, cancel chan interface{}, send chan []byte, err chan error) { | ||
nr, fr, pre, post := ps.NearFarTrigger(elapsedSecondsNow()) | ||
if pre > 0 { | ||
// this will work as expected even when pre=0, but the problem is it sill still allow the processor to jump to the next task | ||
<-time.After(time.Duration(pre) * time.Duration(1*time.Second)) | ||
} | ||
byt, e := json.Marshal(nr) | ||
if e != nil { // state of the trigger is applied | ||
err <- fmt.Errorf("Schedule/Apply: Failed to marshall trigger data - %s", e) | ||
return | ||
} | ||
send <- byt | ||
select { | ||
// sleep duration is always a second extra than the sleep time | ||
// so that incase the processor is fast enough this will still be in the next slot | ||
case <-time.After(time.Duration(post+1) * time.Duration(1*time.Second)): | ||
log.Info("End of schedule.. ") | ||
if byt, e = json.Marshal(fr); e != nil { | ||
err <- fmt.Errorf("Schedule/Apply: Failed to marshall trigger data - %s", e) | ||
return | ||
} | ||
send <- byt | ||
ok <- struct{}{} | ||
case <-cancel: | ||
log.Warn("Schedule/Apply: Interruption") | ||
ps.Close() | ||
} | ||
return | ||
} | ||
|
||
// Loop : this shall loop the schedule forever till there is a interruption or the schedule application fails | ||
func (ps *primarySched) Loop(cancel, interrupt chan interface{}, send chan []byte, loopErr chan error) { | ||
// this channnel communicates the ok from apply function | ||
// The loop still does not indicate done unless ofcourse the done <-nil | ||
ok := make(chan interface{}, 1) | ||
defer close(ok) | ||
stop := make(chan interface{}) // this is to stop the currently running schedule | ||
for { | ||
ps.Apply(ok, stop, send, loopErr) // applies the schedul infinitely | ||
select { | ||
case <-cancel: | ||
case <-interrupt: | ||
close(stop) | ||
log.Warn("Running schedule is stopped or interrupted, now closing the loop as well") | ||
return | ||
case <-ok: | ||
// this is when the schedule has done applying for one cycle | ||
// will go back to applying the next schedule for the then current time | ||
} | ||
} | ||
} | ||
|
||
// NewPrimarySchedule : makes a new TriggeredSchedul, will take 2 triggers | ||
func NewPrimarySchedule(trg1, trg2 Trigger) (Schedule, error) { | ||
if trg1.At() == trg2.At() { | ||
return nil, fmt.Errorf("ERROR/NewPrimarySchedule: triggers cannot be overlapping") | ||
} | ||
if !trg1.IdenticalRelays(trg2) { | ||
return nil, fmt.Errorf("ERROR/NewPrimarySchedule:triggers are paired with same relay ids") | ||
} | ||
var l, h Trigger | ||
if trg1.At() < trg2.At() { | ||
l, h = trg1, trg2 | ||
} else { | ||
l, h = trg2, trg1 | ||
} | ||
return &primarySched{l, h}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package scheduling | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
const ( | ||
// I tried to change PM to AM in the format, and then it fails to read PM. but when PM in format, it reads both | ||
// https://stackoverflow.com/questions/44924628/golang-time-parse-1122-pm-something-that-i-can-do-math-with | ||
format = "03:04 PM" | ||
mdNt = "12:00 AM" | ||
) | ||
|
||
// TimeStr : custom definition of time as string, indicated of the format above | ||
type TimeStr string | ||
|
||
// ToElapsedTm : just converts the string time to | ||
func (ts TimeStr) ToElapsedTm() (int, error) { | ||
mdntTm, _ := time.Parse(format, mdNt) // getting the midnight time | ||
tm, err := time.Parse(format, string(ts)) | ||
if err != nil { | ||
return -1, err | ||
} | ||
elapsed := tm.Sub(mdntTm).Seconds() | ||
return int(elapsed), nil | ||
} | ||
|
||
// tmStrFromUnixSecs : for the unix seconds given this can convert that into TimeStr | ||
// this application uses specific format of clock so that its compatible to PArsing of time | ||
func tmStrFromUnixSecs(elapsed int) TimeStr { | ||
hr, rem := elapsed/3600, elapsed%3600 | ||
min := rem / 60 | ||
// fmt.Printf("%d %d\n", hr, min) | ||
ampm := "AM" | ||
if hr >= 12 { // noon 12 is 12:01, while midnight 12 is 00:01 | ||
hr = hr - 12 | ||
ampm = "PM" | ||
} | ||
return TimeStr(fmt.Sprintf("%02d:%02d %s", hr, min, ampm)) | ||
} | ||
|
||
// elapsedSecondsNow : this can for any given day, calculate the seconds that have elapsed since midnight | ||
func elapsedSecondsNow() int { | ||
hr, min, sec := time.Now().Clock() | ||
return (hr * 3600) + (min * 60) + sec | ||
} |
Oops, something went wrong.