-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathDoorController.cpp
305 lines (257 loc) · 8.33 KB
/
DoorController.cpp
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
////////////////////////////////////////////////////////////
// Coop Door Controller
////////////////////////////////////////////////////////////
#include <Arduino.h>
#include <GPSParser.h>
#include <SaveController.h>
#include "ICommInterface.h"
#include "TelemetryTags.h"
#include "Telemetry.h"
#include "MilliTimer.h"
#include "Pins.h"
#include "SunCalc.h"
#include "DoorController.h"
#include "LightController.h"
#include "BeepController.h"
#include "GaryCooper.h"
////////////////////////////////////////////////////////////
// Use GPS to decide when to open and close the coop door
////////////////////////////////////////////////////////////
CDoorController::CDoorController()
{
m_correctState = doorState_unknown;
m_command = (doorCommandE) - 1;
m_sunriseOffset = 0.;
m_sunsetOffset = 0.;
m_stuckDoorS = GARY_COOPER_DEF_DOOR_DELAY;
}
CDoorController::~CDoorController()
{
}
void CDoorController::setup()
{
if(getDoorMotor())
getDoorMotor()->setup();
}
void CDoorController::saveSettings(CSaveController &_saveController, bool _defaults)
{
// Should I setup for default settings?
if(_defaults)
{
setSunriseOffset(0.);
setSunsetOffset(0.);
setStuckDoorDelay(GARY_COOPER_DEF_DOOR_DELAY);
}
// Save settings
_saveController.writeInt(getSunriseOffset());
_saveController.writeInt(getSunsetOffset());
_saveController.writeInt(getStuckDoorDelay());
}
void CDoorController::loadSettings(CSaveController &_saveController)
{
// Load settings
int sunriseOffset = _saveController.readInt();
setSunriseOffset(sunriseOffset);
int sunsetOffset = _saveController.readInt();
setSunsetOffset(sunsetOffset);
int stuckDoorDelay = _saveController.readInt();
setStuckDoorDelay(stuckDoorDelay);
#ifdef DEBUG_DOOR_CONTROLLER
DEBUG_SERIAL.print(F("CDoorController - sunrise offset is: "));
DEBUG_SERIAL.println(getSunriseOffset());
DEBUG_SERIAL.print(F("CDoorController - sunset offset is :"));
DEBUG_SERIAL.println(getSunsetOffset());
DEBUG_SERIAL.print(F("CDoorController - stuck door delay :"));
DEBUG_SERIAL.println(getStuckDoorDelay());
DEBUG_SERIAL.println();
#endif
}
void CDoorController::tick()
{
// Tick the door motor
if(getDoorMotor())
getDoorMotor()->tick();
// OK, there is a race condition here. If the door is commanded
// to a different state, reaches that state, and then is
// * spontaneously * returned to the original state before this
// timer expires then a false failure will be reported.
// I'm not interested in fixing it.
// Check to see if the door is stuck
if(m_stuckDoorTimer.getState() == CMilliTimer::expired)
{
bool raiseAlarm = false;
// Check for failure to open
if((m_command == doorCommand_open) &&
(getDoorMotor()->getDoorState() != doorState_open))
{
raiseAlarm = true;
}
// Check for failure to close
if((m_command == doorCommand_close) &&
(getDoorMotor()->getDoorState() != doorState_closed))
{
raiseAlarm = true;
}
// Alarm if error
if(raiseAlarm)
{
reportError(telemetry_error_door_motor_unknown_not_responding, true);
}
else
{
m_stuckDoorTimer.reset();
reportError(telemetry_error_door_motor_unknown_not_responding, false);
}
}
}
double CDoorController::getDoorOpenTime()
{
double sunrise = g_sunCalc.getSunriseTime() + (getSunriseOffset() / 60.);
normalizeTime(sunrise);
return sunrise;
}
double CDoorController::getDoorCloseTime()
{
double sunset = g_sunCalc.getSunsetTime() + (getSunsetOffset() / 60.);
normalizeTime(sunset);
return sunset;
}
void CDoorController::checkTime()
{
// First of all, if the door motor does not know the door state
// then there is nothing to do.
if(!getDoorMotor())
{
#ifdef DEBUG_DOOR_CONTROLLER
DEBUG_SERIAL.println(F("CDoorController - no door motor found."));
DEBUG_SERIAL.println();
#endif
reportError(telemetry_error_no_door_motor, true);
return;
}
else
{
reportError(telemetry_error_no_door_motor, false);
}
// If the door state is unknown and we are not waiting for it to
// move then we have a problem
if(getDoorMotor()->getDoorState() == doorState_unknown)
{
#ifdef DEBUG_DOOR_CONTROLLER
DEBUG_SERIAL.println(F("CDoorController - door motor in unknown state."));
#endif
reportError(telemetry_error_door_motor_unknown_state, true);
return;
}
else
{
reportError(telemetry_error_door_motor_unknown_state, false);
}
// Get the times and keep going
double currentTime = g_sunCalc.getCurrentTime();
double doorOpenTime = getDoorOpenTime();
double doorCloseTime = getDoorCloseTime();
#ifdef DEBUG_DOOR_CONTROLLER
DEBUG_SERIAL.print(F("CDoorController - door open from: "));
debugPrintDoubleTime(doorOpenTime, false);
DEBUG_SERIAL.print(F(" - "));
debugPrintDoubleTime(doorCloseTime, false);
DEBUG_SERIAL.println(F(" (UTC)"));
#endif
// Validate the values and report telemetry
if(!g_sunCalc.isValidTime(doorOpenTime))
{
reportError(telemetry_error_suncalc_invalid_time, true);
return;
}
if(!g_sunCalc.isValidTime(doorCloseTime))
{
reportError(telemetry_error_suncalc_invalid_time, true);
return;
}
reportError(telemetry_error_suncalc_invalid_time, false);
// Check to see if the door state should change.
// NOTE: we do it this way because a blind setting of the state
// each time would make it impossible to remotely command the door
// because the DoorController would keep resetting it to the "correct"
// state each minute. So, we check for changes in the correct state and
// then tell the door motor where we want it. That way, if you close the door
// early, perhaps the birds have already cooped up, then it won't keep forcing
// the door back to open until sunset.
doorStateE newCorrectState =
timeIsBetween(currentTime, doorOpenTime, doorCloseTime) ? doorState_open : doorState_closed;
if(m_correctState != newCorrectState)
{
#ifdef COOPDOOR_CHANGE_BEEPER
if(newCorrectState)
g_beepController.beep(BEEP_FREQ_INFO, 900, 100, 2);
else
g_beepController.beep(BEEP_FREQ_INFO, 500, 500, 2);
#endif
m_correctState = newCorrectState;
command((m_correctState == doorState_open) ? doorCommand_open : doorCommand_close);
#ifdef DEBUG_DOOR_CONTROLLER
if(m_correctState == doorState_open)
DEBUG_SERIAL.println(F("CDoorController - opening coop door."));
else
DEBUG_SERIAL.println(F("CDoorController - closing coop door."));
#endif
}
#ifdef DEBUG_DOOR_CONTROLLER
if(m_correctState)
DEBUG_SERIAL.println(F("CDoorController - coop door should be OPEN."));
else
DEBUG_SERIAL.println(F("CDoorController - coop door should be CLOSED."));
doorStateE doorState = getDoorMotor()->getDoorState();
DEBUG_SERIAL.print(F("CDoorController - door motor reports: "));
DEBUG_SERIAL.println((doorState == doorState_open) ? F("open.") :
(doorState == doorState_closed) ? F("closed.") :
(doorState == doorState_moving) ? F("moving") :
(doorState == doorState_unknown) ? F("UNKNOWN.") :
F("*** INVALID ***"));
DEBUG_SERIAL.println();
#endif
}
void CDoorController::sendTelemetry()
{
double doorOpenTime = getDoorOpenTime();
double doorCloseTime = getDoorCloseTime();
// Update telemetry starting with config info
g_telemetry.transmissionStart();
g_telemetry.sendTerm(telemetry_tag_door_config);
g_telemetry.sendTerm((int)getSunriseOffset());
g_telemetry.sendTerm((int)getSunsetOffset());
g_telemetry.sendTerm((int)getStuckDoorDelay());
g_telemetry.transmissionEnd();
// Now, current times and door state
g_telemetry.transmissionStart();
g_telemetry.sendTerm(telemetry_tag_door_info);
g_telemetry.sendTerm(doorOpenTime);
g_telemetry.sendTerm(doorCloseTime);
g_telemetry.sendTerm((int)getDoorMotor()->getDoorState());
g_telemetry.transmissionEnd();
}
telemetrycommandResponseE CDoorController::command(doorCommandE _command)
{
// This had better work
if(!getDoorMotor())
{
#ifdef DEBUG_DOOR_CONTROLLER
DEBUG_SERIAL.println(F("CDoorController - command - *** NO DOOR MOTOR FOUND ***"));
#endif
return telemetry_cmd_response_nak_internal_error;
}
// Only accept valid commands
if((_command != doorCommand_open) && (_command != doorCommand_close))
return telemetry_cmd_response_nak_invalid_value;
// Remember the command for checking door response
m_command = _command;
telemetrycommandResponseE response = getDoorMotor()->command(_command);
if(response == telemetry_cmd_response_ack)
{
m_stuckDoorTimer.reset();
unsigned long stuckDoorMS = (unsigned long)(m_stuckDoorS * MILLIS_PER_SECOND);
m_stuckDoorTimer.start((unsigned long)stuckDoorMS);
}
return response;
}