-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathMOVIShield.cpp
More file actions
547 lines (480 loc) · 13.7 KB
/
MOVIShield.cpp
File metadata and controls
547 lines (480 loc) · 13.7 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
/********************************************************************
This is a library for the Audeme MOVI Voice Control Shield
----> http://www.audeme.com/MOVI/
This code is inspired and maintained by Audeme but open to change
and organic development on GITHUB:
----> https://github.com/audeme/MOVIArduinoAPI
Written by Gerald Friedland for Audeme LLC.
Contact: fractor@audeme.com
BSD license, all text above must be included in any redistribution.
********************************************************************/
#include "MOVIShield.h"
#include <Stream.h>
#if defined(ARDUINO) && ARDUINO >=100
#include <Arduino.h>
#elif defined RASPBERRYPI
#include "Arduino.h"
#include "HardwareSerial.h"
#else
#include <WProgram.h>
#include <avr/pgmspace.h>
#define F(str) (str)
#endif
#ifdef ARDUINO_ARCH_AVR
#include <SoftwareSerial.h>
#endif
#ifdef ARDUINO_ARCH_PIC32
#include <SoftwareSerial.h>
#endif
#ifndef F // check to see if F() macro is missing -- should not be triggered but...
#error MOVI 1.10 and higher requires the F() macro.
#endif
// This is a workaround to not have the MOVI API give warning messages about the use of the F() function.
// It is explained here: https://github.com/arduino/Arduino/issues/1793
#ifdef __GNUC__
#ifndef GCC_VERSION
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#endif
#if GCC_VERSION < 40602 // Test for GCC < 4.6.2
#ifdef PROGMEM
#undef PROGMEM
#define PROGMEM __attribute__((section(".progmem.data"))) // Workaround for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734#c4
#ifdef PSTR
#undef PSTR
#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];})) // Copied from pgmspace.h in avr-libc source
#endif
#endif
#endif
#endif
MOVI::MOVI()
{
usehardwareserial=false;
construct(ARDUINO_RX_PIN, ARDUINO_TX_PIN, false);
}
MOVI::MOVI(bool debugonoff)
{
usehardwareserial=false;
construct(ARDUINO_RX_PIN, ARDUINO_TX_PIN, debugonoff);
}
MOVI::MOVI(bool debugonoff, int rx, int tx)
{
#ifndef ARDUINO_ARCH_AVR
#ifndef ARDUINO_ARCH_PIC32
#warning rx and tx parameters only supported on AVR and PIC32 architecture. Using Serial1 hardwired.
#endif
#endif
usehardwareserial=false;
construct(rx, tx, debugonoff);
}
MOVI::MOVI(bool debugonoff, HardwareSerial *hs)
{
usehardwareserial=true;
mySerial = hs;
construct(0, 0, debugonoff);
}
// Arduino's' C++ does not allow for constructor overloading!
void inline MOVI::construct(int rx, int tx, bool debugonoff)
{
debug=debugonoff;
shieldinit=0;
passstring="";
response="";
result="";
intraining=false;
firstsentence=true;
callsigntrainok=true;
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_PIC32)
if (!usehardwareserial) {
mySerial=new SoftwareSerial(rx, tx);
}
#else
if (!usehardwareserial) {
usehardwareserial = true;
mySerial = &Serial1;
}
#endif
}
void MOVI::init()
{
init(true);
}
void MOVI::init(bool waitformovi)
{
if (shieldinit==0) {
if (debug) {
Serial.begin(ARDUINO_BAUDRATE);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only.
}
}
#ifdef ARDUINO_ARCH_AVR
if (!usehardwareserial) ((SoftwareSerial *)mySerial)->begin(ARDUINO_BAUDRATE);
#elif defined ARDUINO_ARCH_SAM
((USARTClass *)mySerial)->begin(ARDUINO_BAUDRATE);
#elif defined __ARDUINO_X86__ // Intel Edison, etc.
((TTYUARTClass *)mySerial)->begin(ARDUINO_BAUDRATE);
#elif defined ARDUINO_ARCH_SAMD // Arduino Zero, Zero Pro, M0 and M0 Pro
((HardwareSerial *)mySerial)->begin(ARDUINO_BAUDRATE);
#elif defined ARDUINO_ARCH_PIC32
if (!usehardwareserial) ((SoftwareSerial *)mySerial)->begin(ARDUINO_BAUDRATE);
#elif defined RASPBERRYPI
((HardwareSerial *)mySerial)->begin(ARDUINO_BAUDRATE);
#else
#error This version of the MOVI library only supports boards with an AVR, SAM, SAMD, PIC32 or Intel processor.
#endif
while (!mySerial) {
; // wait for serial port to connect. Needed for Leonardo only.
}
shieldinit=1;
while (waitformovi && !isReady()) {
delay(10);
}
String sresponse="";
do {
mySerial->println(F("INIT"));
delay(10);
sresponse=getShieldResponse();
} while (sresponse.indexOf("@")==-1);
int s=sresponse.indexOf(": ");
String ver=sresponse.substring(s+2);
// toFloat() didn't work for me. Ugly workarounds working around further bugs follow!
#ifdef __ARDUINO_X86__ // Intel can't do .c_str() either!
char carray[ver.length() + 1];
ver.toCharArray(carray, sizeof(carray));
firmwareversion = atof(carray);
#else // AVR, SAM, SAMD, PIC32
firmwareversion=atof(ver.c_str());
#endif
s=sresponse.indexOf("@");
ver=sresponse.substring(s+1);
#ifdef __ARDUINO_X86__ // Intel
char c2array[ver.length() + 1];
ver.toCharArray(c2array, sizeof(c2array));
hardwareversion = atof(c2array);
#else // AVR, Sam, PIC32, SAMD
hardwareversion=atof(ver.c_str());
#endif
}
}
signed int MOVI::poll()
{
firstsentence=false; // We assume loop() and we can't train in loop.
intraining=false;
int curchar;
int eventno;
while (mySerial->available()) {
curchar=mySerial->read();
if (curchar=='\n') {
if (debug) {
Serial.println(response);
}
if (response.lastIndexOf("MOVIEvent[")>=0) { // Dylan-suggested fix that makes sure buffer junk is not interpreted
eventno=response.substring(response.indexOf("[")+1,response.indexOf("]:")).toInt();
result=response.substring(response.indexOf(" ")+1);
if (eventno<100) { // then it's a user-read-only event
response="";
return SHIELD_IDLE;
}
if (eventno==202) {
result=response.substring(response.indexOf("#")+1);
response="";
return result.toInt()+1; // Sentences returned start at 0,
// we make it easier for non-programmers and start at 1.
}
if (eventno==203) { // this is a password event
response="";
result.trim();
if (passstring.equals(result)) {
return PASSWORD_ACCEPT;
} else {
return PASSWORD_REJECT;
}
}
response="";
return -eventno;
} else {
// other jibberish not belonging to MOVI
}
} else {
response+=(char) curchar;
}
}
if (debug) {
while (Serial.available()) {
mySerial->write(Serial.read());
}
}
return SHIELD_IDLE;
}
String MOVI::getResult()
{
return result;
}
String MOVI::getShieldResponse()
{
String resp="";
int curchar;
if (shieldinit==0) {
init();
return "";
}
while (shieldinit>0) {
mySerial->flush();
while (mySerial->available()) {
curchar=mySerial->read();
if (curchar=='\n') {
if (resp=="") continue;
return resp;
} else {
resp+=(char) curchar;
}
}
delay(10);
}
return "";
}
bool MOVI::sendCommand(String command, String parameter, String okresponse)
{
if (isReady()) {
mySerial->println(command+" "+parameter+"\n");
if (okresponse=="") return true;
if (getShieldResponse().indexOf(okresponse)>=0) {
return true;
} else return false;
} else return false;
}
bool MOVI::sendCommand(const __FlashStringHelper* command, const __FlashStringHelper* parameter, String okresponse)
{
if (isReady()) {
mySerial->print(command);
mySerial->print(" ");
mySerial->print(parameter);
mySerial->println("\n");
if (okresponse=="") return true;
if (getShieldResponse().indexOf(okresponse)>=0) {
return true;
} else return false;
} else return false;
}
void MOVI::sendCommand(String command, String parameter)
{
if (firstsentence || intraining) sendCommand(command,parameter,"]"); // Use controlled sendcommand when used during initialization
else mySerial->println(command+" "+parameter+"\n");
}
void MOVI::sendCommand(String command)
{
if (firstsentence || intraining) sendCommand(command,"","]"); // Use controlled sendcommand when used during initialization
else mySerial->println(command+"\n");
}
void MOVI::sendCommand(const __FlashStringHelper* command, const __FlashStringHelper* parameter)
{
if (firstsentence || intraining) { // Use controlled sendcommand when used during initialization
sendCommand(command,parameter,"]");
} else {
mySerial->print(command);
mySerial->print(" ");
mySerial->print(parameter);
mySerial->println("\n");
}
}
void MOVI::sendCommand(const __FlashStringHelper* command)
{
if (firstsentence || intraining) { // Use controlled sendcommand when used during initialization
sendCommand(command,F(""),"]");
} else {
mySerial->print(command);
mySerial->println("\n");
}
}
bool MOVI::isReady()
{
if (shieldinit==100) {
return true;
}
if (shieldinit==0) {
init();
}
mySerial->println(F("PING\n"));
if (getShieldResponse().indexOf("PONG")) {
shieldinit=100;
return true;
}
shieldinit=1;
return false;
}
void MOVI::factoryDefault()
{
sendCommand(F("FACTORY"));
}
void MOVI::stopDialog()
{
sendCommand(F("STOP"));
}
void MOVI::restartDialog()
{
sendCommand(F("RESTART"));
}
void MOVI::say(const __FlashStringHelper* sentence)
{
sendCommand(F("SAY"),sentence);
}
void MOVI::say(String sentence)
{
sendCommand("SAY",sentence);
}
void MOVI::pause()
{
sendCommand(F("PAUSE"));
}
void MOVI::unpause()
{
sendCommand(F("UNPAUSE"));
}
void MOVI::finish()
{
sendCommand(F("FINISH"));
}
void MOVI::play(String filename)
{
sendCommand("PLAY",filename);
}
void MOVI::play(const __FlashStringHelper* filename)
{
sendCommand(F("PLAY"),filename);
}
void MOVI::abort()
{
sendCommand(F("ABORT"),F(""));
}
void MOVI::setSynthesizer(int synth)
{
if (synth==SYNTH_PICO) {
sendCommand(F("SETSYNTH"),F("PICO"));
} else {
sendCommand(F("SETSYNTH"),F("ESPEAK"));
}
}
void MOVI::setSynthesizer(int synth, String commandline)
{
if (synth==SYNTH_PICO) {
sendCommand("SETSYNTH","PICO "+commandline);
} else {
sendCommand("SETSYNTH","ESPEAK "+commandline);
}
}
void MOVI::password(const __FlashStringHelper* question, String passkey)
{
passstring=String(passkey);
passstring.toUpperCase();
passstring.trim();
say(question);
sendCommand(F("PASSWORD"),F(""));
}
void MOVI::password(String question, String passkey)
{
passstring=String(passkey);
passstring.toUpperCase();
passstring.trim();
say(question);
sendCommand(F("PASSWORD"));
}
void MOVI::ask(String question)
{
// checking for empty string makes ask faster when there is no question, it's better to use ask() though
if (question.length() > 0) say(question);
sendCommand(F("ASK"));
}
void MOVI::ask(const __FlashStringHelper* question)
{
// To check for empty string here, we need to copy the string into string memory. Bad idea.
say(question);
sendCommand(F("ASK"));
}
// this is a new ask method without passing a string.
void MOVI::ask()
{
sendCommand(F("ASK"));
}
void MOVI::callSign(String callsign)
{
if (callsigntrainok) sendCommand("CALLSIGN",callsign,"callsign");
callsigntrainok=false;
}
void MOVI::responses(bool on)
{
String parameter="ON";
if (!on) parameter="OFF";
sendCommand("RESPONSES",parameter);
}
void MOVI::welcomeMessage(bool on)
{
String parameter="ON";
if (!on) parameter="OFF";
sendCommand("WELCOMEMESSAGE",parameter);
}
void MOVI::beeps(bool on)
{
String parameter="ON";
if (!on) parameter="OFF";
sendCommand("BEEPS",parameter);
}
void MOVI::setVoiceGender(bool female)
{
if (female) sendCommand(F("FEMALE"));
else sendCommand(F("MALE"));
}
void MOVI::setVolume(int volume)
{
sendCommand("VOLUME",String(volume));
}
void MOVI::setThreshold(int threshold)
{
sendCommand("THRESHOLD",String(threshold));
}
float MOVI::getFirmwareVersion()
{
return firmwareversion;
}
float MOVI::getAPIVersion()
{
return API_VERSION;
}
float MOVI::getHardwareVersion()
{
return hardwareversion;
}
bool MOVI::addSentence(const __FlashStringHelper* sentence)
{
if (firstsentence) {
intraining=sendCommand(F("NEWSENTENCES"),F(""),"210");
firstsentence=false;
}
if (!intraining) return false;
intraining=sendCommand(F("ADDSENTENCE"),sentence,"211");
return intraining;
}
bool MOVI::addSentence(String sentence)
{
// Needs a new MOVI instance (typically restart Arduino). This avoids training only part of the sentence set.
if (firstsentence) {
intraining=sendCommand("NEWSENTENCES","","210");
firstsentence=false;
}
if (!intraining) return false;
intraining=sendCommand("ADDSENTENCE",sentence,"211");
return intraining;
}
bool MOVI::train()
{
if (!intraining) return false;
sendCommand("TRAINSENTENCES","","trained");
intraining=false;
return true;
}
MOVI::~MOVI()
{
if (NULL != mySerial && (!usehardwareserial))
{
delete mySerial;
}
}