@ -44,7 +44,7 @@ Instead of downloading and decoding this entire list on your microcontroller, yo
1. Open the `local.settings.json` file and add settings for the speech API key and location:
```json
"SPEECH_KEY": "<key>`",
"SPEECH_KEY": "<key>",
"SPEECH_LOCATION": "<location>"
```
@ -103,6 +103,8 @@ Instead of downloading and decoding this entire list on your microcontroller, yo
Replace `<language>` with your language, such as `en-GB`, or `zh-CN`.
> 💁 You can find this code in the [code-spoken-response/functions](code-spoken-response/functions) folder.
### Task - retrieve the voice from your Wio Terminal
1. Open the `smart-timer` project in VS Code if it is not already open.
@ -190,8 +192,7 @@ Instead of downloading and decoding this entire list on your microcontroller, yo
doc["language"] = LANGUAGE;
String body;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, body);
serializeJson(doc, body);
```
1. Next create an `HTTPClient`, then use it to call the functions app to get the voices, posting the JSON document:
@ -397,6 +398,8 @@ When needing to manipulate data like this, it is often better to use serverless
This will save the audio to `hello.wav` in the current directory.
> 💁 You can find this code in the [code-spoken-response/functions](code-spoken-response/functions) folder.
### Task - retrieve the speech from your Wio Terminal
1. Open the `smart-timer` project in VS Code if it is not already open.
@ -426,8 +429,7 @@ When needing to manipulate data like this, it is often better to use serverless
doc["text"] = text;
String body;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, body);
serializeJson(doc, body);
```
This writes the language, voice and text to the JSON document, then serializes it to a string.
@ -469,44 +471,19 @@ When needing to manipulate data like this, it is often better to use serverless
### Task - play audio from your Wio Terminal
**Coming soon**
## Deploying your functions app to the cloud
The reason for running the functions app locally is because the `librosa` Pip package on linux has a dependency on a library that is not installed by default, and will need to be installed before the function app can run. Function apps are serverless - there are no servers you can manage yourself, so no way to install this library up front.
#include<WiFiClientSecure.h>
The way to do this is instead to deploy your functions app using a Docker container. This container is deployed by the cloud whenever it needs to spin up a new instance of your function app (such as when the demand exceeds the available resources, or if the function app hasn't been used for a while and is closed down).
You can find the instructions to set up a function app and deploy via Docker in the [create a function on Linux using a custom container documentation on Microsoft Docs](https://docs.microsoft.com/azure/azure-functions/functions-create-function-linux-custom-image?tabs=bash%2Cazurecli&pivots=programming-language-python&WT.mc_id=academic-17441-jabenn).
Once this has been deployed, you can port your Wio Terminal code to access this function:
* If you are running the function app in the cloud, add the following to the `private` section of the class:
```cpp
WiFiClientSecure _client;
```
You will also need to set the certificate on this class, so add the following constructor to the `public` section:
```cpp
LanguageUnderstanding()
{
_client.setCACert(FUNCTIONS_CERTIFICATE);
}
```
1. If you have deployed your functions app to the cloud, add the following certificate for `azurewebsites.net` to the `config.h` file.
1. Add the Azure Functions certificate to `config.h`:
```cpp
const char *FUNCTIONS_CERTIFICATE =
@ -543,4 +520,12 @@ When needing to manipulate data like this, it is often better to use serverless
"-----END CERTIFICATE-----\r\n";
```
> 💁 If you are accessing your functions app locally, you don't need to do this.
1. Change all includes of `<WiFiClient.h>` to `<WiFiClientSecure.h>`.
1. Change all `WiFiClient` fields to `WiFiClientSecure`.
1. In every class that has a `WiFiClientSecure` field, add a constructor and set the certificate in that constructor:
@ -8,7 +8,7 @@ The speech service REST API doesn't support direct translations, instead you can
### Task - use the translator resource to translate text
1. Your smart timer will have 2 languages set - the language of the server that was used to train LUIS, and the language spoken by the user. Update the `language` variable to be the language that will be spoken by the used, and add a new variable called `server_language` for the language used to train LUIS:
1. Your smart timer will have 2 languages set - the language of the server that was used to train LUIS (the same language is also used to build the messages to speak to the user), and the language spoken by the user. Update the `language` variable to be the language that will be spoken by the user, and add a new variable called `server_language` for the language used to train LUIS:
@ -20,7 +20,7 @@ The speech service can take speech and not only convert to text in the same lang
This imports classes used to translate speech, and a `requests` library that will be used to make a call to the Translator service later in this lesson.
1. Your smart timer will have 2 languages set - the language of the server that was used to train LUIS, and the language spoken by the user. Update the `language` variable to be the language that will be spoken by the used, and add a new variable called `server_language` for the language used to train LUIS:
1. Your smart timer will have 2 languages set - the language of the server that was used to train LUIS (the same language is also used to build the messages to speak to the user), and the language spoken by the user. Update the `language` variable to be the language that will be spoken by the user, and add a new variable called `server_language` for the language used to train LUIS:
@ -4,178 +4,267 @@ In this part of the lesson, you will write code to translate text using the tran
## Convert text to speech using the translator service
The speech service REST API doesn't support direct translations, instead you can use the Translator service to translate the text generated by the speech to text service, and the text of the spoken response. This service has a REST API you can use to translate the text.
The speech service REST API doesn't support direct translations, instead you can use the Translator service to translate the text generated by the speech to text service, and the text of the spoken response. This service has a REST API you can use to translate the text, but to make it easier to use this will be wrapped in another HTTP trigger in your functions app.
### Task - use the translator resource to translate text
### Task - create a serverless function to translate text
1. Open the `smart-timer` project in VS Code if it is not already open.
1. Open your `smart-timer-trigger` project in VS Code, and open the terminal ensuring the virtual environment is activated. If not, kill and re-create the terminal.
1. Your smart timer will have 2 languages set - the language of the server that was used to train LUIS, and the language spoken by the user. Update the `LANGUAGE` constant in the `config.h` header file to be the language that will be spoken by the used, and add a new constant called `SERVER_LANGUAGE` for the language used to train LUIS:
1. Open the `local.settings.json` file and add settings for the translator API key and location:
```cpp
const char *LANGUAGE = "<userlanguage>";
const char *SERVER_LANGUAGE = "<serverlanguage>";
```json
"TRANSLATOR_KEY": "<key>",
"TRANSLATOR_LOCATION": "<location>"
```
Replace `<user language>` with the locale name for language you will be speaking in, for example `fr-FR` for French, or `zn-HK` for Cantonese.
Replace `<key>` with the API key for your translator service resource. Replace `<location>` with the location you used when you created the translator service resource.
Replace `<server language>` with the locale name for language used to train LUIS.
1. Add a new HTTP trigger to this app called `translate-text` using the following command from inside the VS Code terminal in the root folder of the functions app project:
You can find a list of the supported languages and their locale names in the [Language and voice support documentation on Microsoft docs](https://docs.microsoft.com/azure/cognitive-services/speech-service/language-support?WT.mc_id=academic-17441-jabenn#speech-to-text).
```sh
func new --name translate-text --template "HTTP trigger"
```
> 💁 If you don't speak multiple languages you can use a service like [Bing Translate](https://www.bing.com/translator) or [Google Translate](https://translate.google.com) to translate from your preferred language to a language of your choice. These services can then play audio of the translated text.
>
> For example, if you train LUIS in English, but want to use French as the user language, you can translate sentences like "set a 2 minute and 27 second timer" from English into French using Bing Translate, then use the **Listen translation** button to speak the translation into your microphone.
>
> 
This will create an HTTP trigger called `translate-text`.
1. Add the translator API key below the `SPEECH_API_KEY`:
1. Replace the contents of the `__init__.py` file in the `translate-text` folder with the following:
```cpp
const char *TRANSLATOR_API_KEY = "<key>";
```
```python
import logging
import os
import requests
Replace `<key>` with the API key for your translator service resource.
This is a global URL and doesn't need your location in it, instead the location of the translator resource is passed as a header. This URL will need the from and to languages - so the language that the text is in and the language you want it translated to.
logging.info(f'Translating {text} from {from_language} to {to_language}')
This code extracts the text and the languages from the HTTP request. It then makes a request to the translator REST API, passing the languages as parameters for the URL and the text to translate as the body. Finally, the translation is returned.
1. Run your function app locally. You can then call this using a tool like curl in the same way that you tested your `text-to-timer` HTTP trigger. Make sure to pass the text to translate and the languages as a JSON body:
```json
{
"text": "Définir une minuterie de 30 secondes",
"from_language": "fr-FR",
"to_language": "en-US"
}
```
This example translates *Définir une minuterie de 30 secondes* from French to US English. It will return *Set a 30-second timer*.
> 💁 You can find this code in the [code/functions](code/functions) folder.
### Task - use the translator function to translate text
1. Open the `smart-timer` project in VS Code if it is not already open.
1. Your smart timer will have 2 languages set - the language of the server that was used to train LUIS (the same language is also used to build the messages to speak to the user), and the language spoken by the user. Update the `LANGUAGE` constant in the `config.h` header file to be the language that will be spoken by the user, and add a new constant called `SERVER_LANGUAGE` for the language used to train LUIS:
```cpp
const char *LANGUAGE = "<userlanguage>";
const char *SERVER_LANGUAGE = "<serverlanguage>";
```
Replace `<user language>` with the locale name for language you will be speaking in, for example `fr-FR` for French, or `zn-HK` for Cantonese.
Replace `<server language>` with the locale name for language used to train LUIS.
You can find a list of the supported languages and their locale names in the [Language and voice support documentation on Microsoft docs](https://docs.microsoft.com/azure/cognitive-services/speech-service/language-support?WT.mc_id=academic-17441-jabenn#speech-to-text).
> 💁 If you don't speak multiple languages you can use a service like [Bing Translate](https://www.bing.com/translator) or [Google Translate](https://translate.google.com) to translate from your preferred language to a language of your choice. These services can then play audio of the translated text.
>
> For example, if you train LUIS in English, but want to use French as the user language, you can translate sentences like "set a 2 minute and 27 second timer" from English into French using Bing Translate, then use the **Listen translation** button to speak the translation into your microphone.
>
> 
1. Add the translator API key and location below the `SPEECH_LOCATION`:
```cpp
const char *TRANSLATOR_API_KEY = "<KEY>";
const char *TRANSLATOR_LOCATION = "<LOCATION>";
```
1. Above the `say` function, define a `translate_text` function that will translate text from the server language to the user language:
Replace `<KEY>` with the API key for your translator service resource. Replace `<LOCATION>` with the location you used when you created the translator service resource.
1. Add the translator trigger URL below the `VOICE_URL`:
```cpp
const char *TRANSLATE_FUNCTION_URL = "<URL>";
```
The from and to languages are passed to this function - your app needs to convert from user language to server language when recognizing speech, and from server language to user language when provided spoken feedback.
Replace `<URL>` with the URL for the `translate-text` HTTP trigger on your function app. This will be the same as the value for `TEXT_TO_TIMER_FUNCTION_URL`, except with a function name of `translate-text` instead of `text-to-timer`.
1. Inside this function, define the URL and headers for the REST API call:
1. Add a new file to the `src` folder called `text_translator.h`.
1. This new `text_translator.h` header file will contain a class to translate text. Add the following to this file to declare this class:
headers = {
'Ocp-Apim-Subscription-Key': translator_api_key,
'Ocp-Apim-Subscription-Region': location,
'Content-type': 'application/json'
}
```cpp
#pragma once
#include<Arduino.h>
#include<ArduinoJson.h>
#include<HTTPClient.h>
#include<WiFiClient.h>
#include "config.h"
class TextTranslator
{
public:
private:
WiFiClient _client;
};
TextTranslator textTranslator;
```
The URL for this API is not location specific, instead the location is passed in as a header. The API key is used directly, so unlike the speech service there is no need to get an access token from the token issuer API.
This declares the `TextTranslator` class, along with an instance of this class. The class has a single field for the WiFi client.
1. Below this define the parameters and body for the call:
1. To the `public` section of this class, add a method to translate text:
The `params` defines the parameters to pass to the API call, passing the from and to languages. This call will translate text in the `from` language into the `to` language.
This method takes the language to translate from, and the language to translate to. When handling speech, the speech will be translated from the user language to the LUIS server language, and when giving responses it will translate from the LUIS server language to the users language.
The `body` contains the text to translate. This is an array, as multiple blocks of text can be translated in the same call.
1. In this method, add code to construct a JSON body containing the text to translate and the languages:
1. Make the call the REST API, and get the response:
The response that comes back is a JSON array, with one item that contains the translations. This item has an array for translations of all the items passed in the body.
1. Next, add code to get the response:
```json
[
{
"translations": [
```cpp
String translated_text = "";
if (httpResponseCode == 200)
{
"text": "Set a 2 minute 27 second timer.",
"to": "en"
translated_text = httpClient.getString();
Serial.print("Translated: ");
Serial.println(translated_text);
}
]
else
{
Serial.print("Failed to translate text - error ");
Serial.println(httpResponseCode);
}
]
```
1. Return the `test` property from the first translation from the first item in the array:
1. Finally, add code to close the connection and return the translated text:
1. Update the `while True` loop to translate the text from the call to `convert_speech_to_text` from the user language to the server language:
### Task - translate the recognized speech and the responses
```python
if len(text) > 0:
print('Original:', text)
text = translate_text(text, language, server_language)
print('Translated:', text)
1. Open the `main.cpp` file.
1. Add an include directive at the top of the file for the `TextTranslator` class header file:
```cpp
#include "text_translator.h"
```
message = Message(json.dumps({ 'speech': text }))
device_client.send_message(message)
1. The text that is said when a timer is set or expires needs to be translated. To do this, add the following as the first line of the `say` function:
```cpp
text = textTranslator.translateText(text, LANGUAGE, SERVER_LANGUAGE);
```
This code also prints the original and translated versions of the text to the console.
This will translate the text to the users language.
1. Update the `say` function to translate the text to say from the server language to the user language:
1. In the `processAudio` function, text is retrieved from the captured audio with the `String text = speechToText.convertSpeechToText();` call. After this call, translate the text:
```python
def say(text):
print('Original:', text)
text = translate_text(text, server_language, language)
print('Translated:', text)
speech = get_speech(text)
play_speech(speech)
```cpp
String text = speechToText.convertSpeechToText();
text = textTranslator.translateText(text, LANGUAGE, SERVER_LANGUAGE);
```
This code also prints the original and translated versions of the text to the console.
This will translate the text from the users language into the language used on the server.
1. Run your code. Ensure your function app is running, and request a timer in the user language, either by speaking that language yourself, or using a translation app.
1. Build this code, upload it to your Wio Terminal and test it out through the serial monitor. Once you see `Ready` in the serial monitor, press the C button (the one on the left-hand side, closest to the power switch), and speak. Ensure your function app is running, and request a timer in the user language, either by speaking that language yourself, or using a translation app.
```output
pi@raspberrypi:~/smart-timer $ python3 app.py
Connecting
Connected
Using voice fr-FR-DeniseNeural
Original: Définir une minuterie de 2 minutes et 27 secondes.
Translated: Set a timer of 2 minutes and 27 seconds.
Original: 2 minute 27 second timer started.
Connecting to WiFi..
Connected!
Got access token.
Ready.
Starting recording...
Finished recording
Sending speech...
Speech sent!
{"RecognitionStatus":"Success","DisplayText":"Définir une minuterie de 2 minutes 27 secondes.","Offset":9600000,"Duration":40400000}
Translating Définir une minuterie de 2 minutes 27 secondes. from fr-FR to en-US
Translated: Set a timer of 2 minutes 27 seconds.
Set a timer of 2 minutes 27 seconds.
{"seconds": 147}
Translating 2 minute 27 second timer started. from en-US to fr-FR
Translated: 2 minute 27 seconde minute a commencé.
Original: Times up on your 2 minute 27 second timer.
2 minute 27 seconde minute a commencé.
Translating Times up on your 2 minute 27 second timer. from en-US to fr-FR
Translated: Chronométrant votre minuterie de 2 minutes 27 secondes.
Chronométrant votre minuterie de 2 minutes 27 secondes.
```
> 💁 Due to the different ways of saying something in different languages, you may get translations that are slightly different to the examples you gave LUIS. If this is the case, add more examples to LUIS, retrain then re-publish the model.
> 💁 You can find this code in the [code/pi](code/pi) folder.
> 💁 You can find this code in the [code/wio-terminal](code/wio-terminal) folder.