parent
016f100c25
commit
0ff507e7f2
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
@ -0,0 +1,23 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:seeed_wio_terminal]
|
||||||
|
platform = atmelsam
|
||||||
|
board = seeed_wio_terminal
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
seeed-studio/Seeed Arduino FS @ 2.1.1
|
||||||
|
seeed-studio/Seeed Arduino SFUD @ 2.0.2
|
||||||
|
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
|
||||||
|
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
|
||||||
|
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
|
||||||
|
seeed-studio/Seeed Arduino RTC @ 2.0.0
|
||||||
|
bblanchon/ArduinoJson @ 6.17.3
|
||||||
|
contrem/arduino-timer @ 2.3.0
|
@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define RATE 16000
|
||||||
|
#define SAMPLE_LENGTH_SECONDS 4
|
||||||
|
#define SAMPLES RATE * SAMPLE_LENGTH_SECONDS
|
||||||
|
#define BUFFER_SIZE (SAMPLES * 2) + 44
|
||||||
|
#define ADC_BUF_LEN 1600
|
||||||
|
|
||||||
|
const char *SSID = "<SSID>";
|
||||||
|
const char *PASSWORD = "<PASSWORD>";
|
||||||
|
|
||||||
|
const char *SPEECH_API_KEY = "<API_KEY>";
|
||||||
|
const char *SPEECH_LOCATION = "<LOCATION>";
|
||||||
|
const char *LANGUAGE = "<LANGUAGE>";
|
||||||
|
|
||||||
|
const char *TOKEN_URL = "https://%s.api.cognitive.microsoft.com/sts/v1.0/issuetoken";
|
||||||
|
const char *SPEECH_URL = "https://%s.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=%s";
|
||||||
|
|
||||||
|
const char *TEXT_TO_TIMER_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/text-to-timer";
|
||||||
|
const char *GET_VOICES_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/get-voices";
|
||||||
|
const char *TEXT_TO_SPEECH_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/text-to-speech";
|
||||||
|
|
||||||
|
const char *TOKEN_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\r\n"
|
||||||
|
"MIIF8zCCBNugAwIBAgIQAueRcfuAIek/4tmDg0xQwDANBgkqhkiG9w0BAQwFADBh\r\n"
|
||||||
|
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
|
||||||
|
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
|
||||||
|
"MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
|
||||||
|
"MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
|
||||||
|
"c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNjCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
|
||||||
|
"ggIPADCCAgoCggIBALVGARl56bx3KBUSGuPc4H5uoNFkFH4e7pvTCxRi4j/+z+Xb\r\n"
|
||||||
|
"wjEz+5CipDOqjx9/jWjskL5dk7PaQkzItidsAAnDCW1leZBOIi68Lff1bjTeZgMY\r\n"
|
||||||
|
"iwdRd3Y39b/lcGpiuP2d23W95YHkMMT8IlWosYIX0f4kYb62rphyfnAjYb/4Od99\r\n"
|
||||||
|
"ThnhlAxGtfvSbXcBVIKCYfZgqRvV+5lReUnd1aNjRYVzPOoifgSx2fRyy1+pO1Uz\r\n"
|
||||||
|
"aMMNnIOE71bVYW0A1hr19w7kOb0KkJXoALTDDj1ukUEDqQuBfBxReL5mXiu1O7WG\r\n"
|
||||||
|
"0vltg0VZ/SZzctBsdBlx1BkmWYBW261KZgBivrql5ELTKKd8qgtHcLQA5fl6JB0Q\r\n"
|
||||||
|
"gs5XDaWehN86Gps5JW8ArjGtjcWAIP+X8CQaWfaCnuRm6Bk/03PQWhgdi84qwA0s\r\n"
|
||||||
|
"sRfFJwHUPTNSnE8EiGVk2frt0u8PG1pwSQsFuNJfcYIHEv1vOzP7uEOuDydsmCjh\r\n"
|
||||||
|
"lxuoK2n5/2aVR3BMTu+p4+gl8alXoBycyLmj3J/PUgqD8SL5fTCUegGsdia/Sa60\r\n"
|
||||||
|
"N2oV7vQ17wjMN+LXa2rjj/b4ZlZgXVojDmAjDwIRdDUujQu0RVsJqFLMzSIHpp2C\r\n"
|
||||||
|
"Zp7mIoLrySay2YYBu7SiNwL95X6He2kS8eefBBHjzwW/9FxGqry57i71c2cDAgMB\r\n"
|
||||||
|
"AAGjggGtMIIBqTAdBgNVHQ4EFgQU1cFnOsKjnfR3UltZEjgp5lVou6UwHwYDVR0j\r\n"
|
||||||
|
"BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
|
||||||
|
"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
|
||||||
|
"CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
|
||||||
|
"Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
|
||||||
|
"aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
|
||||||
|
"cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
|
||||||
|
"MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
|
||||||
|
"cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
|
||||||
|
"AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQB2oWc93fB8esci/8esixj++N22meiGDjgF\r\n"
|
||||||
|
"+rA2LUK5IOQOgcUSTGKSqF9lYfAxPjrqPjDCUPHCURv+26ad5P/BYtXtbmtxJWu+\r\n"
|
||||||
|
"cS5BhMDPPeG3oPZwXRHBJFAkY4O4AF7RIAAUW6EzDflUoDHKv83zOiPfYGcpHc9s\r\n"
|
||||||
|
"kxAInCedk7QSgXvMARjjOqdakor21DTmNIUotxo8kHv5hwRlGhBJwps6fEVi1Bt0\r\n"
|
||||||
|
"trpM/3wYxlr473WSPUFZPgP1j519kLpWOJ8z09wxay+Br29irPcBYv0GMXlHqThy\r\n"
|
||||||
|
"8y4m/HyTQeI2IMvMrQnwqPpY+rLIXyviI2vLoI+4xKE4Rn38ZZ8m\r\n"
|
||||||
|
"-----END CERTIFICATE-----\r\n";
|
||||||
|
|
||||||
|
const char *SPEECH_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\r\n"
|
||||||
|
"MIIF8zCCBNugAwIBAgIQCq+mxcpjxFFB6jvh98dTFzANBgkqhkiG9w0BAQwFADBh\r\n"
|
||||||
|
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
|
||||||
|
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
|
||||||
|
"MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
|
||||||
|
"MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
|
||||||
|
"c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMTCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
|
||||||
|
"ggIPADCCAgoCggIBAMedcDrkXufP7pxVm1FHLDNA9IjwHaMoaY8arqqZ4Gff4xyr\r\n"
|
||||||
|
"RygnavXL7g12MPAx8Q6Dd9hfBzrfWxkF0Br2wIvlvkzW01naNVSkHp+OS3hL3W6n\r\n"
|
||||||
|
"l/jYvZnVeJXjtsKYcXIf/6WtspcF5awlQ9LZJcjwaH7KoZuK+THpXCMtzD8XNVdm\r\n"
|
||||||
|
"GW/JI0C/7U/E7evXn9XDio8SYkGSM63aLO5BtLCv092+1d4GGBSQYolRq+7Pd1kR\r\n"
|
||||||
|
"EkWBPm0ywZ2Vb8GIS5DLrjelEkBnKCyy3B0yQud9dpVsiUeE7F5sY8Me96WVxQcb\r\n"
|
||||||
|
"OyYdEY/j/9UpDlOG+vA+YgOvBhkKEjiqygVpP8EZoMMijephzg43b5Qi9r5UrvYo\r\n"
|
||||||
|
"o19oR/8pf4HJNDPF0/FJwFVMW8PmCBLGstin3NE1+NeWTkGt0TzpHjgKyfaDP2tO\r\n"
|
||||||
|
"4bCk1G7pP2kDFT7SYfc8xbgCkFQ2UCEXsaH/f5YmpLn4YPiNFCeeIida7xnfTvc4\r\n"
|
||||||
|
"7IxyVccHHq1FzGygOqemrxEETKh8hvDR6eBdrBwmCHVgZrnAqnn93JtGyPLi6+cj\r\n"
|
||||||
|
"WGVGtMZHwzVvX1HvSFG771sskcEjJxiQNQDQRWHEh3NxvNb7kFlAXnVdRkkvhjpR\r\n"
|
||||||
|
"GchFhTAzqmwltdWhWDEyCMKC2x/mSZvZtlZGY+g37Y72qHzidwtyW7rBetZJAgMB\r\n"
|
||||||
|
"AAGjggGtMIIBqTAdBgNVHQ4EFgQUDyBd16FXlduSzyvQx8J3BM5ygHYwHwYDVR0j\r\n"
|
||||||
|
"BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
|
||||||
|
"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
|
||||||
|
"CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
|
||||||
|
"Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
|
||||||
|
"aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
|
||||||
|
"cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
|
||||||
|
"MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
|
||||||
|
"cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
|
||||||
|
"AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQAlFvNh7QgXVLAZSsNR2XRmIn9iS8OHFCBA\r\n"
|
||||||
|
"WxKJoi8YYQafpMTkMqeuzoL3HWb1pYEipsDkhiMnrpfeYZEA7Lz7yqEEtfgHcEBs\r\n"
|
||||||
|
"K9KcStQGGZRfmWU07hPXHnFz+5gTXqzCE2PBMlRgVUYJiA25mJPXfB00gDvGhtYa\r\n"
|
||||||
|
"+mENwM9Bq1B9YYLyLjRtUz8cyGsdyTIG/bBM/Q9jcV8JGqMU/UjAdh1pFyTnnHEl\r\n"
|
||||||
|
"Y59Npi7F87ZqYYJEHJM2LGD+le8VsHjgeWX2CJQko7klXvcizuZvUEDTjHaQcs2J\r\n"
|
||||||
|
"+kPgfyMIOY1DMJ21NxOJ2xPRC/wAh/hzSBRVtoAnyuxtkZ4VjIOh\r\n"
|
||||||
|
"-----END CERTIFICATE-----\r\n";
|
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <sfud.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class FlashStream : public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FlashStream()
|
||||||
|
{
|
||||||
|
_pos = 0;
|
||||||
|
_flash_address = 0;
|
||||||
|
_flash = sfud_get_device_table() + 0;
|
||||||
|
|
||||||
|
populateBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t val)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int available()
|
||||||
|
{
|
||||||
|
int remaining = BUFFER_SIZE - ((_flash_address - HTTP_TCP_BUFFER_SIZE) + _pos);
|
||||||
|
int bytes_available = min(HTTP_TCP_BUFFER_SIZE, remaining);
|
||||||
|
|
||||||
|
if (bytes_available == 0)
|
||||||
|
{
|
||||||
|
bytes_available = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read()
|
||||||
|
{
|
||||||
|
int retVal = _buffer[_pos++];
|
||||||
|
|
||||||
|
if (_pos == HTTP_TCP_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
populateBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int peek()
|
||||||
|
{
|
||||||
|
return _buffer[_pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void populateBuffer()
|
||||||
|
{
|
||||||
|
sfud_read(_flash, _flash_address, HTTP_TCP_BUFFER_SIZE, _buffer);
|
||||||
|
_flash_address += HTTP_TCP_BUFFER_SIZE;
|
||||||
|
_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t _pos;
|
||||||
|
size_t _flash_address;
|
||||||
|
const sfud_flash *_flash;
|
||||||
|
|
||||||
|
byte _buffer[HTTP_TCP_BUFFER_SIZE];
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <sfud.h>
|
||||||
|
|
||||||
|
class FlashWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
_flash = sfud_get_device_table() + 0;
|
||||||
|
_sfudBufferSize = _flash->chip.erase_gran;
|
||||||
|
_sfudBuffer = new byte[_sfudBufferSize];
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
_sfudBufferWritePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
_sfudBufferWritePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeSfudBuffer(byte b)
|
||||||
|
{
|
||||||
|
_sfudBuffer[_sfudBufferPos++] = b;
|
||||||
|
if (_sfudBufferPos == _sfudBufferSize)
|
||||||
|
{
|
||||||
|
sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer);
|
||||||
|
_sfudBufferWritePos += _sfudBufferSize;
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushSfudBuffer()
|
||||||
|
{
|
||||||
|
if (_sfudBufferPos > 0)
|
||||||
|
{
|
||||||
|
sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer);
|
||||||
|
_sfudBufferWritePos += _sfudBufferSize;
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeSfudBuffer(byte *b, size_t len)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
{
|
||||||
|
writeSfudBuffer(b[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
byte *_sfudBuffer;
|
||||||
|
size_t _sfudBufferSize;
|
||||||
|
size_t _sfudBufferPos;
|
||||||
|
size_t _sfudBufferWritePos;
|
||||||
|
|
||||||
|
const sfud_flash *_flash;
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class LanguageUnderstanding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int GetTimerDuration(String text)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["text"] = text;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TEXT_TO_TIMER_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
int seconds = 0;
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
Serial.println(result);
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
seconds = obj["seconds"].as<int>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to understand text - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WiFiClient _client;
|
||||||
|
};
|
||||||
|
|
||||||
|
LanguageUnderstanding languageUnderstanding;
|
@ -0,0 +1,130 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <arduino-timer.h>
|
||||||
|
#include <rpcWiFi.h>
|
||||||
|
#include <sfud.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "language_understanding.h"
|
||||||
|
#include "mic.h"
|
||||||
|
#include "speech_to_text.h"
|
||||||
|
#include "text_to_speech.h"
|
||||||
|
|
||||||
|
void connectWiFi()
|
||||||
|
{
|
||||||
|
while (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.println("Connecting to WiFi..");
|
||||||
|
WiFi.begin(SSID, PASSWORD);
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Connected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
while (!Serial)
|
||||||
|
; // Wait for Serial to be ready
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
connectWiFi();
|
||||||
|
|
||||||
|
while (!(sfud_init() == SFUD_SUCCESS))
|
||||||
|
;
|
||||||
|
|
||||||
|
sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q32_DEVICE_INDEX), 2);
|
||||||
|
|
||||||
|
pinMode(WIO_KEY_C, INPUT_PULLUP);
|
||||||
|
|
||||||
|
mic.init();
|
||||||
|
|
||||||
|
speechToText.init();
|
||||||
|
textToSpeech.init();
|
||||||
|
|
||||||
|
Serial.println("Ready.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto timer = timer_create_default();
|
||||||
|
|
||||||
|
void say(String text)
|
||||||
|
{
|
||||||
|
Serial.println(text);
|
||||||
|
textToSpeech.convertTextToSpeech(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timerExpired(void *announcement)
|
||||||
|
{
|
||||||
|
say((char *)announcement);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processAudio()
|
||||||
|
{
|
||||||
|
String text = speechToText.convertSpeechToText();
|
||||||
|
Serial.println(text);
|
||||||
|
|
||||||
|
int total_seconds = languageUnderstanding.GetTimerDuration(text);
|
||||||
|
if (total_seconds == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutes = total_seconds / 60;
|
||||||
|
int seconds = total_seconds % 60;
|
||||||
|
|
||||||
|
String begin_message;
|
||||||
|
if (minutes > 0)
|
||||||
|
{
|
||||||
|
begin_message += minutes;
|
||||||
|
begin_message += " minute ";
|
||||||
|
}
|
||||||
|
if (seconds > 0)
|
||||||
|
{
|
||||||
|
begin_message += seconds;
|
||||||
|
begin_message += " second ";
|
||||||
|
}
|
||||||
|
|
||||||
|
begin_message += "timer started.";
|
||||||
|
|
||||||
|
String end_message("Times up on your ");
|
||||||
|
if (minutes > 0)
|
||||||
|
{
|
||||||
|
end_message += minutes;
|
||||||
|
end_message += " minute ";
|
||||||
|
}
|
||||||
|
if (seconds > 0)
|
||||||
|
{
|
||||||
|
end_message += seconds;
|
||||||
|
end_message += " second ";
|
||||||
|
}
|
||||||
|
|
||||||
|
end_message += "timer.";
|
||||||
|
|
||||||
|
say(begin_message);
|
||||||
|
|
||||||
|
timer.in(total_seconds * 1000, timerExpired, (void *)(end_message.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (digitalRead(WIO_KEY_C) == LOW && !mic.isRecording())
|
||||||
|
{
|
||||||
|
Serial.println("Starting recording...");
|
||||||
|
mic.startRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mic.isRecording() && mic.isRecordingReady())
|
||||||
|
{
|
||||||
|
Serial.println("Finished recording");
|
||||||
|
|
||||||
|
processAudio();
|
||||||
|
|
||||||
|
mic.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.tick();
|
||||||
|
}
|
@ -0,0 +1,242 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "flash_writer.h"
|
||||||
|
|
||||||
|
class Mic
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Mic()
|
||||||
|
{
|
||||||
|
_isRecording = false;
|
||||||
|
_isRecordingReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startRecording()
|
||||||
|
{
|
||||||
|
_isRecording = true;
|
||||||
|
_isRecordingReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRecording()
|
||||||
|
{
|
||||||
|
return _isRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRecordingReady()
|
||||||
|
{
|
||||||
|
return _isRecordingReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
analogReference(AR_INTERNAL2V23);
|
||||||
|
|
||||||
|
_writer.init();
|
||||||
|
|
||||||
|
initBufferHeader();
|
||||||
|
configureDmaAdc();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
_isRecordingReady = false;
|
||||||
|
_isRecording = false;
|
||||||
|
|
||||||
|
_writer.reset();
|
||||||
|
|
||||||
|
initBufferHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dmaHandler()
|
||||||
|
{
|
||||||
|
static uint8_t count = 0;
|
||||||
|
|
||||||
|
if (DMAC->Channel[1].CHINTFLAG.bit.SUSP)
|
||||||
|
{
|
||||||
|
DMAC->Channel[1].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;
|
||||||
|
DMAC->Channel[1].CHINTFLAG.bit.SUSP = 1;
|
||||||
|
|
||||||
|
if (count)
|
||||||
|
{
|
||||||
|
audioCallback(_adc_buf_0, ADC_BUF_LEN);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
audioCallback(_adc_buf_1, ADC_BUF_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = (count + 1) % 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile bool _isRecording;
|
||||||
|
volatile bool _isRecordingReady;
|
||||||
|
FlashWriter _writer;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t btctrl;
|
||||||
|
uint16_t btcnt;
|
||||||
|
uint32_t srcaddr;
|
||||||
|
uint32_t dstaddr;
|
||||||
|
uint32_t descaddr;
|
||||||
|
} dmacdescriptor;
|
||||||
|
|
||||||
|
// Globals - DMA and ADC
|
||||||
|
volatile dmacdescriptor _wrb[DMAC_CH_NUM] __attribute__((aligned(16)));
|
||||||
|
dmacdescriptor _descriptor_section[DMAC_CH_NUM] __attribute__((aligned(16)));
|
||||||
|
dmacdescriptor _descriptor __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
void configureDmaAdc()
|
||||||
|
{
|
||||||
|
// Configure DMA to sample from ADC at a regular interval (triggered by timer/counter)
|
||||||
|
DMAC->BASEADDR.reg = (uint32_t)_descriptor_section; // Specify the location of the descriptors
|
||||||
|
DMAC->WRBADDR.reg = (uint32_t)_wrb; // Specify the location of the write back descriptors
|
||||||
|
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral
|
||||||
|
DMAC->Channel[1].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC5_DMAC_ID_OVF) | // Set DMAC to trigger on TC5 timer overflow
|
||||||
|
DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer
|
||||||
|
|
||||||
|
_descriptor.descaddr = (uint32_t)&_descriptor_section[1]; // Set up a circular descriptor
|
||||||
|
_descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register
|
||||||
|
_descriptor.dstaddr = (uint32_t)_adc_buf_0 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_0 array
|
||||||
|
_descriptor.btcnt = ADC_BUF_LEN; // Beat count
|
||||||
|
_descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
|
||||||
|
DMAC_BTCTRL_DSTINC | // Increment the destination address
|
||||||
|
DMAC_BTCTRL_VALID | // Descriptor is valid
|
||||||
|
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 0 after block transfer
|
||||||
|
memcpy(&_descriptor_section[0], &_descriptor, sizeof(_descriptor)); // Copy the descriptor to the descriptor section
|
||||||
|
|
||||||
|
_descriptor.descaddr = (uint32_t)&_descriptor_section[0]; // Set up a circular descriptor
|
||||||
|
_descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register
|
||||||
|
_descriptor.dstaddr = (uint32_t)_adc_buf_1 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_1 array
|
||||||
|
_descriptor.btcnt = ADC_BUF_LEN; // Beat count
|
||||||
|
_descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
|
||||||
|
DMAC_BTCTRL_DSTINC | // Increment the destination address
|
||||||
|
DMAC_BTCTRL_VALID | // Descriptor is valid
|
||||||
|
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 0 after block transfer
|
||||||
|
memcpy(&_descriptor_section[1], &_descriptor, sizeof(_descriptor)); // Copy the descriptor to the descriptor section
|
||||||
|
|
||||||
|
// Configure NVIC
|
||||||
|
NVIC_SetPriority(DMAC_1_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC1 to 0 (highest)
|
||||||
|
NVIC_EnableIRQ(DMAC_1_IRQn); // Connect DMAC1 to Nested Vector Interrupt Controller (NVIC)
|
||||||
|
|
||||||
|
// Activate the suspend (SUSP) interrupt on DMAC channel 1
|
||||||
|
DMAC->Channel[1].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;
|
||||||
|
|
||||||
|
// Configure ADC
|
||||||
|
ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN12_Val; // Set the analog input to ADC0/AIN2 (PB08 - A4 on Metro M4)
|
||||||
|
while (ADC1->SYNCBUSY.bit.INPUTCTRL)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->SAMPCTRL.bit.SAMPLEN = 0x00; // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
|
||||||
|
while (ADC1->SYNCBUSY.bit.SAMPCTRL)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128; // Divide Clock ADC GCLK by 128 (48MHz/128 = 375kHz)
|
||||||
|
ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT | // Set ADC resolution to 12 bits
|
||||||
|
ADC_CTRLB_FREERUN; // Set ADC to free run mode
|
||||||
|
while (ADC1->SYNCBUSY.bit.CTRLB)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->CTRLA.bit.ENABLE = 1; // Enable the ADC
|
||||||
|
while (ADC1->SYNCBUSY.bit.ENABLE)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->SWTRIG.bit.START = 1; // Initiate a software trigger to start an ADC conversion
|
||||||
|
while (ADC1->SYNCBUSY.bit.SWTRIG)
|
||||||
|
; // Wait for synchronization
|
||||||
|
|
||||||
|
// Enable DMA channel 1
|
||||||
|
DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1;
|
||||||
|
|
||||||
|
// Configure Timer/Counter 5
|
||||||
|
GCLK->PCHCTRL[TC5_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable perhipheral channel for TC5
|
||||||
|
GCLK_PCHCTRL_GEN_GCLK1; // Connect generic clock 0 at 48MHz
|
||||||
|
|
||||||
|
TC5->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TC5 to Match Frequency (MFRQ) mode
|
||||||
|
TC5->COUNT16.CC[0].reg = 3000 - 1; // Set the trigger to 16 kHz: (4Mhz / 16000) - 1
|
||||||
|
while (TC5->COUNT16.SYNCBUSY.bit.CC0)
|
||||||
|
; // Wait for synchronization
|
||||||
|
|
||||||
|
// Start Timer/Counter 5
|
||||||
|
TC5->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC5 timer
|
||||||
|
while (TC5->COUNT16.SYNCBUSY.bit.ENABLE)
|
||||||
|
; // Wait for synchronization
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _adc_buf_0[ADC_BUF_LEN];
|
||||||
|
uint16_t _adc_buf_1[ADC_BUF_LEN];
|
||||||
|
|
||||||
|
// WAV files have a header. This struct defines that header
|
||||||
|
struct wavFileHeader
|
||||||
|
{
|
||||||
|
char riff[4]; /* "RIFF" */
|
||||||
|
long flength; /* file length in bytes */
|
||||||
|
char wave[4]; /* "WAVE" */
|
||||||
|
char fmt[4]; /* "fmt " */
|
||||||
|
long chunk_size; /* size of FMT chunk in bytes (usually 16) */
|
||||||
|
short format_tag; /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */
|
||||||
|
short num_chans; /* 1=mono, 2=stereo */
|
||||||
|
long srate; /* Sampling rate in samples per second */
|
||||||
|
long bytes_per_sec; /* bytes per second = srate*bytes_per_samp */
|
||||||
|
short bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo */
|
||||||
|
short bits_per_samp; /* Number of bits per sample */
|
||||||
|
char data[4]; /* "data" */
|
||||||
|
long dlength; /* data length in bytes (filelength - 44) */
|
||||||
|
};
|
||||||
|
|
||||||
|
void initBufferHeader()
|
||||||
|
{
|
||||||
|
wavFileHeader wavh;
|
||||||
|
|
||||||
|
strncpy(wavh.riff, "RIFF", 4);
|
||||||
|
strncpy(wavh.wave, "WAVE", 4);
|
||||||
|
strncpy(wavh.fmt, "fmt ", 4);
|
||||||
|
strncpy(wavh.data, "data", 4);
|
||||||
|
|
||||||
|
wavh.chunk_size = 16;
|
||||||
|
wavh.format_tag = 1; // PCM
|
||||||
|
wavh.num_chans = 1; // mono
|
||||||
|
wavh.srate = RATE;
|
||||||
|
wavh.bytes_per_sec = (RATE * 1 * 16 * 1) / 8;
|
||||||
|
wavh.bytes_per_samp = 2;
|
||||||
|
wavh.bits_per_samp = 16;
|
||||||
|
wavh.dlength = RATE * 2 * 1 * 16 / 2;
|
||||||
|
wavh.flength = wavh.dlength + 44;
|
||||||
|
|
||||||
|
_writer.writeSfudBuffer((byte *)&wavh, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audioCallback(uint16_t *buf, uint32_t buf_len)
|
||||||
|
{
|
||||||
|
static uint32_t idx = 44;
|
||||||
|
|
||||||
|
if (_isRecording)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < buf_len; i++)
|
||||||
|
{
|
||||||
|
int16_t audio_value = ((int16_t)buf[i] - 2048) * 16;
|
||||||
|
|
||||||
|
_writer.writeSfudBuffer(audio_value & 0xFF);
|
||||||
|
_writer.writeSfudBuffer((audio_value >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += buf_len;
|
||||||
|
|
||||||
|
if (idx >= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
_writer.flushSfudBuffer();
|
||||||
|
idx = 44;
|
||||||
|
_isRecording = false;
|
||||||
|
_isRecordingReady = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mic mic;
|
||||||
|
|
||||||
|
void DMAC_1_Handler()
|
||||||
|
{
|
||||||
|
mic.dmaHandler();
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "flash_stream.h"
|
||||||
|
|
||||||
|
class SpeechToText
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
_token_client.setCACert(TOKEN_CERTIFICATE);
|
||||||
|
_speech_client.setCACert(SPEECH_CERTIFICATE);
|
||||||
|
_access_token = getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertSpeechToText()
|
||||||
|
{
|
||||||
|
char url[128];
|
||||||
|
sprintf(url, SPEECH_URL, SPEECH_LOCATION, LANGUAGE);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_speech_client, url);
|
||||||
|
|
||||||
|
httpClient.addHeader("Authorization", String("Bearer ") + _access_token);
|
||||||
|
httpClient.addHeader("Content-Type", String("audio/wav; codecs=audio/pcm; samplerate=") + String(RATE));
|
||||||
|
httpClient.addHeader("Accept", "application/json;text/xml");
|
||||||
|
|
||||||
|
Serial.println("Sending speech...");
|
||||||
|
|
||||||
|
FlashStream stream;
|
||||||
|
int httpResponseCode = httpClient.sendRequest("POST", &stream, BUFFER_SIZE);
|
||||||
|
|
||||||
|
Serial.println("Speech sent!");
|
||||||
|
|
||||||
|
String text = "";
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
Serial.println(result);
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
text = obj["DisplayText"].as<String>();
|
||||||
|
}
|
||||||
|
else if (httpResponseCode == 401)
|
||||||
|
{
|
||||||
|
Serial.println("Access token expired, trying again with a new token");
|
||||||
|
_access_token = getAccessToken();
|
||||||
|
return convertSpeechToText();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to convert text to speech - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
String getAccessToken()
|
||||||
|
{
|
||||||
|
char url[128];
|
||||||
|
sprintf(url, TOKEN_URL, SPEECH_LOCATION);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_token_client, url);
|
||||||
|
|
||||||
|
httpClient.addHeader("Ocp-Apim-Subscription-Key", SPEECH_API_KEY);
|
||||||
|
int httpResultCode = httpClient.POST("{}");
|
||||||
|
|
||||||
|
if (httpResultCode != 200)
|
||||||
|
{
|
||||||
|
Serial.println("Error getting access token, trying again...");
|
||||||
|
delay(10000);
|
||||||
|
return getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Got access token.");
|
||||||
|
String result = httpClient.getString();
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFiClientSecure _token_client;
|
||||||
|
WiFiClientSecure _speech_client;
|
||||||
|
String _access_token;
|
||||||
|
};
|
||||||
|
|
||||||
|
SpeechToText speechToText;
|
@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <Seeed_FS.h>
|
||||||
|
#include <SD/Seeed_SD.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class TextToSpeech
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["language"] = LANGUAGE;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, GET_VOICES_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
Serial.println(result);
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonArray obj = doc.as<JsonArray>();
|
||||||
|
_voice = obj[0].as<String>();
|
||||||
|
|
||||||
|
Serial.print("Using voice ");
|
||||||
|
Serial.println(_voice);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to get voices - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void convertTextToSpeech(String text)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["language"] = LANGUAGE;
|
||||||
|
doc["voice"] = _voice;
|
||||||
|
doc["text"] = text;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TEXT_TO_SPEECH_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
File wav_file = SD.open("SPEECH.WAV", FILE_WRITE);
|
||||||
|
httpClient.writeToStream(&wav_file);
|
||||||
|
wav_file.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to get speech - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
WiFiClient _client;
|
||||||
|
String _voice;
|
||||||
|
};
|
||||||
|
|
||||||
|
TextToSpeech textToSpeech;
|
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/page/plus/unit-testing.html
|
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
@ -0,0 +1,23 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:seeed_wio_terminal]
|
||||||
|
platform = atmelsam
|
||||||
|
board = seeed_wio_terminal
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
seeed-studio/Seeed Arduino FS @ 2.1.1
|
||||||
|
seeed-studio/Seeed Arduino SFUD @ 2.0.2
|
||||||
|
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
|
||||||
|
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
|
||||||
|
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
|
||||||
|
seeed-studio/Seeed Arduino RTC @ 2.0.0
|
||||||
|
bblanchon/ArduinoJson @ 6.17.3
|
||||||
|
contrem/arduino-timer @ 2.3.0
|
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define RATE 16000
|
||||||
|
#define SAMPLE_LENGTH_SECONDS 4
|
||||||
|
#define SAMPLES RATE * SAMPLE_LENGTH_SECONDS
|
||||||
|
#define BUFFER_SIZE (SAMPLES * 2) + 44
|
||||||
|
#define ADC_BUF_LEN 1600
|
||||||
|
|
||||||
|
const char *SSID = "<SSID>";
|
||||||
|
const char *PASSWORD = "<PASSWORD>";
|
||||||
|
|
||||||
|
const char *SPEECH_API_KEY = "<API_KEY>";
|
||||||
|
const char *SPEECH_LOCATION = "<LOCATION>";
|
||||||
|
const char *LANGUAGE = "<LANGUAGE>";
|
||||||
|
const char *SERVER_LANGUAGE = "<LANGUAGE>";
|
||||||
|
|
||||||
|
const char *TOKEN_URL = "https://%s.api.cognitive.microsoft.com/sts/v1.0/issuetoken";
|
||||||
|
const char *SPEECH_URL = "https://%s.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=%s";
|
||||||
|
|
||||||
|
const char *TEXT_TO_TIMER_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/text-to-timer";
|
||||||
|
const char *GET_VOICES_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/get-voices";
|
||||||
|
const char *TEXT_TO_SPEECH_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/text-to-speech";
|
||||||
|
const char *TRANSLATE_FUNCTION_URL = "http://<IP_ADDRESS>:7071/api/translate-text";
|
||||||
|
|
||||||
|
const char *TOKEN_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\r\n"
|
||||||
|
"MIIF8zCCBNugAwIBAgIQAueRcfuAIek/4tmDg0xQwDANBgkqhkiG9w0BAQwFADBh\r\n"
|
||||||
|
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
|
||||||
|
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
|
||||||
|
"MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
|
||||||
|
"MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
|
||||||
|
"c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNjCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
|
||||||
|
"ggIPADCCAgoCggIBALVGARl56bx3KBUSGuPc4H5uoNFkFH4e7pvTCxRi4j/+z+Xb\r\n"
|
||||||
|
"wjEz+5CipDOqjx9/jWjskL5dk7PaQkzItidsAAnDCW1leZBOIi68Lff1bjTeZgMY\r\n"
|
||||||
|
"iwdRd3Y39b/lcGpiuP2d23W95YHkMMT8IlWosYIX0f4kYb62rphyfnAjYb/4Od99\r\n"
|
||||||
|
"ThnhlAxGtfvSbXcBVIKCYfZgqRvV+5lReUnd1aNjRYVzPOoifgSx2fRyy1+pO1Uz\r\n"
|
||||||
|
"aMMNnIOE71bVYW0A1hr19w7kOb0KkJXoALTDDj1ukUEDqQuBfBxReL5mXiu1O7WG\r\n"
|
||||||
|
"0vltg0VZ/SZzctBsdBlx1BkmWYBW261KZgBivrql5ELTKKd8qgtHcLQA5fl6JB0Q\r\n"
|
||||||
|
"gs5XDaWehN86Gps5JW8ArjGtjcWAIP+X8CQaWfaCnuRm6Bk/03PQWhgdi84qwA0s\r\n"
|
||||||
|
"sRfFJwHUPTNSnE8EiGVk2frt0u8PG1pwSQsFuNJfcYIHEv1vOzP7uEOuDydsmCjh\r\n"
|
||||||
|
"lxuoK2n5/2aVR3BMTu+p4+gl8alXoBycyLmj3J/PUgqD8SL5fTCUegGsdia/Sa60\r\n"
|
||||||
|
"N2oV7vQ17wjMN+LXa2rjj/b4ZlZgXVojDmAjDwIRdDUujQu0RVsJqFLMzSIHpp2C\r\n"
|
||||||
|
"Zp7mIoLrySay2YYBu7SiNwL95X6He2kS8eefBBHjzwW/9FxGqry57i71c2cDAgMB\r\n"
|
||||||
|
"AAGjggGtMIIBqTAdBgNVHQ4EFgQU1cFnOsKjnfR3UltZEjgp5lVou6UwHwYDVR0j\r\n"
|
||||||
|
"BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
|
||||||
|
"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
|
||||||
|
"CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
|
||||||
|
"Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
|
||||||
|
"aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
|
||||||
|
"cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
|
||||||
|
"MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
|
||||||
|
"cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
|
||||||
|
"AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQB2oWc93fB8esci/8esixj++N22meiGDjgF\r\n"
|
||||||
|
"+rA2LUK5IOQOgcUSTGKSqF9lYfAxPjrqPjDCUPHCURv+26ad5P/BYtXtbmtxJWu+\r\n"
|
||||||
|
"cS5BhMDPPeG3oPZwXRHBJFAkY4O4AF7RIAAUW6EzDflUoDHKv83zOiPfYGcpHc9s\r\n"
|
||||||
|
"kxAInCedk7QSgXvMARjjOqdakor21DTmNIUotxo8kHv5hwRlGhBJwps6fEVi1Bt0\r\n"
|
||||||
|
"trpM/3wYxlr473WSPUFZPgP1j519kLpWOJ8z09wxay+Br29irPcBYv0GMXlHqThy\r\n"
|
||||||
|
"8y4m/HyTQeI2IMvMrQnwqPpY+rLIXyviI2vLoI+4xKE4Rn38ZZ8m\r\n"
|
||||||
|
"-----END CERTIFICATE-----\r\n";
|
||||||
|
|
||||||
|
const char *SPEECH_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\r\n"
|
||||||
|
"MIIF8zCCBNugAwIBAgIQCq+mxcpjxFFB6jvh98dTFzANBgkqhkiG9w0BAQwFADBh\r\n"
|
||||||
|
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
|
||||||
|
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
|
||||||
|
"MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
|
||||||
|
"MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
|
||||||
|
"c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMTCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
|
||||||
|
"ggIPADCCAgoCggIBAMedcDrkXufP7pxVm1FHLDNA9IjwHaMoaY8arqqZ4Gff4xyr\r\n"
|
||||||
|
"RygnavXL7g12MPAx8Q6Dd9hfBzrfWxkF0Br2wIvlvkzW01naNVSkHp+OS3hL3W6n\r\n"
|
||||||
|
"l/jYvZnVeJXjtsKYcXIf/6WtspcF5awlQ9LZJcjwaH7KoZuK+THpXCMtzD8XNVdm\r\n"
|
||||||
|
"GW/JI0C/7U/E7evXn9XDio8SYkGSM63aLO5BtLCv092+1d4GGBSQYolRq+7Pd1kR\r\n"
|
||||||
|
"EkWBPm0ywZ2Vb8GIS5DLrjelEkBnKCyy3B0yQud9dpVsiUeE7F5sY8Me96WVxQcb\r\n"
|
||||||
|
"OyYdEY/j/9UpDlOG+vA+YgOvBhkKEjiqygVpP8EZoMMijephzg43b5Qi9r5UrvYo\r\n"
|
||||||
|
"o19oR/8pf4HJNDPF0/FJwFVMW8PmCBLGstin3NE1+NeWTkGt0TzpHjgKyfaDP2tO\r\n"
|
||||||
|
"4bCk1G7pP2kDFT7SYfc8xbgCkFQ2UCEXsaH/f5YmpLn4YPiNFCeeIida7xnfTvc4\r\n"
|
||||||
|
"7IxyVccHHq1FzGygOqemrxEETKh8hvDR6eBdrBwmCHVgZrnAqnn93JtGyPLi6+cj\r\n"
|
||||||
|
"WGVGtMZHwzVvX1HvSFG771sskcEjJxiQNQDQRWHEh3NxvNb7kFlAXnVdRkkvhjpR\r\n"
|
||||||
|
"GchFhTAzqmwltdWhWDEyCMKC2x/mSZvZtlZGY+g37Y72qHzidwtyW7rBetZJAgMB\r\n"
|
||||||
|
"AAGjggGtMIIBqTAdBgNVHQ4EFgQUDyBd16FXlduSzyvQx8J3BM5ygHYwHwYDVR0j\r\n"
|
||||||
|
"BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
|
||||||
|
"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
|
||||||
|
"CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
|
||||||
|
"Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
|
||||||
|
"aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
|
||||||
|
"cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
|
||||||
|
"MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
|
||||||
|
"cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
|
||||||
|
"AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQAlFvNh7QgXVLAZSsNR2XRmIn9iS8OHFCBA\r\n"
|
||||||
|
"WxKJoi8YYQafpMTkMqeuzoL3HWb1pYEipsDkhiMnrpfeYZEA7Lz7yqEEtfgHcEBs\r\n"
|
||||||
|
"K9KcStQGGZRfmWU07hPXHnFz+5gTXqzCE2PBMlRgVUYJiA25mJPXfB00gDvGhtYa\r\n"
|
||||||
|
"+mENwM9Bq1B9YYLyLjRtUz8cyGsdyTIG/bBM/Q9jcV8JGqMU/UjAdh1pFyTnnHEl\r\n"
|
||||||
|
"Y59Npi7F87ZqYYJEHJM2LGD+le8VsHjgeWX2CJQko7klXvcizuZvUEDTjHaQcs2J\r\n"
|
||||||
|
"+kPgfyMIOY1DMJ21NxOJ2xPRC/wAh/hzSBRVtoAnyuxtkZ4VjIOh\r\n"
|
||||||
|
"-----END CERTIFICATE-----\r\n";
|
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <sfud.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class FlashStream : public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FlashStream()
|
||||||
|
{
|
||||||
|
_pos = 0;
|
||||||
|
_flash_address = 0;
|
||||||
|
_flash = sfud_get_device_table() + 0;
|
||||||
|
|
||||||
|
populateBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t val)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int available()
|
||||||
|
{
|
||||||
|
int remaining = BUFFER_SIZE - ((_flash_address - HTTP_TCP_BUFFER_SIZE) + _pos);
|
||||||
|
int bytes_available = min(HTTP_TCP_BUFFER_SIZE, remaining);
|
||||||
|
|
||||||
|
if (bytes_available == 0)
|
||||||
|
{
|
||||||
|
bytes_available = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read()
|
||||||
|
{
|
||||||
|
int retVal = _buffer[_pos++];
|
||||||
|
|
||||||
|
if (_pos == HTTP_TCP_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
populateBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int peek()
|
||||||
|
{
|
||||||
|
return _buffer[_pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void populateBuffer()
|
||||||
|
{
|
||||||
|
sfud_read(_flash, _flash_address, HTTP_TCP_BUFFER_SIZE, _buffer);
|
||||||
|
_flash_address += HTTP_TCP_BUFFER_SIZE;
|
||||||
|
_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t _pos;
|
||||||
|
size_t _flash_address;
|
||||||
|
const sfud_flash *_flash;
|
||||||
|
|
||||||
|
byte _buffer[HTTP_TCP_BUFFER_SIZE];
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <sfud.h>
|
||||||
|
|
||||||
|
class FlashWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
_flash = sfud_get_device_table() + 0;
|
||||||
|
_sfudBufferSize = _flash->chip.erase_gran;
|
||||||
|
_sfudBuffer = new byte[_sfudBufferSize];
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
_sfudBufferWritePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
_sfudBufferWritePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeSfudBuffer(byte b)
|
||||||
|
{
|
||||||
|
_sfudBuffer[_sfudBufferPos++] = b;
|
||||||
|
if (_sfudBufferPos == _sfudBufferSize)
|
||||||
|
{
|
||||||
|
sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer);
|
||||||
|
_sfudBufferWritePos += _sfudBufferSize;
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushSfudBuffer()
|
||||||
|
{
|
||||||
|
if (_sfudBufferPos > 0)
|
||||||
|
{
|
||||||
|
sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer);
|
||||||
|
_sfudBufferWritePos += _sfudBufferSize;
|
||||||
|
_sfudBufferPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeSfudBuffer(byte *b, size_t len)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
{
|
||||||
|
writeSfudBuffer(b[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
byte *_sfudBuffer;
|
||||||
|
size_t _sfudBufferSize;
|
||||||
|
size_t _sfudBufferPos;
|
||||||
|
size_t _sfudBufferWritePos;
|
||||||
|
|
||||||
|
const sfud_flash *_flash;
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class LanguageUnderstanding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int GetTimerDuration(String text)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["text"] = text;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TEXT_TO_TIMER_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
int seconds = 0;
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
Serial.println(result);
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
seconds = obj["seconds"].as<int>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to understand text - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WiFiClient _client;
|
||||||
|
};
|
||||||
|
|
||||||
|
LanguageUnderstanding languageUnderstanding;
|
@ -0,0 +1,133 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <arduino-timer.h>
|
||||||
|
#include <rpcWiFi.h>
|
||||||
|
#include <sfud.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "language_understanding.h"
|
||||||
|
#include "mic.h"
|
||||||
|
#include "speech_to_text.h"
|
||||||
|
#include "text_to_speech.h"
|
||||||
|
#include "text_translator.h"
|
||||||
|
|
||||||
|
void connectWiFi()
|
||||||
|
{
|
||||||
|
while (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.println("Connecting to WiFi..");
|
||||||
|
WiFi.begin(SSID, PASSWORD);
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Connected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
while (!Serial)
|
||||||
|
; // Wait for Serial to be ready
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
connectWiFi();
|
||||||
|
|
||||||
|
while (!(sfud_init() == SFUD_SUCCESS))
|
||||||
|
;
|
||||||
|
|
||||||
|
sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q32_DEVICE_INDEX), 2);
|
||||||
|
|
||||||
|
pinMode(WIO_KEY_C, INPUT_PULLUP);
|
||||||
|
|
||||||
|
mic.init();
|
||||||
|
|
||||||
|
speechToText.init();
|
||||||
|
textToSpeech.init();
|
||||||
|
|
||||||
|
Serial.println("Ready.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto timer = timer_create_default();
|
||||||
|
|
||||||
|
void say(String text)
|
||||||
|
{
|
||||||
|
text = textTranslator.translateText(text, SERVER_LANGUAGE, LANGUAGE);
|
||||||
|
Serial.println(text);
|
||||||
|
textToSpeech.convertTextToSpeech(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timerExpired(void *announcement)
|
||||||
|
{
|
||||||
|
say((char *)announcement);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processAudio()
|
||||||
|
{
|
||||||
|
String text = speechToText.convertSpeechToText();
|
||||||
|
text = textTranslator.translateText(text, LANGUAGE, SERVER_LANGUAGE);
|
||||||
|
Serial.println(text);
|
||||||
|
|
||||||
|
int total_seconds = languageUnderstanding.GetTimerDuration(text);
|
||||||
|
if (total_seconds == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutes = total_seconds / 60;
|
||||||
|
int seconds = total_seconds % 60;
|
||||||
|
|
||||||
|
String begin_message;
|
||||||
|
if (minutes > 0)
|
||||||
|
{
|
||||||
|
begin_message += minutes;
|
||||||
|
begin_message += " minute ";
|
||||||
|
}
|
||||||
|
if (seconds > 0)
|
||||||
|
{
|
||||||
|
begin_message += seconds;
|
||||||
|
begin_message += " second ";
|
||||||
|
}
|
||||||
|
|
||||||
|
begin_message += "timer started.";
|
||||||
|
|
||||||
|
String end_message("Times up on your ");
|
||||||
|
if (minutes > 0)
|
||||||
|
{
|
||||||
|
end_message += minutes;
|
||||||
|
end_message += " minute ";
|
||||||
|
}
|
||||||
|
if (seconds > 0)
|
||||||
|
{
|
||||||
|
end_message += seconds;
|
||||||
|
end_message += " second ";
|
||||||
|
}
|
||||||
|
|
||||||
|
end_message += "timer.";
|
||||||
|
|
||||||
|
say(begin_message);
|
||||||
|
|
||||||
|
timer.in(total_seconds * 1000, timerExpired, (void *)(end_message.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (digitalRead(WIO_KEY_C) == LOW && !mic.isRecording())
|
||||||
|
{
|
||||||
|
Serial.println("Starting recording...");
|
||||||
|
mic.startRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mic.isRecording() && mic.isRecordingReady())
|
||||||
|
{
|
||||||
|
Serial.println("Finished recording");
|
||||||
|
|
||||||
|
processAudio();
|
||||||
|
|
||||||
|
mic.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.tick();
|
||||||
|
}
|
@ -0,0 +1,242 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "flash_writer.h"
|
||||||
|
|
||||||
|
class Mic
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Mic()
|
||||||
|
{
|
||||||
|
_isRecording = false;
|
||||||
|
_isRecordingReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startRecording()
|
||||||
|
{
|
||||||
|
_isRecording = true;
|
||||||
|
_isRecordingReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRecording()
|
||||||
|
{
|
||||||
|
return _isRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRecordingReady()
|
||||||
|
{
|
||||||
|
return _isRecordingReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
analogReference(AR_INTERNAL2V23);
|
||||||
|
|
||||||
|
_writer.init();
|
||||||
|
|
||||||
|
initBufferHeader();
|
||||||
|
configureDmaAdc();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
_isRecordingReady = false;
|
||||||
|
_isRecording = false;
|
||||||
|
|
||||||
|
_writer.reset();
|
||||||
|
|
||||||
|
initBufferHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dmaHandler()
|
||||||
|
{
|
||||||
|
static uint8_t count = 0;
|
||||||
|
|
||||||
|
if (DMAC->Channel[1].CHINTFLAG.bit.SUSP)
|
||||||
|
{
|
||||||
|
DMAC->Channel[1].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;
|
||||||
|
DMAC->Channel[1].CHINTFLAG.bit.SUSP = 1;
|
||||||
|
|
||||||
|
if (count)
|
||||||
|
{
|
||||||
|
audioCallback(_adc_buf_0, ADC_BUF_LEN);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
audioCallback(_adc_buf_1, ADC_BUF_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = (count + 1) % 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile bool _isRecording;
|
||||||
|
volatile bool _isRecordingReady;
|
||||||
|
FlashWriter _writer;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t btctrl;
|
||||||
|
uint16_t btcnt;
|
||||||
|
uint32_t srcaddr;
|
||||||
|
uint32_t dstaddr;
|
||||||
|
uint32_t descaddr;
|
||||||
|
} dmacdescriptor;
|
||||||
|
|
||||||
|
// Globals - DMA and ADC
|
||||||
|
volatile dmacdescriptor _wrb[DMAC_CH_NUM] __attribute__((aligned(16)));
|
||||||
|
dmacdescriptor _descriptor_section[DMAC_CH_NUM] __attribute__((aligned(16)));
|
||||||
|
dmacdescriptor _descriptor __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
void configureDmaAdc()
|
||||||
|
{
|
||||||
|
// Configure DMA to sample from ADC at a regular interval (triggered by timer/counter)
|
||||||
|
DMAC->BASEADDR.reg = (uint32_t)_descriptor_section; // Specify the location of the descriptors
|
||||||
|
DMAC->WRBADDR.reg = (uint32_t)_wrb; // Specify the location of the write back descriptors
|
||||||
|
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral
|
||||||
|
DMAC->Channel[1].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC5_DMAC_ID_OVF) | // Set DMAC to trigger on TC5 timer overflow
|
||||||
|
DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer
|
||||||
|
|
||||||
|
_descriptor.descaddr = (uint32_t)&_descriptor_section[1]; // Set up a circular descriptor
|
||||||
|
_descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register
|
||||||
|
_descriptor.dstaddr = (uint32_t)_adc_buf_0 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_0 array
|
||||||
|
_descriptor.btcnt = ADC_BUF_LEN; // Beat count
|
||||||
|
_descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
|
||||||
|
DMAC_BTCTRL_DSTINC | // Increment the destination address
|
||||||
|
DMAC_BTCTRL_VALID | // Descriptor is valid
|
||||||
|
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 0 after block transfer
|
||||||
|
memcpy(&_descriptor_section[0], &_descriptor, sizeof(_descriptor)); // Copy the descriptor to the descriptor section
|
||||||
|
|
||||||
|
_descriptor.descaddr = (uint32_t)&_descriptor_section[0]; // Set up a circular descriptor
|
||||||
|
_descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register
|
||||||
|
_descriptor.dstaddr = (uint32_t)_adc_buf_1 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_1 array
|
||||||
|
_descriptor.btcnt = ADC_BUF_LEN; // Beat count
|
||||||
|
_descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
|
||||||
|
DMAC_BTCTRL_DSTINC | // Increment the destination address
|
||||||
|
DMAC_BTCTRL_VALID | // Descriptor is valid
|
||||||
|
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 0 after block transfer
|
||||||
|
memcpy(&_descriptor_section[1], &_descriptor, sizeof(_descriptor)); // Copy the descriptor to the descriptor section
|
||||||
|
|
||||||
|
// Configure NVIC
|
||||||
|
NVIC_SetPriority(DMAC_1_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC1 to 0 (highest)
|
||||||
|
NVIC_EnableIRQ(DMAC_1_IRQn); // Connect DMAC1 to Nested Vector Interrupt Controller (NVIC)
|
||||||
|
|
||||||
|
// Activate the suspend (SUSP) interrupt on DMAC channel 1
|
||||||
|
DMAC->Channel[1].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;
|
||||||
|
|
||||||
|
// Configure ADC
|
||||||
|
ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN12_Val; // Set the analog input to ADC0/AIN2 (PB08 - A4 on Metro M4)
|
||||||
|
while (ADC1->SYNCBUSY.bit.INPUTCTRL)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->SAMPCTRL.bit.SAMPLEN = 0x00; // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
|
||||||
|
while (ADC1->SYNCBUSY.bit.SAMPCTRL)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128; // Divide Clock ADC GCLK by 128 (48MHz/128 = 375kHz)
|
||||||
|
ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT | // Set ADC resolution to 12 bits
|
||||||
|
ADC_CTRLB_FREERUN; // Set ADC to free run mode
|
||||||
|
while (ADC1->SYNCBUSY.bit.CTRLB)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->CTRLA.bit.ENABLE = 1; // Enable the ADC
|
||||||
|
while (ADC1->SYNCBUSY.bit.ENABLE)
|
||||||
|
; // Wait for synchronization
|
||||||
|
ADC1->SWTRIG.bit.START = 1; // Initiate a software trigger to start an ADC conversion
|
||||||
|
while (ADC1->SYNCBUSY.bit.SWTRIG)
|
||||||
|
; // Wait for synchronization
|
||||||
|
|
||||||
|
// Enable DMA channel 1
|
||||||
|
DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1;
|
||||||
|
|
||||||
|
// Configure Timer/Counter 5
|
||||||
|
GCLK->PCHCTRL[TC5_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable perhipheral channel for TC5
|
||||||
|
GCLK_PCHCTRL_GEN_GCLK1; // Connect generic clock 0 at 48MHz
|
||||||
|
|
||||||
|
TC5->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TC5 to Match Frequency (MFRQ) mode
|
||||||
|
TC5->COUNT16.CC[0].reg = 3000 - 1; // Set the trigger to 16 kHz: (4Mhz / 16000) - 1
|
||||||
|
while (TC5->COUNT16.SYNCBUSY.bit.CC0)
|
||||||
|
; // Wait for synchronization
|
||||||
|
|
||||||
|
// Start Timer/Counter 5
|
||||||
|
TC5->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC5 timer
|
||||||
|
while (TC5->COUNT16.SYNCBUSY.bit.ENABLE)
|
||||||
|
; // Wait for synchronization
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _adc_buf_0[ADC_BUF_LEN];
|
||||||
|
uint16_t _adc_buf_1[ADC_BUF_LEN];
|
||||||
|
|
||||||
|
// WAV files have a header. This struct defines that header
|
||||||
|
struct wavFileHeader
|
||||||
|
{
|
||||||
|
char riff[4]; /* "RIFF" */
|
||||||
|
long flength; /* file length in bytes */
|
||||||
|
char wave[4]; /* "WAVE" */
|
||||||
|
char fmt[4]; /* "fmt " */
|
||||||
|
long chunk_size; /* size of FMT chunk in bytes (usually 16) */
|
||||||
|
short format_tag; /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */
|
||||||
|
short num_chans; /* 1=mono, 2=stereo */
|
||||||
|
long srate; /* Sampling rate in samples per second */
|
||||||
|
long bytes_per_sec; /* bytes per second = srate*bytes_per_samp */
|
||||||
|
short bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo */
|
||||||
|
short bits_per_samp; /* Number of bits per sample */
|
||||||
|
char data[4]; /* "data" */
|
||||||
|
long dlength; /* data length in bytes (filelength - 44) */
|
||||||
|
};
|
||||||
|
|
||||||
|
void initBufferHeader()
|
||||||
|
{
|
||||||
|
wavFileHeader wavh;
|
||||||
|
|
||||||
|
strncpy(wavh.riff, "RIFF", 4);
|
||||||
|
strncpy(wavh.wave, "WAVE", 4);
|
||||||
|
strncpy(wavh.fmt, "fmt ", 4);
|
||||||
|
strncpy(wavh.data, "data", 4);
|
||||||
|
|
||||||
|
wavh.chunk_size = 16;
|
||||||
|
wavh.format_tag = 1; // PCM
|
||||||
|
wavh.num_chans = 1; // mono
|
||||||
|
wavh.srate = RATE;
|
||||||
|
wavh.bytes_per_sec = (RATE * 1 * 16 * 1) / 8;
|
||||||
|
wavh.bytes_per_samp = 2;
|
||||||
|
wavh.bits_per_samp = 16;
|
||||||
|
wavh.dlength = RATE * 2 * 1 * 16 / 2;
|
||||||
|
wavh.flength = wavh.dlength + 44;
|
||||||
|
|
||||||
|
_writer.writeSfudBuffer((byte *)&wavh, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audioCallback(uint16_t *buf, uint32_t buf_len)
|
||||||
|
{
|
||||||
|
static uint32_t idx = 44;
|
||||||
|
|
||||||
|
if (_isRecording)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < buf_len; i++)
|
||||||
|
{
|
||||||
|
int16_t audio_value = ((int16_t)buf[i] - 2048) * 16;
|
||||||
|
|
||||||
|
_writer.writeSfudBuffer(audio_value & 0xFF);
|
||||||
|
_writer.writeSfudBuffer((audio_value >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += buf_len;
|
||||||
|
|
||||||
|
if (idx >= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
_writer.flushSfudBuffer();
|
||||||
|
idx = 44;
|
||||||
|
_isRecording = false;
|
||||||
|
_isRecordingReady = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mic mic;
|
||||||
|
|
||||||
|
void DMAC_1_Handler()
|
||||||
|
{
|
||||||
|
mic.dmaHandler();
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "flash_stream.h"
|
||||||
|
|
||||||
|
class SpeechToText
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
_token_client.setCACert(TOKEN_CERTIFICATE);
|
||||||
|
_speech_client.setCACert(SPEECH_CERTIFICATE);
|
||||||
|
_access_token = getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertSpeechToText()
|
||||||
|
{
|
||||||
|
char url[128];
|
||||||
|
sprintf(url, SPEECH_URL, SPEECH_LOCATION, LANGUAGE);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_speech_client, url);
|
||||||
|
|
||||||
|
httpClient.addHeader("Authorization", String("Bearer ") + _access_token);
|
||||||
|
httpClient.addHeader("Content-Type", String("audio/wav; codecs=audio/pcm; samplerate=") + String(RATE));
|
||||||
|
httpClient.addHeader("Accept", "application/json;text/xml");
|
||||||
|
|
||||||
|
Serial.println("Sending speech...");
|
||||||
|
|
||||||
|
FlashStream stream;
|
||||||
|
int httpResponseCode = httpClient.sendRequest("POST", &stream, BUFFER_SIZE);
|
||||||
|
|
||||||
|
Serial.println("Speech sent!");
|
||||||
|
|
||||||
|
String text = "";
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
Serial.println(result);
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
text = obj["DisplayText"].as<String>();
|
||||||
|
}
|
||||||
|
else if (httpResponseCode == 401)
|
||||||
|
{
|
||||||
|
Serial.println("Access token expired, trying again with a new token");
|
||||||
|
_access_token = getAccessToken();
|
||||||
|
return convertSpeechToText();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to convert text to speech - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
String getAccessToken()
|
||||||
|
{
|
||||||
|
char url[128];
|
||||||
|
sprintf(url, TOKEN_URL, SPEECH_LOCATION);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_token_client, url);
|
||||||
|
|
||||||
|
httpClient.addHeader("Ocp-Apim-Subscription-Key", SPEECH_API_KEY);
|
||||||
|
int httpResultCode = httpClient.POST("{}");
|
||||||
|
|
||||||
|
if (httpResultCode != 200)
|
||||||
|
{
|
||||||
|
Serial.println("Error getting access token, trying again...");
|
||||||
|
delay(10000);
|
||||||
|
return getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Got access token.");
|
||||||
|
String result = httpClient.getString();
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFiClientSecure _token_client;
|
||||||
|
WiFiClientSecure _speech_client;
|
||||||
|
String _access_token;
|
||||||
|
};
|
||||||
|
|
||||||
|
SpeechToText speechToText;
|
@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <Seeed_FS.h>
|
||||||
|
#include <SD/Seeed_SD.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class TextToSpeech
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["language"] = LANGUAGE;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, GET_VOICES_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
String result = httpClient.getString();
|
||||||
|
Serial.println(result);
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, result.c_str());
|
||||||
|
|
||||||
|
JsonArray obj = doc.as<JsonArray>();
|
||||||
|
_voice = obj[0].as<String>();
|
||||||
|
|
||||||
|
Serial.print("Using voice ");
|
||||||
|
Serial.println(_voice);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to get voices - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void convertTextToSpeech(String text)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["language"] = LANGUAGE;
|
||||||
|
doc["voice"] = _voice;
|
||||||
|
doc["text"] = text;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TEXT_TO_SPEECH_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
File wav_file = SD.open("SPEECH.WAV", FILE_WRITE);
|
||||||
|
httpClient.writeToStream(&wav_file);
|
||||||
|
wav_file.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to get speech - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
WiFiClient _client;
|
||||||
|
String _voice;
|
||||||
|
};
|
||||||
|
|
||||||
|
TextToSpeech textToSpeech;
|
@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
class TextTranslator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
String translateText(String text, String from_language, String to_language)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["text"] = text;
|
||||||
|
doc["from_language"] = from_language;
|
||||||
|
doc["to_language"] = to_language;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
Serial.print("Translating ");
|
||||||
|
Serial.print(text);
|
||||||
|
Serial.print(" from ");
|
||||||
|
Serial.print(from_language);
|
||||||
|
Serial.print(" to ");
|
||||||
|
Serial.println(to_language);
|
||||||
|
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TRANSLATE_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
|
||||||
|
String translated_text = "";
|
||||||
|
|
||||||
|
if (httpResponseCode == 200)
|
||||||
|
{
|
||||||
|
translated_text = httpClient.getString();
|
||||||
|
Serial.print("Translated: ");
|
||||||
|
Serial.println(translated_text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("Failed to translate text - error ");
|
||||||
|
Serial.println(httpResponseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return translated_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WiFiClient _client;
|
||||||
|
};
|
||||||
|
|
||||||
|
TextTranslator textTranslator;
|
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/page/plus/unit-testing.html
|
Loading…
Reference in new issue