-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGarageDoorControl.ino
460 lines (405 loc) · 14.4 KB
/
GarageDoorControl.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
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
#include <BME280.h>
#include <BME280I2C.h>
#include <EnvironmentCalculations.h>
#include <Wire.h>
#include <MNPCIHandler.h>
#include <MNRGBLEDBaseLib.h>
#include <MNTimerLib.h>
#include <WiFiNINA.h>
#include <WiFiUdp.h>
#include <time.h>
#include "GarageControl.h"
#include "WiFiService.h"
#include "logging.h"
#include "Display.h"
/*
GarageDoorControl.ino
Arduino sketch to control a Hormann Garage door via a Horman UAP1
Also collects the temperature, humidity and barometric pressure
Door state and humidity can be queried by remote client sending and receiving UDP messages - see WiFiService files
This is designed to use two RGB LEDS to display status
An external RGB LED displays the door state when we are connected to a UAP
WHITE, no flash - state unknown
GREEN, no flash - Door Closed
GREEN, flashing - Door Closing
RED, no flash - Door Open
RED, flashing - Door Opening
PURPLE, no flash - Door Stopped
BLUE, flashing - Door unknown state, not open and not closed ie in transit but we cannot determine which
YELLOW, flashing - Door in a BAD State, UAP says open and closed at same time.
When not connected to a UAP the colours indicate how close to teh desired humidity threshold it is
Green - at desired level, the more red the drier and the more blue the more humid it is. Will flash when above min or max threshold
The built in MKR WiFi 1010 RGB LED displays the WiFI status
Author: (c) M. Naylor 2022
History:
Ver 1.0 Initial version
Ver 1.0.4 Supports config to add/remove UAP, Distance Centre, Barometruc sensor
Ver 1.0.5 Add ability to get loggging data over a telnet connection on 0xFEEE
Ver 1.0.6 Changed input pins to be simply INPUT and use external pulldown resistors
Ver 1.0.7 Moved all input and out pins into DoorState, added InputPin class
Ver 1.0.8 Moved logging to object SerialLogger
Ver 1.0.10 Added BME280 support and changed logging to inherit from Stream class
Ver 1.0.11 Added external LED usage in Non UAP mode to show how far from desired humidity we are
Ver 1.0.12 Detect door state in main loop rather than calc on pin change
Ber 1.0.13 Improved encapsulation of InputPin
Ver 1.0.15 Moved display code to own file
*/
const char * VERSION = "1.0.15 Beta";
#ifdef MNDEBUG
#ifdef TELNET
ansiVT220Logger MyLogger ( Telnet );
#else
SerialLogger slog;
ansiVT220Logger MyLogger ( slog ); // create serial comms object to log to
#endif
#endif
#ifdef BME280_SUPPORT // Temp, humidity and pressure sensor
struct TEMP_STATS
{
float temperature;
float pressure; // at sea level
float humidity;
float dewpoint;
uint32_t ulTimeOfReadingms;
} EnvironmentResults = { NAN, NAN, NAN, 0UL };
constexpr float ALTITUDE_COMPENSATION = 131.0; // sensor is 135 metres aboves sea level, we need this to adjust pressure reading to sea level equivalent.
BME280I2C::Settings settings ( BME280::OSR_X2, BME280::OSR_X2, BME280::OSR_X2, BME280::Mode_Normal, BME280::StandbyTime_250ms, BME280::Filter_Off, BME280::SpiEnable_False, BME280I2C::I2CAddr_0x76 );
BME280I2C MyBME280 ( settings );
#endif
#ifdef UAP_SUPPORT
#include "DoorState.h"
// PIN allocations, input & output from arduino perspective
// Need to be interrupt pins, inputs of status from UAP
constexpr pin_size_t DOOR_IS_OPEN_STATUS_PIN = 9;
constexpr uint8_t DOOR_IS_CLOSED_STATUS_PIN = 8;
constexpr uint8_t LIGHT_IS_ON_STATUS_PIN = 7;
constexpr uint8_t DOOR_SWITCH_INPUT_PIN = 0;
// Don't need to be interrupt pins, outputs to UAP
constexpr uint8_t TURN_LIGHT_ON_OUTPUT_PIN = 2;
constexpr uint8_t CLOSE_DOOR_OUTPUT_PIN = 3;
constexpr uint8_t OPEN_DOOR_OUTPUT_PIN = 4;
constexpr uint8_t STOP_DOOR_OUTPUT_PIN = 5;
//constexpr uint32_t SWITCH_DEBOUNCE_MS = 100; // min ms between consecutive pin interrupts before signal accepted from manual switch
//constexpr uint32_t MAX_SWITCH_MATCH_TIMER_MS = 2000; // max time pin should be in matched state to be considered a real signal
DoorState *pGarageDoor = nullptr;
//DoorStatusPin *pDoorSwitchPin = nullptr;
#endif
constexpr pin_size_t RED_PIN = PIN_A4;
constexpr pin_size_t GREEN_PIN = PIN_A3;
constexpr pin_size_t BLUE_PIN = 10;
MNRGBLEDBaseLib *pMyLED = new CRGBLED ( RED_PIN, GREEN_PIN, BLUE_PIN, 255, 180, 120 ); // new CRGBLED ( RED_PIN, GREEN_PIN, BLUE_PIN, 255, 90, 60 );
/*
WiFi config
*/
constexpr char ssid [] = "Naylorfamily"; // your network SSID (name)
constexpr char pass [] = "welcome1"; // your network password
#ifdef UAP_SUPPORT
constexpr char MyHostName [] = "GarageControl2";
#else
constexpr char MyHostName [] = "OfficeTHSensor";
#endif
UDPWiFiService *pMyUDPService = nullptr;
unsigned long ulLastClientReq = 0UL; // millis of last wifi incoming message
// main setup routine
void setup ()
{
//Serial.begin ( 115200 );
//while ( !Serial )
;
MyLogger.LogStart ();
MyLogger.ClearScreen ();
pMyUDPService = new UDPWiFiService ();
// now we have state table set up and temp sensor configured, allow users to query state
TheMKR_RGB_LED.Invert (); // Only if required!
if ( !pMyUDPService->Begin ( ProcessUDPMsg, ssid, pass, MyHostName, &TheMKR_RGB_LED ) )
{
Error ( F ( "Cannot connect WiFI " ) );
}
#ifdef BME280_SUPPORT
Wire.begin ();
if ( !MyBME280.begin () )
{
Error ( F ( "Could not find BME280 sensor!" ) );
delay ( 1000 );
}
else
{
switch ( MyBME280.chipModel () )
{
case BME280::ChipModel_BME280:
Info ( F ( "Found BME280 sensor! Success." ) );
break;
case BME280::ChipModel_BMP280:
Info ( F ( "Found BMP280 sensor! No Humidity available." ) );
break;
default:
Error ( F ( "Found UNKNOWN sensor! Error!" ) );
}
}
DisplaylastInfoErrorMsg ();
#endif
#ifdef UAP_SUPPORT
// Setup so we are called if the state of door changes
pGarageDoor = new DoorState ( OPEN_DOOR_OUTPUT_PIN, CLOSE_DOOR_OUTPUT_PIN, STOP_DOOR_OUTPUT_PIN, TURN_LIGHT_ON_OUTPUT_PIN, DOOR_IS_OPEN_STATUS_PIN, DOOR_IS_CLOSED_STATUS_PIN, LIGHT_IS_ON_STATUS_PIN, DOOR_SWITCH_INPUT_PIN );
// pDoorSwitchPin = new DoorStatusPin ( pGarageDoor, DoorState::Event::SwitchPress, DoorState::Event::Nothing, DOOR_SWITCH_INPUT_PIN, SWITCH_DEBOUNCE_MS, PinStatus::HIGH, PinMode::INPUT_PULLDOWN, PinStatus::CHANGE );
//pDoorSwitchPin = new DoorStatusPin ( pGarageDoor, DoorState::Event::Nothing, DoorState::Event::SwitchPress, DOOR_SWITCH_INPUT_PIN, SWITCH_DEBOUNCE_MS, MAX_SWITCH_MATCH_TIMER_MS, PinStatus::HIGH, PinMode::INPUT_PULLDOWN, PinStatus::CHANGE );
SetLED ();
#endif
}
#ifdef UAP_SUPPORT
// set the colour of the inbuilt MKR Wifi 1010 RGB LED based on the current door state
void SetLED ()
{
static DoorState::State OldState = DoorState::State::Opening;
DoorState::State currentState = DoorState::State::Unknown;
if ( pGarageDoor != nullptr )
{
currentState = pGarageDoor->GetDoorState ();
}
if ( currentState != OldState )
{
OldState = currentState;
switch ( currentState )
{
case DoorState::State::Closed:
pMyLED->SetLEDColour ( DOOR_CLOSED_COLOUR, DOOR_STATIONARY_FLASHTIME );
break;
case DoorState::State::Closing:
pMyLED->SetLEDColour ( DOOR_CLOSED_COLOUR, DOOR_MOVING_FLASHTIME );
break;
case DoorState::State::Open:
pMyLED->SetLEDColour ( DOOR_OPEN_COLOUR, DOOR_STATIONARY_FLASHTIME );
break;
case DoorState::State::Opening:
pMyLED->SetLEDColour ( DOOR_OPEN_COLOUR, DOOR_MOVING_FLASHTIME );
break;
case DoorState::State::Stopped:
pMyLED->SetLEDColour ( DOOR_STOPPED_COLOUR, DOOR_STATIONARY_FLASHTIME );
break;
case DoorState::State::Bad:
pMyLED->SetLEDColour ( DOOR_BAD_COLOUR, DOOR_MOVING_FLASHTIME );
break;
case DoorState::State::Unknown:
pMyLED->SetLEDColour ( DOOR_UNKNOWN_COLOUR, DOOR_MOVING_FLASHTIME );
break;
}
}
}
#else
// When not showing the door (UAP) status then show the humidity status
void SetLED ()
{
uint8_t red, green, blue;
bool bOutsideRange = false;
uint8_t Flashtime = 0U;
float constrainedHumidity;
static float OldHumidity = NAN;
// calculate color component
constexpr float HUMIDITY_MAX = 60.0;
constexpr float HUMIDITY_MIN = 40.0;
constexpr float HUMIDITY_MID = 50.0;
constexpr uint32_t OUTSIDE_RANGE_FLASHTIME = 10U;
if ( EnvironmentResults.humidity == OldHumidity )
{
return;
}
else
{
OldHumidity = EnvironmentResults.humidity;
}
constrainedHumidity = max ( EnvironmentResults.humidity, HUMIDITY_MIN );
constrainedHumidity = min ( constrainedHumidity, HUMIDITY_MAX );
if ( EnvironmentResults.humidity > HUMIDITY_MAX || EnvironmentResults.humidity < HUMIDITY_MIN )
{
Flashtime = OUTSIDE_RANGE_FLASHTIME;
}
// red level indicates how dry
if ( constrainedHumidity < HUMIDITY_MID )
{
red = ( HUMIDITY_MID - constrainedHumidity ) * 255.0 / ( HUMIDITY_MID - HUMIDITY_MIN );
}
else
{
red = 0;
}
// blue level indicates how wet
if ( EnvironmentResults.humidity > HUMIDITY_MID )
{
blue = ( constrainedHumidity - HUMIDITY_MID ) * 255.0 / ( HUMIDITY_MAX - HUMIDITY_MID );
}
else
{
blue = 0;
}
// green level indicates how close to wanted level
green = 255.0 - ( ( abs ( constrainedHumidity - HUMIDITY_MID ) * 255.0 ) / ( ( HUMIDITY_MAX - HUMIDITY_MIN ) / 2.0 ) );
pMyLED->SetLEDColour ( RGB ( red, green, blue ), Flashtime );
MyLogger.AT ( 3, 1, "Red :" );
MyLogger.AT ( 4, 1, "Green :" );
MyLogger.AT ( 5, 1, "Blue :" );
MyLogger.ClearPartofLine ( 3, 8, 3 );
MyLogger.ClearPartofLine ( 4, 8, 3 );
MyLogger.ClearPartofLine ( 5, 8, 3 );
MyLogger.AT ( 3, 8, String ( red ) );
MyLogger.AT ( 4, 8, String ( green ) );
MyLogger.AT ( 5, 8, String ( blue ) );
}
#endif
// main loop function
void loop ()
{
#ifdef BME280_SUPPORT
float pres ( NAN ), temp ( NAN ), hum ( NAN );
static unsigned long ulLastSensorTime = millis () - ( 30UL * 1000UL );
#endif
static unsigned long ulLastDisplayTime = 0UL;
#ifdef UAP_SUPPORT
static DoorState::State LastDoorState = DoorState::Unknown;
static bool LastLightState = false;
// set initial light state
if ( pGarageDoor != nullptr && ulLastDisplayTime == 0UL )
{
LastLightState = !pGarageDoor->IsLit ();
}
#endif
// set LED
SetLED ();
// See if we have any udp requests to action
pMyUDPService->CheckUDP ();
#ifdef BME280_SUPPORT
if ( millis () - ulLastSensorTime > 30 * 1000 )
{
MyBME280.read ( EnvironmentResults.pressure, EnvironmentResults.temperature, EnvironmentResults.humidity, BME280::TempUnit::TempUnit_Celsius, BME280::PresUnit::PresUnit_hPa );
//Info ( "Temperature: " + String ( EnvironmentResults.temperature ) + "C" );
EnvironmentResults.pressure = EnvironmentCalculations::EquivalentSeaLevelPressure ( ALTITUDE_COMPENSATION, EnvironmentResults.temperature, EnvironmentResults.pressure );
EnvironmentResults.dewpoint = EnvironmentCalculations::DewPoint ( EnvironmentResults.temperature, EnvironmentResults.humidity );
EnvironmentResults.ulTimeOfReadingms = pMyUDPService->GetTime ();
MulticastMsg ( UDPWiFiService::ReqMsgType::TEMPDATA );
// reset time counter
ulLastSensorTime = millis ();
}
#endif
// update debug stats every 1/2 second
if ( millis () - ulLastDisplayTime > 500 )
{
ulLastDisplayTime = millis ();
DisplayStats ();
}
#ifdef UAP_SUPPORT
// if door state has changed, multicast news
if ( pGarageDoor != nullptr )
{
pGarageDoor->UpdateDoorState ();
if ( pGarageDoor->GetDoorState () != LastDoorState || LastLightState != pGarageDoor->IsLit () )
{
LastDoorState = pGarageDoor->GetDoorState ();
LastLightState = pGarageDoor->IsLit ();
MulticastMsg ( UDPWiFiService::ReqMsgType::DOORDATA );
}
}
if ( pGarageDoor->IsSwitchConfigured() && pMyUDPService != nullptr )
{
static uint16_t SwitchPressedCount = 0;
uint16_t LatestSwitchPressedCount = pGarageDoor->GetSwitchMatchCount ();
if ( LatestSwitchPressedCount > SwitchPressedCount )
{
SwitchPressedCount = LatestSwitchPressedCount;
}
}
#endif
}
// called to generate a response to a command
// returns an empty string if no response required ie only do action
void BuildMessage ( UDPWiFiService::ReqMsgType eReqType, String &sResponse )
{
switch ( eReqType )
{
case UDPWiFiService::ReqMsgType::TEMPDATA:
#ifdef BME280_SUPPORT
sResponse = F ( "T=" );
sResponse += EnvironmentResults.temperature;
sResponse += F ( ",H=" );
sResponse += EnvironmentResults.humidity;
sResponse += F ( ",D=" );
sResponse += EnvironmentResults.dewpoint;
sResponse += F ( ",P=" );
sResponse += EnvironmentResults.pressure;
sResponse += F ( ",A=" );
sResponse += EnvironmentResults.ulTimeOfReadingms;
sResponse += F ( "\r" );
#endif
break;
#ifdef UAP_SUPPORT
case UDPWiFiService::ReqMsgType::DOORDATA:
if ( pGarageDoor != nullptr )
{
sResponse = F ( "S=" );
sResponse += pGarageDoor->GetDoorDisplayState (); // Door State
sResponse += F ( ",L=" );
sResponse += pGarageDoor->IsLit () ? F ( "On" ) : F ( "Off" ); // Light on or not
sResponse += F ( ",C=" );
sResponse += pGarageDoor->IsClosed () ? F ( "Y" ) : F ( "N" ); // Closed or not
sResponse += F ( ",O=" );
sResponse += pGarageDoor->IsOpen () ? F ( "Y" ) : F ( "N" ); // Open or not
sResponse += F ( ",M=" );
sResponse += pGarageDoor->IsMoving () ? F ( "Y" ) : F ( "N" ); // Moving or not
sResponse += F ( ",A=" );
sResponse += pMyUDPService->GetTime (); // current epoch time
sResponse += F ( "\r" );
}
else
{
Error ( F ( "Door data unavailable: pGarageDoor is null" ) );
}
break;
case UDPWiFiService::ReqMsgType::DOOROPEN:
if ( pGarageDoor != nullptr )
{
pGarageDoor->DoRequest ( DoorState::Request::OpenDoor );
}
break;
case UDPWiFiService::ReqMsgType::DOORCLOSE:
if ( pGarageDoor != nullptr )
{
pGarageDoor->DoRequest ( DoorState::Request::CloseDoor );
}
break;
case UDPWiFiService::ReqMsgType::DOORSTOP:
if ( pGarageDoor != nullptr )
{
pGarageDoor->DoRequest ( DoorState::Request::StopDoor );
}
break;
case UDPWiFiService::ReqMsgType::LIGHTON:
if ( pGarageDoor != nullptr )
{
pGarageDoor->DoRequest ( DoorState::Request::LightOn );
}
break;
case UDPWiFiService::ReqMsgType::LIGHTOFF:
if ( pGarageDoor != nullptr )
{
pGarageDoor->DoRequest ( DoorState::Request::LightOff );
}
break;
#endif
}
}
void MulticastMsg ( UDPWiFiService::ReqMsgType eReqType )
{
String sResponse;
BuildMessage ( eReqType, sResponse );
if ( sResponse.length () > 0 )
{
pMyUDPService->SendAll ( sResponse );
}
}
void ProcessUDPMsg ( UDPWiFiService::ReqMsgType eReqType )
{
String sResponse;
BuildMessage ( eReqType, sResponse );
if ( sResponse.length () > 0 )
{
pMyUDPService->SendReply ( sResponse );
}
}