-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathrolloff.ino.standard
542 lines (486 loc) · 16.6 KB
/
rolloff.ino.standard
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
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
/*
* A controller example for roof motor controller from the INDI rolloffino roof driver.
*
* tg August 2018 Original
* tg February 2020 Generalize to make less installation specific
* Add unspecified usage AUX switch and relay
* Communication protocol in terms of function not switches/relays
* ~ 15KB 2% of Due, 50% of Nano
* tg November 2021 Break out commandReceived and requestReceived to make alernate actions more
* obvious/accessible, Remove Due specific code.
*/
/*
* This version of the rolloff.ino is a generic example, intended as a starting place.
* Review additions to the other Arduino samples to see if they are a more appropriate base.
* The auxiliary button and light in the remote driver are of unspecified purpose.
*/
#define BAUD_RATE 38400
# define OPEN_CONTACT HIGH // Switch definition, Change to LOW if pull-down resistors are used.
// Define name to pin assignments
#define SWITCH_1 A0
#define SWITCH_2 A1
#define SWITCH_3 A2
#define SWITCH_4 A3
#define RELAY_1 4
#define RELAY_2 7
#define RELAY_3 8
#define RELAY_4 12
// Indirection to define a functional name in terms of a switch
// Use 0 if switch not implemented
#define SWITCH_OPENED SWITCH_1 // Fully opened is assigned to switch 1
#define SWITCH_CLOSED SWITCH_2 // Fully closed is assigned to switch 2
#define SWITCH_LOCKED SWITCH_3 // External lock
#define SWITCH_AUX SWITCH_4 // Auxiliary switch
// Indirection to define a functional name in terms of a relay
// Use 0 if function not supportd
#define FUNC_OPEN RELAY_1
#define FUNC_CLOSE RELAY_1 // For a single button controller might map this also to RELAY_1
#define FUNC_STOP RELAY_1 // For a single button controller might map this also to RELAY_1
#define FUNC_LOCK 0 // If automated roof lock is available.
#define FUNC_AUX 0 // Relay to perform some unspecified function
/*
* For the relay that the function is mapped to indicate if that relay is to be momentarily closed
* or held in a closed position.
* If for example the relay is intended to provide power to a motor the need might be to
* keep it closed until a relay off is received.
* If a function such as OPEN is held on, it will be up to local code in the Arduino to determine
* when and how to turn it off. The host sends open and close requests but expects the roof to
* brings its self to a stop when complete.
*
* If HOLD is 0 then the relay will be activated for RELAY_DELAY milliseconds, then released.
* If the relay is simulating a button push/release to the motor controller HOLD would be 0.
*/
#define FUNC_OPEN_HOLD 0
#define FUNC_CLOSE_HOLD 0
#define FUNC_STOP_HOLD 0
#define FUNC_LOCK_HOLD 1
#define FUNC_AUX_HOLD 1
#define RELAY_PRIOR_DELAY 50
#define RELAY_ON_DELAY 500
#define RELAY_POST_DELAY 50
/*
* Abort (stop) request is only meaningful if roof is in motion.
*
* On Abort for a single button controller, only want to activate relay and pulse the controller if
* the roof is still moving, then it would stop. If it has already reached the end of its
* travel, a pulse could set it off again in the opposite direction.
*
* In case the end of run switches are not reached, some way to know if it is moving
* would be helpful. Short of that estimate how long it takes the roof to open or close
*/
#define ROOF_OPEN_MILLI 15000
// Buffer limits
#define MAX_INPUT 45
#define MAX_RESPONSE 127
#define MAX_MESSAGE 63
enum cmd_input {
CMD_NONE,
CMD_OPEN,
CMD_CLOSE,
CMD_STOP,
CMD_LOCK,
CMD_AUXSET
} command_input;
unsigned long timeMove = 0;
const int cLen = 15;
const int tLen = 15;
const int vLen = MAX_RESPONSE;
char command[cLen+1];
char target[tLen+1];
char value[vLen+1];
// Maximum length of messages = 63 *|
const char* ERROR1 = "The controller response message was too long";
const char* ERROR2 = "The controller failure message was too long";
const char* ERROR3 = "Command input request is too long";
const char* ERROR4 = "Invalid command syntax, both start and end tokens missing";
const char* ERROR5 = "Invalid command syntax, no start token found";
const char* ERROR6 = "Invalid command syntax, no end token found";
const char* ERROR7 = "Roof controller unable to parse command";
const char* ERROR8 = "Command must map to either set a relay or get a switch";
const char* ERROR9 = "Request not implemented in controller";
const char* ERROR10 = "Abort command ignored, roof already stationary";
const char* VERSION_ID = "V1.2-0";
void sendAck(char* val)
{
char response [MAX_RESPONSE];
if (strlen(val) > MAX_MESSAGE)
sendNak(ERROR1);
else
{
strcpy(response, "(ACK:");
strcat(response, target);
strcat(response, ":");
strcat(response, val);
strcat(response, ")");
Serial.println(response);
Serial.flush();
}
}
void sendNak(const char* errorMsg)
{
char buffer[MAX_RESPONSE];
if (strlen(errorMsg) > MAX_MESSAGE)
sendNak(ERROR2);
else
{
strcpy(buffer, "(NAK:ERROR:");
strcat(buffer, value);
strcat(buffer, ":");
strcat(buffer, errorMsg);
strcat(buffer, ")");
Serial.println(buffer);
Serial.flush();
}
}
/*
* Using a SainSmart 4 channel, 5V relay module. The relay module's JD-VCC jumper is removed. A separate 5V supply
* powers the relay module using the GND and JD-VCC pins where the jumper was removed. The Arduino is powered from
* the USB connection. Also no common ground between the Arduino and the relay module so pull the ground connector.
* No external pullup resistor is used on the Arduino's output pin. The relay output uses the NO and common connectors.
* The relay requires a LOW on the signal pin to activate, This is provided when a ON command is received. Default input
* HIGH = inactive relay, open contact.
*
* hold 1 indicates to hold relay closed until a relay OFF is received.
* hold 0 indicates a momentary close is wanted
*/
void setRelay(int id, int hold, char* value)
{
if (strcmp(value, "ON") == 0)
{
digitalWrite(id, LOW); // NO RELAY would normally already be in this condition (open)
delay(RELAY_PRIOR_DELAY);
digitalWrite(id, HIGH); // Activate the NO relay (close it)
if (hold == 0)
{
delay(RELAY_ON_DELAY);
digitalWrite(id, LOW); // Turn NO relay off
}
}
else
{
digitalWrite(id, LOW); // Turn NO relay off
}
delay(RELAY_POST_DELAY);
}
/*
* Get switch value
* Expect a NO switch configured with a pull up resistor.
* NO switch: Inactive HIGH input to the pin with pull up resistor, logical 0 input.
* When switch closes The LOW voltage logical 1 is applied to the input pin.
* The off or on value is to be sent to the host in the ACK response
*/
void getSwitch(int id, char* value)
{
if (digitalRead(id) == OPEN_CONTACT)
strcpy(value, "OFF");
else
strcpy(value, "ON");
}
bool isSwitchOn(int id)
{
char switch_value[16+1];
getSwitch(id, switch_value);
if (strcmp(switch_value, "ON") == 0)
{
return true;
}
return false;
}
bool parseCommand() // (command:target:value)
{
bool start = false;
bool eof = false;
int recv_count = 0;
int wait = 0;
int offset = 0;
char startToken = '(';
char endToken = ')';
const int bLen = MAX_INPUT;
char inpBuf[bLen+1];
memset(inpBuf, 0, sizeof(inpBuf));
memset(command, 0, sizeof(command));
memset(target, 0, sizeof(target));
memset(value, 0, sizeof(value));
while (!eof && (wait < 20))
{
if (Serial.available() > 0)
{
Serial.setTimeout(1000);
recv_count = Serial.readBytes((inpBuf + offset), 1);
if (recv_count == 1)
{
offset++;
if (offset >= MAX_INPUT)
{
sendNak(ERROR3);
return false;
}
if (inpBuf[offset-1] == startToken)
{
start = true;
}
if (inpBuf[offset-1] == endToken)
{
eof = true;
inpBuf[offset] = '\0';
}
continue;
}
}
wait++;
delay(100);
}
if (!start || !eof)
{
if (!start && !eof)
sendNak(ERROR4);
else if (!start)
sendNak(ERROR5);
else if (!eof)
sendNak(ERROR6);
return false;
}
else
{
strcpy(command, strtok(inpBuf,"(:"));
strcpy(target, strtok(NULL,":"));
strcpy(value, strtok(NULL,")"));
if ((strlen(command) >= 3) && (strlen(target) >= 1) && (strlen(value) >= 1))
{
return true;
}
else
{
sendNak(ERROR7);
return false;
}
}
}
/*
* Use the parseCommand routine to decode message
* Determine associated action in the message. Resolve the relay or switch associated
* pin with the target identity. Acknowledge any initial connection request. Return
* negative acknowledgement with message for any errors found. Dispatch to commandReceived
* or requestReceived routines to activate the command or get the requested switch state
*/
void readUSB()
{
// Confirm there is input available, read and parse it.
if (Serial && (Serial.available() > 0))
{
if (parseCommand())
{
unsigned long timeNow = millis();
int hold = 0;
int relay = -1; // -1 = not found, 0 = not implemented, pin number = supported
int sw = -1; // " " "
bool connecting = false;
const char* error = ERROR8;
// On initial connection return the version
if (strcmp(command, "CON") == 0)
{
connecting = true;
strcpy(value, VERSION_ID); // Can be seen on host to confirm what is running
sendAck(value);
}
// Map the general input command term to the local action
// SET: OPEN, CLOSE, ABORT, LOCK, AUXSET
else if (strcmp(command, "SET") == 0)
{
// Prepare to OPEN
if (strcmp(target, "OPEN") == 0)
{
command_input = CMD_OPEN;
relay = FUNC_OPEN;
hold = FUNC_OPEN_HOLD;
timeMove = timeNow;
}
// Prepare to CLOSE
else if (strcmp(target, "CLOSE") == 0)
{
command_input = CMD_CLOSE;
relay = FUNC_CLOSE;
hold = FUNC_CLOSE_HOLD;
timeMove = timeNow;
}
// Prepare to ABORT
else if (strcmp(target, "ABORT") == 0)
{
command_input = CMD_STOP;
// Test whether or not to Abort
if (!isStopAllowed())
{
error = ERROR10;
}
else
{
relay = FUNC_STOP;
hold = FUNC_STOP_HOLD;
}
}
// Prepare for the Lock function
else if (strcmp(target, "LOCK") == 0)
{
command_input = CMD_LOCK;
relay = FUNC_LOCK;
hold = FUNC_LOCK_HOLD;
}
// Prepare for the Auxiliary function
else if (strcmp(target, "AUXSET") == 0)
{
command_input = CMD_AUXSET;
relay = FUNC_AUX;
hold = FUNC_AUX_HOLD;
}
}
// Handle requests to obtain the status of switches
// GET: OPENED, CLOSED, LOCKED, AUXSTATE
else if (strcmp(command, "GET") == 0)
{
if (strcmp(target, "OPENED") == 0)
sw = SWITCH_OPENED;
else if (strcmp(target, "CLOSED") == 0)
sw = SWITCH_CLOSED;
else if (strcmp(target, "LOCKED") == 0)
sw = SWITCH_LOCKED;
else if (strcmp(target, "AUXSTATE") == 0)
sw = SWITCH_AUX;
}
/*
* See if there was a valid command or request
*/
if (!connecting)
{
if ((relay == -1) && (sw == -1))
{
sendNak(error); // Unknown input or Abort command was rejected
}
// Command or Request not implemented
else if ((relay == 0 || relay == -1) && (sw == 0 || sw == -1))
{
strcpy(value, "OFF"); // Request Not implemented
//sendNak(ERROR9);
sendAck(value);
}
// Valid input received
// A command was received
// Set the relay associated with the command and send acknowlege to host
else if (relay > 0) // Set Relay response
{
commandReceived(relay, hold, value);
}
// A state request was received
else if (sw > 0) // Get switch response
{
requestReceived(sw);
}
} // end !connecting
} // end command parsed
} // end Serial input found
}
////////////////////////////////////////////////////////////////////////////////
// Abort movement command received, test to see if abort is allowed.
// If not return false and an error message will be returned to the host. If yes then return true.
// If either fully open or fully closed switches are on then deny the request by returning false.
// If neither switch is on then if there is a specific button (relay) assigned that can stop movement then return true
// to allow it to do so.
//
// This implementation assumes a one button setup and one which does not know if the roof is still moving or
// has hit something and already stopped. Before taking action see how long it has been since movement was initiated.
// If it is longer than the estimate to open or close the roof, assume motion has already stopped. In this case avoid
// emulating the single button push because that would set the roof moving again. If it seems that the roof
// could be moving then return true.
//
// Returning true will cause the Abort request to appear in the commandReceived routine where it will activate
// the requested relay.
//
bool isStopAllowed()
{
unsigned long timeNow = millis();
// If the roof is either fully opened or fully closed, ignore the request.
if (isSwitchOn(SWITCH_OPENED) || isSwitchOn(SWITCH_CLOSED))
{
return false;
}
// If time since last open or close request is longer than the time for the roof travel return false
if ((timeNow - timeMove) >= ROOF_OPEN_MILLI)
{
return false;
}
else
// Stop will be attempted
{
return true;
}
}
////////////////////////////////////////////////////////////////////////////////
// Action command received
// Here after pin associations resolved and request action known
// Default action is to set the associated relay to the requested state "ON" or "OFF" and
// send acknowledgement to the host.
// target is the name associated with the relay "OPEN", "CLOSE", "STOP", "LOCK", "AUXSET".
// It will be used when sending the acknowledgement to the host. Find out if a particular
// command is being processed using if (strcmp(target, "OPEN") == 0) {do something}
//
// relay: pin id of the relay
// hold: whether relay is to be set permanently =0, or temporarily =1
// value: How to set the relay "ON" or "OFF"
//
//
void commandReceived(int relay, int hold, char* value)
{
setRelay(relay, hold, value);
sendAck(value); // Send acknowledgement that relay pin associated with "target" was activated to value requested
}
////////////////////////////////////////////////////////////////////////////////
// Here after pin associations resolved and request action known
// Request to obtain state of a switch
// Default action is to read the associated switch and return result to the host
// target is the name associated with the switch "OPENED", "CLOSED" etc and will
// be used when sending the acknowledgement to the host. Find out if a certain request is being processed using
// if (strcmp(target, "OPENED") == 0) {do something}
//
// sw: The switch's pin identifier.
// value getSwitch will read the pin and set this to "ON" or "OFF"
void requestReceived(int sw)
{
getSwitch(sw, value);
sendAck(value); // Send result of reading pin associated with "target"
}
// One time initialization
void setup()
{
// Initialize the input switches
pinMode(SWITCH_1, INPUT_PULLUP);
pinMode(SWITCH_2, INPUT_PULLUP);
pinMode(SWITCH_3, INPUT_PULLUP);
pinMode(SWITCH_4, INPUT_PULLUP);
// Initialize the relays
//Pin Setups
pinMode(RELAY_1, OUTPUT);
pinMode(RELAY_2, OUTPUT);
pinMode(RELAY_3, OUTPUT);
pinMode(RELAY_4, OUTPUT);
//Turn Off the relays.
digitalWrite(RELAY_1, LOW);
digitalWrite(RELAY_2, LOW);
digitalWrite(RELAY_3, LOW);
digitalWrite(RELAY_4, LOW);
// Establish USB port.
Serial.begin(BAUD_RATE); // Baud rate to match that in the driver
}
// Wait here for command or switch request from host
void loop()
{
while (Serial.available() <= 0)
{
for (int cnt=0; cnt < 60; cnt++)
{
if (Serial.available() > 0)
break;
else
delay(100);
}
}
readUSB();
} // end loop