-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPumps.ino
126 lines (103 loc) · 3.75 KB
/
Pumps.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
class PumpSchedule {
public:
PumpSchedule(const Deck& deck) :
deck(deck),
cycleInterval(deck.lightDuration / (deck.floodCycles - 1)) // for 5 cycles, there are 4 intervals between, thus floodCycles-1
{
updateNextFlood();
}
void updateNextFlood() {
currentCycle = calculateCurrentCycle();
nextCycleTimestamp = calculateNextFlood();
}
uint8_t getCycle() { return currentCycle; }
uint32_t getNextCycleTime() { return nextCycleTimestamp; }
const Deck& deck;
const uint32_t cycleInterval;
private:
uint8_t calculateCurrentCycle() {
uint32_t daySeconds = g_now.unixtime() % DAY;
if (daySeconds < deck.sunriseTime)
return 0;
// for simplicity, I here assume cycle increments when flood begins, so at deck.sunriseTime the cycle is 1.
int cycle = 1 + (daySeconds - deck.sunriseTime) / cycleInterval;
if (cycle > deck.floodCycles)
cycle = deck.floodCycles; // max cycle value
return cycle;
}
uint32_t calculateNextFlood() {
uint32_t nextFlood; // seconds after midnight
if (currentCycle < deck.floodCycles)
nextFlood = deck.sunriseTime + currentCycle * cycleInterval;
else
nextFlood = DAY + deck.sunriseTime;
DateTime nowDay(g_now.year(), g_now.month(), g_now.day(), 0, 0, 0);
return nowDay.unixtime() + nextFlood;
}
uint8_t currentCycle;
uint32_t nextCycleTimestamp;
};
static PumpSchedule* PumpSchedules[deckCount];
/*static*/ void Pumps::init() {
for (int i=0; i < deckCount; i++)
PumpSchedules[i] = new PumpSchedule(g_deckList[i]); // lives until shutdown - we never deallocate.
}
void updateSchedulesAtMidnight() {
// resets cycle count each day at midnight
static uint8_t prevDay = g_now.day();
if (g_now.day() != prevDay) {
prevDay = g_now.day();
for (PumpSchedule* schedule: PumpSchedules)
schedule->updateNextFlood();
}
}
PumpSchedule* findNextFloodEvent() {
uint32_t nextFloodTime = -1; // max uint
PumpSchedule* nextSchedule = PumpSchedules[0];
for (PumpSchedule* schedule: PumpSchedules) {
if (schedule->getNextCycleTime() < nextFloodTime) {
nextFloodTime = schedule->getNextCycleTime();
nextSchedule = schedule;
}
}
return nextSchedule;
}
enum State {
wait,
flood,
drain,
stateCount
};
/*static*/ void Pumps::poll() {
updateSchedulesAtMidnight();
// timed state changes
static State currentState = wait;
static PumpSchedule* currentSchedule = findNextFloodEvent();
static uint32_t stateChangeTime = currentSchedule->getNextCycleTime();
if (g_now.unixtime() < stateChangeTime)
return;
currentState = static_cast<State>((currentState + 1) % stateCount);
switch (currentState) {
case wait:
Log::logString(Log::info, "Pump State: Wait");
if (currentSchedule)
currentSchedule->updateNextFlood(); // only the tray that just got flooded
currentSchedule = findNextFloodEvent();
stateChangeTime = currentSchedule->getNextCycleTime();
break;
case flood:
Log::logString(Log::info, "Pump State: Flood #" + String(currentSchedule->deck.id));
setOutlet(currentSchedule->deck.pumpOutlet, true);
stateChangeTime = g_now.unixtime() + currentSchedule->deck.floodMinutes * MINUTE;
break;
case drain:
Log::logString(Log::info, "Pump State: Drain #" + String(currentSchedule->deck.id));
setOutlet(currentSchedule->deck.pumpOutlet, false);
stateChangeTime = g_now.unixtime() + currentSchedule->deck.drainMinutes * MINUTE;
break;
default:
fatalError("pump state");
}
}
/*static*/ uint8_t Pumps::getCurrentCycle(uint8_t index) { return PumpSchedules[index]->getCycle(); }
/*static*/ uint32_t Pumps::getNextEvent(uint8_t index) { return PumpSchedules[index]->getNextCycleTime(); }