-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontrol.cpp
More file actions
620 lines (558 loc) · 15.3 KB
/
control.cpp
File metadata and controls
620 lines (558 loc) · 15.3 KB
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
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
/**
* Task based handler for receiving input from various devices.
*/
#include <Arduino.h>
#include "config.h"
#include "utils.h"
#include "control.h"
// ####################### SerialIn class definitions ######################
/**
* Constructor.
*/
SerialIn::SerialIn() : Task() {
_repeat = _in = _lastRx = 0; // Initialise all vars.
_newInput = false;
// Open the serial port with default speed.
OpenSerial();
#ifdef DEBUG
Serial << F("Starting SerialIn task...\n");
#endif // DEBUG
}
/**
* Tests if we have any input to process.
*/
bool SerialIn::canRun(uint32_t now) {
return Serial.available();
}
/**
* Processes any new input received.
*
* This method will only be called if serial input is available (the canRun()
* method ensures this).
*
* @param now THe current millis() counter.
*/
void SerialIn::run(uint32_t now) {
// Read the input;
char c = (char)Serial.read();
// Calculate the time since the last input was received
uint32_t rxInterval = now - _lastRx;
// Is the interval between the last received char and this one less than
// the min delay allowed between input chars?
if(rxInterval < SI_MIN_DELAY) {
// Too quick. Ignore it
#ifdef DEBUG
Serial << "Serial min delay exceeded. Ignoring input...\n";
#endif //DEBUG
return;
}
// Update the last received timer
_lastRx = now;
// Is it a repeat of the previous input and are we still within the allowed
// repeat time?
if (c==_in && rxInterval<=SI_REPEAT_MAX) {
_repeat++;
} else {
_repeat = 0;
_in = c;
}
_newInput = true;
}
/**
* Checks if there is new input available and returns this input and any possible
* repeat counts (via pointer args).
*
* NOTE: After calling this method, the input is reset and will not be available
* again on a successive call unless new input has arrived. The caller is
* responsible for processing the input after this call.
*
* @param c A pointer to a char type that will be set to the new char received
* if there is anything new.
* @param rep A pointer to a uint_8 that will be set to the repeat count for how
* many times this input char was repeated within the SI_REPEAT_MAX time
* between repeats.
*
* @return True if new input is available, or False otherwise.
*/
bool SerialIn::newInput(char *c, uint8_t *rep) {
if (!_newInput)
return false;
*c = _in;
*rep = _repeat;
_newInput = false;
return true;
}
// ####################### IrIn class definitions ######################
/**
* Constructor.
*/
IrIn::IrIn(uint8_t pin) : Task(),
_pin(pin) {
_repeat = _lastRx = _lastCode = 0; // Initialise all vars.
_newInput = false;
// Create the IR receiver instance
_irRecv = new IRrecv(_pin);
_irRecv->enableIRIn();
#ifdef DEBUG
OpenSerial();
Serial << F("Starting IrIn task...\n");
#endif // DEBUG
}
/**
* Tests if we have any input to process.
*/
bool IrIn::canRun(uint32_t now) {
// Try to decode any input we may have
if (_irRecv->decode(&_irRes)) {
// Get ready to receive the next input
_irRecv->resume();
return true;
}
return false;
}
/**
* Processes any new input received.
*
* This method will only be called if serial input is available (the canRun()
* method ensures this).
*
* @param now The current millis() counter.
*/
void IrIn::run(uint32_t now) {
// We already have the new decoded input in _irRes.
// Calculate the time since the last input was received
uint32_t rxInterval = now - _lastRx;
// Is the interval between the last received code and this one less than
// the min delay allowed between input?
if(rxInterval < IR_MIN_DELAY) {
// Too quick. Ignore it
#ifdef DEBUG
Serial << "IR min delay exceeded. Ignoring input...\n";
#endif //DEBUG
return;
}
// Update the last received timer
_lastRx = now;
// Is it a repeat of the previous input and are we still within the allowed
// repeat time?
if (_irRes.value==REPEAT && rxInterval<=IR_REPEAT_MAX) {
_repeat++;
} else {
_repeat = 0;
_lastCode = _irRes.value;
}
_newInput = true;
}
/**
* Checks if there is new input available and returns this input and any possible
* repeat counts (via pointer args).
*
* NOTE: After calling this method, the input is reset and will not be available
* again on a successive call unless new input has arrived. The caller is
* responsible for processing the input after this call.
*
* @param c A pointer to a uint32_t type that will be set to the new code received
* if there is anything new.
* @param rep A pointer to a uint8_t that will be set to the repeat count for how
* many times this input code was repeated within the IR_REPEAT_MAX time
* between repeats.
*
* @return True if new input is available, or False otherwise.
*/
bool IrIn::newInput(uint32_t *c, uint8_t *rep) {
if (!_newInput)
return false;
// Always use _lastCode because _irRes.value may be the REPEAT value
*c = _lastCode;
*rep = _repeat;
_newInput = false;
return true;
}
// ####################### InputDecoder class definitions ######################
/**
* Constructor.
*/
InputDecoder::InputDecoder(SerialIn *si, IrIn *ii) : Task(),
_serialIn(si), _irIn(ii) {
// Open the serial port if we have not done so already.
OpenSerial();
// Preset local variables
_learnMode = false;
_learnStep = 0;
_newCmd = false;
_repeat = 0;
}
/**
* Method used to learn which input commands to associate with which commands.
*
* This method will be called once as soon as a "learn command" was received
* (CMD_LRN) by the run() method. The method will then set the internal object
* state to "learn mode" and intercept any new input until all commands have
* been learned or until the user decides to quit.
*
* @param now The time value we receive from the task scheduler vi the run()
* method.
*/
void InputDecoder::_learn(uint32_t now) {
static uint8_t learnCmd = 0; // Command number currently learning
static uint8_t learnInput = 0; // Type of input being learned: INP_SERIAL|INP_IR
int i;
// Set next timeout - 30 secs. The canRun() method will handle timeouts.
_learnTimeout = now + 30000;
// If we are at step 0, it is a new call for learning.
STEP0:
if (_learnStep==0) {
// First set learn mode on.
_learnMode = true;
// Reset the learn command tracker
learnCmd = 0;
// Ask what input to learn
Serial << F("Train key or IR codes (k/i/q) ? ");
// Get ready for next step
_learnStep++;
// Return and wait for next input
return;
}
if (_learnStep==1) {
// Here we only want serial input
if (_whatAvail!=INP_SERIAL) {
Serial << F("\nOnly key (serial) input allowed. Try again...\n");
_learnStep = 0;
goto STEP0;
}
// What input did we get?
switch (_serIn) {
case 'k':
Serial << F("\nLearning key codes.");
learnInput = INP_SERIAL;
break;
case 'i':
Serial << F("\nLearning IR codes.");
learnInput = INP_IR;
break;
case 'q':
case ESC_KEY:
Serial << F("Quiting...\n");
_learnMode = false;
_learnStep = 0;
return;
break;
default:
Serial << F("\nNot a valid answer. Please try again.\n");
_learnStep = 0;
goto STEP0;
break;
}
// Some more messages
Serial << F(" Press key for each command, escape to abort.\n");
// We want to skip to step 3 to ask the command to learn
_learnStep=3;
}
if (_learnStep==2) {
// In this step we check the input supplied for a command
// First check for escape or enter keys
if (_whatAvail==INP_SERIAL) {
switch (_serIn) {
case ESC_KEY:
Serial << F(" Aborting...\n");
_learnMode = false;
_learnStep = 0;
return;
case CR_KEY:
case LF_KEY:
// Do not change the current assignment. Go on to next
_learnStep=3;
learnCmd++;
Serial << F("Not changed.\n");
goto STEP3;
break;
}
}
// If we get here, the type of input received should be the type we
// are learning
if(_whatAvail!=learnInput) {
if(learnInput==INP_SERIAL) {
Serial << F("\nPlease use IR remote.");
} else {
Serial << F("\nPlease use keyboard (serial input).");
}
Serial << F(" Try again...\n");
goto STEP3;
}
// Now we need to make sure that we do not already have this code assigned
// to a previous command.
for (i=0; i<learnCmd; i++) {
if (learnInput==INP_SERIAL) {
if (cmdSerial[i]!=0x00 && cmdSerial[i]==_serIn)
break;
} else {
if (cmdIR[i]!=0x00 && cmdIR[i]==_irCode)
break;
}
}
if(i<learnCmd) {
Serial << F("Already assigned to: ") << cmdName[i] << F(". Try again...\n");
_learnStep=3;
goto STEP3;
}
// Now we can assign the input to the command
if (learnInput==INP_SERIAL) {
cmdSerial[learnCmd] = _serIn;
Serial << cmdSerial[learnCmd] << " (0x" << _HEX(cmdSerial[learnCmd]) << ")" << endl;
} else {
cmdIR[learnCmd] = _irCode;
Serial << F(" IR code 0x") << _HEX(cmdIR[learnCmd]) << endl;
}
// Next command
learnCmd++;
// Next Step
_learnStep=3;
}
STEP3:
if (_learnStep==3) {
// In this step we present the next command to learn.
// Do not let the learn key be changed
if (learnCmd==CMD_LRN) learnCmd++;
// If we are not at the end of the commands yet
if (learnCmd!=CMD_ZZZ) {
// Prompt
Serial << F("New key for ") << cmdName[learnCmd] << " [";
// Add current value
if (learnInput==INP_SERIAL)
Serial << cmdSerial[learnCmd];
else
Serial << F("0x") << _HEX(cmdIR[learnCmd]);
Serial << "]? : ";
// Next time round, get the answer
_learnStep=2;
return;
}
// Else we have done all commands. skip to step 4
_learnStep=4;
}
STEP4:
if (_learnStep==4) {
// In this step we ask if we should write the command maps to EEPROM
Serial << F("Write new map(s) to EEPROM (y/n)? ");
// Next step
_learnStep = 5;
return;
}
if (_learnStep==5) {
// Here we only want serial input
if (_whatAvail!=INP_SERIAL) {
Serial << F("\nOnly key (serial) input allowed. Try again...\n");
_learnStep = 4;
goto STEP4;
}
// What input did we get?
switch (_serIn) {
case 'y':
Serial << F("\nWriting to EEPROM. Please wait....");
saveCmdMaps();
Serial << F(" Done\n");
break;
case 'n':
case ESC_KEY:
Serial << F("\nNot written to EEPROM.\n");
break;
default:
Serial << F("\nNot a valid answer. Please try again.\n");
_learnStep = 4;
goto STEP4;
break;
}
}
// All done, reset
_learnMode = false;
_learnStep = 0;
}
/**
* Tests if we have any input to decode.
*/
bool InputDecoder::canRun(uint32_t now) {
// If we have a SerialIn task, and it has any new input, fetch it and the
// repeat count
if (_serialIn!=NULL && _serialIn->newInput(&_serIn, &_repeat)) {
// Indicate the type of input that is available.
_whatAvail = INP_SERIAL;
return true;
// If we have a SerialIn task, and it has any new input, fetch it and the
// repeat count
} else if (_irIn!=NULL && _irIn->newInput(&_irCode, &_repeat)) {
// Indicate the type of input that is available.
_whatAvail = INP_IR;
return true;
}
// Check for learn mode timeout
if (_learnMode && now>=_learnTimeout) {
// Reset learn mode and learn step
_learnMode = false;
_learnStep = 0;
#ifdef DEBUG
Serial << F("\nTimeout waiting for input. Aborting learn mode...\n");
#endif //DEBUG
}
// Nothing available
return false;
}
/**
* Decodes any new input received.
*
* @param now The current millis() counter.
*/
void InputDecoder::run(uint32_t now) {
int n;
// Preset that no new command is available and clear the command code
_newCmd = false;
_cmd = CMD_ZZZ;
// If we are in learn mode, go straight there.
if(_learnMode) {
_learn(now);
return;
}
// Test the input received agains all possible commands.
for (n=0; n<CMD_ZZZ; n++) {
if(_whatAvail==INP_SERIAL && _serIn==cmdSerial[n]) {
#ifdef DEBUG
Serial << "Received serial input: " << _serIn \
<< " Repeat: " << _repeat << endl;
#endif // DEBUG
break;
} else if(_whatAvail==INP_IR && _irCode==cmdIR[n]) {
#ifdef DEBUG
Serial << "Received IR input: " << _HEX(_irCode) \
<< " Repeat: " << _repeat << endl;
#endif // DEBUG
break;
}
}
// Find a valid command?
if(n==CMD_ZZZ) {
#ifdef DEBUG
if(_whatAvail==INP_SERIAL) {
Serial << "Invalid serial input: " << _serIn << endl;
} else if(_whatAvail==INP_IR) {
Serial << "Invalid IR input: " << _HEX(_irCode) << endl;
}
#endif
return;
}
// If we received the learn command, we go into learning mode
if (n==CMD_LRN) {
_learn(now);
return;
}
// A valid command was found. Set the command and indicator
_cmd = n;
_newCmd = true;
}
/**
* Checks if there is a new comand available and returns this command and any
* possible repeat counts (via pointer args).
*
* NOTE: After calling this method, the command is reset and will not be available
* again on a successive call unless a new command has arrived. The caller
* is responsible for processing the command after this call.
*
* @param c A pointer to a uint8_t type that will be set to the new command ID
* received if there is anything new. This will correspond to one of the
* CMD_nnn defines in commands.h.
* @param rep A pointer to a uint8_t that will be set to the repeat count for how
* many times this command was repeated within the max allowed repeat
* time.
*
* @return True if a new command is available, or False otherwise.
*/
bool InputDecoder::newCommand(uint8_t *c, uint8_t *rep) {
if (!_newCmd)
return false;
// Return the command and repeats via the pointers
*c = _cmd;
*rep = _repeat;
// Reset new command indicator.
_newCmd = false;
return true;
}
// ####################### CommandConsumer class definitions ######################
/**
* Constructor.
*/
CommandConsumer::CommandConsumer(InputDecoder *id, DriveTrain *dev,
LineFollow *lf) : Task(), _iDecoder(id), _device(dev), _lineFol(lf) {
// Open the serial port if we have not done so already.
OpenSerial();
}
/**
* Tests if we have any new commands to handle
* @param now The current millis() counter.
*/
bool CommandConsumer::canRun(uint32_t now) {
// Any new command?
return _iDecoder->newCommand(&_cmd, &_repeat);
}
/**
* Executes any new command received.
*
* @param now The current millis() counter.
*/
void CommandConsumer::run(uint32_t now) {
// Debug
D(F("Received command: ") << cmdName[_cmd] << F(" Repeat count: ") << _repeat << endl);
// Dispatch command
switch(_cmd) {
case CMD_FWD:
// Forward
_device->forward();
break;
case CMD_REV:
// Reverse
_device->reverse();
break;
case CMD_BRK:
// Brake. Deactive line follow mode in case it was active.
_lineFol->deactivate();
_device->stop();
break;
case CMD_LFT:
// Left
_device->left();
break;
case CMD_RGT:
// Right
_device->right();
break;
case CMD_SUP:
// Speed up
_device->speedUp();
break;
case CMD_SDN:
// Slow down
_device->slowDown();
break;
case CMD_INF:
// Info
_device->info();
break;
case CMD_DMO:
// For now we use the demo command to go into line follower mode if not
// doing so already.
if (!_lineFol->isActive()) {
// Stop the bot
_device->stop();
// Activate the line follower
_lineFol->activate();
}
break;
default:
// Debug
D(__FILE__<<":"<<__LINE__<<F("# ")<< F("Command not supported now.\n"));
}
}
/**
* Returns a pointer into the cmdName array for the string name of the last
* command issued.
*/
char *CommandConsumer::lastCommand() {
return cmdName[_cmd];
}