commit
cd386846b4
@ -0,0 +1,13 @@
|
|||||||
|
# Investigue um projeto de IoT
|
||||||
|
|
||||||
|
## Instruções
|
||||||
|
|
||||||
|
Existem muitos projetos de IoT de grande e pequena escala sendo implementados globalmente, de fazendas inteligentes a cidades inteligentes, em monitoramento de saúde, transporte e para o uso de espaços públicos.
|
||||||
|
|
||||||
|
Pesquise na web por detalhes de um projeto do seu interesse, de preferência um perto de onde você mora. Explique as vantagens e desvantagens do projeto, por exemplo, quais os benefícios dele, quaisquer problemas que ele causa e como a privacidade é levada em consideração.
|
||||||
|
|
||||||
|
## Rubrica
|
||||||
|
|
||||||
|
| Critérios | Exemplar | Adequado | Precisa Melhorar |
|
||||||
|
| --------- | -------- | -------- | ---------------- |
|
||||||
|
| Explique as vantagens e desvantagens | Deu uma explicação clara das vantagens e desvantagens do projeto | Deu uma breve explicação sobre as vantagens e desvantagens | Não explicou as vantagens ou desvantagens |
|
@ -0,0 +1,245 @@
|
|||||||
|
# Raspberry Pi
|
||||||
|
|
||||||
|
O [Raspberry Pi](https://raspberrypi.org) é um computador de placa única. Você pode adicionar sensores e atuadores usando uma ampla variedade de dispositivos e ecossistemas, e para essas lições, usando um ecossistema de hardware chamado [Grove](https://www.seeedstudio.com/category/Grove-c-1003.html). Você codificará seu Pi e acessará os sensores Grove usando Python.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Configuração
|
||||||
|
|
||||||
|
Se você estiver usando um Raspberry Pi como seu hardware IoT, você tem duas opções - você pode trabalhar em todas essas lições e codificar diretamente no Pi ou pode se conectar remotamente a um Pi 'headless' e codificar de seu computador.
|
||||||
|
|
||||||
|
Antes de começar, você também precisa conectar o Grove Base Hat ao seu Pi.
|
||||||
|
|
||||||
|
### Tarefa - configuração
|
||||||
|
|
||||||
|
Instale o Grove Base Hat no seu Pi e configure o Pi
|
||||||
|
|
||||||
|
1. Conecte o Grove Base Hat ao seu Pi. O soquete no Grove Base Hat se encaixa em todos os pinos de GPIO no Pi, deslizando para baixo sobre os pinos para que se assente firmemente na base. Ele fica sobre o Pi, cobrindo-o.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Decida como você deseja programar seu Pi e vá para a seção relevante abaixo:
|
||||||
|
|
||||||
|
* [Trabalhe diretamente no seu Pi](#trabalhe-diretamente-no-seu-pi)
|
||||||
|
* [Acesso remoto para codificar o Pi](#acesso-remoto-para-codificar-o-pi)
|
||||||
|
|
||||||
|
### Trabalhe diretamente no seu Pi
|
||||||
|
|
||||||
|
Se você quiser trabalhar diretamente no seu Pi, pode usar a versão desktop do Raspberry Pi OS e instalar todas as ferramentas de que precisa.
|
||||||
|
|
||||||
|
#### Tarefa - trabalhe diretamente no seu Pi
|
||||||
|
|
||||||
|
Configure seu Pi para desenvolvimento.
|
||||||
|
|
||||||
|
1. Siga as instruções no [guia de configuração do Raspberry Pi](https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up) para configurar seu Pi, conecte-o a um teclado/mouse/monitor, conecte-o à sua rede WiFi ou ethernet e atualize o software. O sistema operacional que você deseja instalar é o **Raspberry Pi OS (32 bits)**, ele é marcado como o sistema operacional recomendado ao usar o Raspberry Pi Imager para criar a imagem do seu cartão SD.
|
||||||
|
|
||||||
|
Para programar o Pi usando os sensores e atuadores Grove, você precisará instalar um editor para permitir que você escreva o código do dispositivo e várias bibliotecas e ferramentas que interagem com o hardware Grove.
|
||||||
|
|
||||||
|
1. Assim que seu Pi for reiniciado, inicie o Terminal clicando no ícone **Terminal** na barra de menu superior ou escolha *Menu -> Acessórios -> Terminal*
|
||||||
|
|
||||||
|
1. Execute o seguinte comando para garantir que o sistema operacional e o software instalado estejam atualizados:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt update && sudo apt full-upgrade --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Execute o seguinte comando para instalar todas as bibliotecas necessárias para o hardware Grove:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s -
|
||||||
|
```
|
||||||
|
|
||||||
|
Um dos recursos poderosos do Python é a capacidade de instalar [pacotes pip](https://pypi.org) - são pacotes de código escritos por outras pessoas e publicados na Internet. Você pode instalar um pacote pip em seu computador com um comando e, em seguida, usar esse pacote em seu código. Este script de instalação do Grove instalará os pacotes pip que você usará para trabalhar com o hardware Grove a partir do Python.
|
||||||
|
|
||||||
|
1. Reinicialize o Pi usando o menu ou executando o seguinte comando no Terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Após a reinicialização do Pi, reinicie o Terminal e execute o seguinte comando para instalar o [Visual Studio Code (VS Code)](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) - este é o editor que você usará para escrever o código do seu dispositivo em Python.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install code
|
||||||
|
```
|
||||||
|
|
||||||
|
Depois de instalado, o VS Code estará disponível no menu superior.
|
||||||
|
|
||||||
|
> 💁 Você está livre para usar qualquer IDE de Python ou editor para essas lições se tiver uma ferramenta preferida, mas as lições darão instruções baseadas no uso do VS Code.
|
||||||
|
|
||||||
|
1. Instale o Pylance. Esta é uma extensão para VS Code que fornece suporte à linguagem Python. Consulte a [documentação da extensão Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) para obter instruções sobre como instalar esta extensão no VS Code.
|
||||||
|
|
||||||
|
### Acesso remoto para codificar o Pi
|
||||||
|
|
||||||
|
Em vez de codificar diretamente no Pi, ele pode rodar 'headless', quando não está conectado a um teclado/mouse/monitor, e assim configurar e codificar nele a partir do seu computador, usando o Visual Studio Code.
|
||||||
|
|
||||||
|
#### Configure o Pi OS
|
||||||
|
|
||||||
|
Para codificar remotamente, o Pi OS precisa ser instalado em um cartão SD.
|
||||||
|
|
||||||
|
##### Tarefa - configurar o Pi OS
|
||||||
|
|
||||||
|
Configure o Pi OS headless.
|
||||||
|
|
||||||
|
1. Baixe o **Raspberry Pi Imager** da [página do software Raspberry Pi OS](https://www.raspberrypi.org/software/) e instale-o
|
||||||
|
|
||||||
|
1. Insira um cartão SD em seu computador, usando um adaptador, se necessário
|
||||||
|
|
||||||
|
1. Inicie o Raspberry Pi Imager
|
||||||
|
|
||||||
|
1. No Raspberry Pi Imager, selecione o botão **CHOOSE OS** e, em seguida, selecione *Raspberry Pi OS (Other)*, seguido por *Raspberry Pi OS Lite (32 bits)*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 💁 Raspberry Pi OS Lite é uma versão do Raspberry Pi OS que não possui a UI para desktop ou ferramentas baseadas em UI. Eles não são necessários para um Pi headless e tornam a instalação menor e o tempo de inicialização mais rápido.
|
||||||
|
|
||||||
|
1. Selecione o botão **CHOOSE STORAGE** e, em seguida, selecione seu cartão SD
|
||||||
|
|
||||||
|
1. Inicie as **Advanced Options** pressionando `Ctrl+Shift+X`. Essas opções permitem alguma pré-configuração do sistema operacional do Raspberry Pi antes que sua imagem seja criada no cartão SD.
|
||||||
|
|
||||||
|
1. Marque a caixa de seleção **Enable SSH** e defina uma senha para o usuário `pi`. Esta é a senha que você usará para fazer login no Pi mais tarde.
|
||||||
|
|
||||||
|
1. Se você está planejando se conectar ao Pi por WiFi, marque a caixa de seleção **Configure WiFi** e insira o SSID e a senha do seu WiFi, bem como selecione o país do seu WiFi. Você não precisa fazer isso se for usar um cabo Ethernet. Certifique-se de que a rede à qual você se conecta é a mesma em que seu computador está.
|
||||||
|
|
||||||
|
1. Marque a caixa de seleção **Set locale settings** e defina seu país e fuso horário
|
||||||
|
|
||||||
|
1. Selecione o botão **SAVE**
|
||||||
|
|
||||||
|
1. Selecione o botão **WRITE** para gravar o sistema operacional no cartão SD. Se estiver usando o macOS, será solicitado que você insira sua senha, pois a ferramenta subjacente que grava imagens de disco precisa de acesso privilegiado.
|
||||||
|
|
||||||
|
O sistema operacional será gravado no cartão SD e, uma vez concluído, o cartão será ejetado pelo sistema operacional e você será notificado. Remova o cartão SD do seu computador, insira-o no Pi e ligue o Pi.
|
||||||
|
|
||||||
|
#### Conecte-se ao Pi
|
||||||
|
|
||||||
|
A próxima etapa é acessar remotamente o Pi. Você pode fazer isso usando `ssh`, que está disponível no macOS, Linux e versões recentes do Windows.
|
||||||
|
|
||||||
|
##### Tarefa - conectar ao Pi
|
||||||
|
|
||||||
|
Acesse remotamente o Pi.
|
||||||
|
|
||||||
|
1. Inicie um Terminal ou Prompt de Comando e digite o seguinte comando para se conectar ao Pi:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ssh pi@raspberrypi.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Se você estiver no Windows usando uma versão mais antiga que não possui o `ssh` instalado, você pode usar o OpenSSH. Você pode encontrar as instruções de instalação na [documentação de instalação do OpenSSH](https://docs.microsoft.com//windows-server/administration/openssh/openssh_install_firstuse?WT.mc_id=academic-17441-jabenn).
|
||||||
|
|
||||||
|
1. Isso deve se conectar ao seu Pi e pedir a senha.
|
||||||
|
|
||||||
|
Ser capaz de encontrar computadores em sua rede usando `<hostname>.local` é uma adição bastante recente ao Linux e Windows. Se você estiver usando Linux ou Windows e receber algum erro sobre o nome do host não ser encontrado, será necessário instalar software adicional para habilitar o ZeroConf networking (também conhecido pela Apple como Bonjour):
|
||||||
|
|
||||||
|
1. Se você estiver usando Linux, instale o Avahi usando o seguinte comando:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt-get install avahi-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Se você estiver usando o Windows, a maneira mais fácil de ativar o ZeroConf é instalar [Bonjour Print Services para Windows](http://support.apple.com/kb/DL999). Você também pode instalar o [iTunes para Windows](https://www.apple.com/itunes/download/) para obter uma versão mais recente do utilitário (que não está disponível standalone).
|
||||||
|
|
||||||
|
> 💁 Se você não conseguir se conectar usando `raspberrypi.local`, poderá usar o endereço IP do seu Pi. Consulte a [documentação do endereço IP do Raspberry Pi](https://www.raspberrypi.org/documentation/remote-access/ip-address.md) para obter instruções sobre várias maneiras de obter o endereço IP.
|
||||||
|
|
||||||
|
1. Digite a senha que você definiu nas opções avançadas do Raspberry Pi Imager
|
||||||
|
|
||||||
|
#### Configure o software no Pi
|
||||||
|
|
||||||
|
Assim que estiver conectado ao Pi, você precisa garantir que o sistema operacional esteja atualizado e instalar várias bibliotecas e ferramentas que interagem com o hardware Grove.
|
||||||
|
|
||||||
|
##### Tarefa - configurar software no Pi
|
||||||
|
|
||||||
|
Configure o software Pi instalado e instale as bibliotecas Grove.
|
||||||
|
|
||||||
|
1. Na sessão `ssh`, execute o seguinte comando para atualizar e reinicie o Pi:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt update && sudo apt full-upgrade --yes && sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
O Pi será atualizado e reiniciado. A sessão `ssh` terminará quando o Pi for reiniciado, então deixe-o por cerca de 30 segundos e reconecte.
|
||||||
|
|
||||||
|
1. A partir da sessão `ssh` reconectada, execute o seguinte comando para instalar todas as bibliotecas necessárias para o hardware Grove:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s -
|
||||||
|
```
|
||||||
|
|
||||||
|
Um dos recursos poderosos do Python é a capacidade de instalar [pacotes pip](https://pypi.org) - são pacotes de código escritos por outras pessoas e publicados na Internet. Você pode instalar um pacote pip em seu computador com um comando e, em seguida, usar esse pacote em seu código. Este script de instalação do Grove instalará os pacotes pip que você usará para trabalhar com o hardware Grove a partir do Python.
|
||||||
|
|
||||||
|
1. Reinicialize o Pi executando o seguinte comando:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
A sessão `ssh` terminará quando o Pi for reiniciado. Não há necessidade de reconectar.
|
||||||
|
|
||||||
|
#### Configure o VS Code para acesso remoto
|
||||||
|
|
||||||
|
Depois que o Pi estiver configurado, você pode se conectar a ele usando o Visual Studio Code (VS Code) a partir do seu computador - este é um editor de texto para desenvolvedores gratuito que você usará para escrever o código do seu dispositivo em Python.
|
||||||
|
|
||||||
|
##### Tarefa - configurar o VS Code para acesso remoto
|
||||||
|
|
||||||
|
Instale o software necessário e conecte-se remotamente ao seu Pi.
|
||||||
|
|
||||||
|
1. Instale o VS Code em seu computador seguindo a [documentação do VS Code](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn)
|
||||||
|
|
||||||
|
1. Siga as instruções em [VS Code Remote Development using SSH documentation](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn) para instalar os componentes necessários
|
||||||
|
|
||||||
|
1. Seguindo as mesmas instruções, conecte o VS Code ao Pi
|
||||||
|
|
||||||
|
1. Depois de conectado, siga as instruções em [gerenciando extensões](https://code.visualstudio.com/docs/remote/ssh#_managing-extensions?WT.mc_id=academic-17441-jabenn) para instalar a [extensão Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) remotamente no Pi
|
||||||
|
|
||||||
|
## Hello World
|
||||||
|
|
||||||
|
É tradicional, ao começar com uma nova linguagem de programação ou tecnologia, criar um aplicativo 'Hello World' - um pequeno aplicativo que produz algo como o texto `"Hello World"` para mostrar que todas as ferramentas estão configuradas corretamente.
|
||||||
|
|
||||||
|
O aplicativo Hello World para o Pi garantirá que você tenha o Python e o Visual Studio Code instalados corretamente.
|
||||||
|
|
||||||
|
Este aplicativo estará em uma pasta chamada `nightlight` e será reutilizado com código diferente em partes posteriores desta tarefa para construir o aplicativo nightlight.
|
||||||
|
|
||||||
|
### Tarefa - hello world
|
||||||
|
|
||||||
|
Crie o aplicativo Hello World.
|
||||||
|
|
||||||
|
1. Inicie o VS Code, diretamente no Pi ou em seu computador e conectado ao Pi usando a extensão Remote SSH
|
||||||
|
|
||||||
|
1. Inicie o Terminal do VS Code selecionando *Terminal -> Novo Terminal* ou pressionando `` CTRL+` ``. Ele será aberto no diretório inicial dos usuários `pi`.
|
||||||
|
|
||||||
|
1. Execute os seguintes comandos para criar um diretório para o seu código e crie um arquivo Python chamado `app.py` dentro desse diretório:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir nightlight
|
||||||
|
cd nightlight
|
||||||
|
touch app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Abra esta pasta no VS Code selecionando *File -> Open...* e selecionando a pasta *nightlight* e, em seguida, selecione **OK**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Abra o arquivo `app.py` no VS Code explorer e adicione o seguinte código:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print('Hello World!')
|
||||||
|
```
|
||||||
|
|
||||||
|
A função `print` imprime tudo o que é passado para ela no console.
|
||||||
|
|
||||||
|
1. No Terminal do VS Code, execute o seguinte para executar seu aplicativo Python:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 Você precisa chamar explicitamente `python3` para executar este código apenas no caso de ter o Python 2 instalado além do Python 3 (a versão mais recente). Se você tiver Python2 instalado, chamar `python` usará Python 2 em vez de Python 3
|
||||||
|
|
||||||
|
A seguinte saída aparecerá no terminal:
|
||||||
|
|
||||||
|
```output
|
||||||
|
pi@raspberrypi:~/nightlight $ python3 app.py
|
||||||
|
Hello World!
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 Você pode encontrar este código na pasta [code/pi](code/pi).
|
||||||
|
|
||||||
|
😀 Seu programa 'Hello World' foi um sucesso!
|
@ -0,0 +1,210 @@
|
|||||||
|
# Computador de placa única virtual
|
||||||
|
|
||||||
|
Em vez de comprar um dispositivo IoT, junto com sensores e atuadores, você pode usar seu computador para simular o hardware IoT. O [projeto CounterFit](https://github.com/CounterFit-IoT/CounterFit) permite que você execute um aplicativo localmente que simula hardware IoT, como sensores e atuadores, e acesse os sensores e atuadores a partir do código Python local que está escrito da mesma forma que o código que você escreveria em um Raspberry Pi usando um hardware físico.
|
||||||
|
|
||||||
|
## Configuração
|
||||||
|
|
||||||
|
Para usar o CounterFit, você precisará instalar alguns softwares gratuitos em seu computador.
|
||||||
|
|
||||||
|
### Tarefa
|
||||||
|
|
||||||
|
Instale o software necessário.
|
||||||
|
|
||||||
|
1. Instale o Python. Consulte a [página de downloads do Python](https://www.python.org/downloads/) para obter instruções sobre como instalar a versão mais recente do Python.
|
||||||
|
|
||||||
|
1. Instale o Visual Studio Code (VS Code). Este é o editor que você usará para escrever o código do seu dispositivo virtual em Python. Consulte a [documentação do VS Code](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) para obter instruções sobre como instalar o VS Code.
|
||||||
|
|
||||||
|
> 💁 Você está livre para usar qualquer IDE ou editor de código Python para essas lições se tiver uma ferramenta preferida, mas as lições darão instruções baseadas no uso do VS Code.
|
||||||
|
|
||||||
|
1. Instale a extensão Pylance do VS Code. Esta é uma extensão para VS Code que fornece suporte à linguagem Python. Consulte a [documentação da extensão Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) para obter instruções sobre como instalar esta extensão no VS Code.
|
||||||
|
|
||||||
|
As instruções para instalar e configurar o aplicativo CounterFit serão fornecidas no momento relevante nas instruções de atribuição, uma vez que é instalado por projeto.
|
||||||
|
|
||||||
|
## Hello world
|
||||||
|
|
||||||
|
É tradicional, ao começar com uma nova linguagem de programação ou tecnologia, criar um aplicativo 'Hello World' - um pequeno aplicativo que produz algo como o texto `"Hello World"` para mostrar que todas as ferramentas estão configuradas corretamente.
|
||||||
|
|
||||||
|
O aplicativo Hello World para o hardware IoT virtual garantirá que você tenha o Python e o Visual Studio Code instalados corretamente. Ele também se conectará ao CounterFit para os sensores e atuadores IoT virtuais. Ele não usará nenhum hardware, apenas se conectará para provar que tudo está funcionando.
|
||||||
|
|
||||||
|
Este aplicativo estará em uma pasta chamada `nightlight` e será reutilizado com código diferente em partes posteriores desta atribuição para construir o aplicativo nightlight.
|
||||||
|
|
||||||
|
### Configure um ambiente virtual Python
|
||||||
|
|
||||||
|
Um dos recursos poderosos do Python é a capacidade de instalar [pacotes pip](https://pypi.org) - são pacotes de código escritos por outras pessoas e publicados na Internet. Você pode instalar um pacote pip em seu computador com um comando e, em seguida, usar esse pacote em seu código. Você usará o pip para instalar um pacote para falar com o CounterFit.
|
||||||
|
|
||||||
|
Por padrão, quando você instala um pacote, ele está disponível em qualquer lugar do seu computador, e isso pode causar problemas com as versões do pacote - como um aplicativo dependendo de uma versão de um pacote que quebra quando você instala uma nova versão para um aplicativo diferente. Para contornar esse problema, você pode usar um [ambiente virtual Python](https://docs.python.org/3/library/venv.html), essencialmente uma cópia do Python em uma pasta dedicada, e ao instalar o pip pacotes são instalados apenas nessa pasta.
|
||||||
|
|
||||||
|
#### Tarefa - configurar um ambiente virtual Python
|
||||||
|
|
||||||
|
Configure um ambiente virtual Python e instale os pacotes pip para CounterFit.
|
||||||
|
|
||||||
|
1. Em seu terminal ou linha de comando, execute o seguinte em um local de sua escolha para criar e navegar para um novo diretório:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir nightlight
|
||||||
|
cd nightlight
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Agora execute o seguinte para criar um ambiente virtual na pasta `.venv`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 Você precisa chamar explicitamente `python3` para criar o ambiente virtual apenas no caso de ter o Python 2 instalado além do Python 3 (a versão mais recente). Se você tiver Python2 instalado, chamar `python` usará Python 2 em vez de Python 3
|
||||||
|
|
||||||
|
1. Ative o ambiente virtual:
|
||||||
|
|
||||||
|
* No Windows, execute:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
.venv \ Scripts \ activate.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
* No macOS ou Linux, execute:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
source ./.venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Uma vez que o ambiente virtual foi ativado, o comando padrão `python` irá executar a versão do Python que foi usada para criar o ambiente virtual. Execute o seguinte para obter a versão:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python --version
|
||||||
|
```
|
||||||
|
|
||||||
|
A saída deve conter o seguinte:
|
||||||
|
|
||||||
|
```output
|
||||||
|
(.venv) ➜ nightlight python --version
|
||||||
|
Python 3.9.1
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 Sua versão do Python pode ser diferente - contanto que seja a versão 3.6 ou superior, você está bem. Caso contrário, exclua esta pasta, instale uma versão mais recente do Python e tente novamente.
|
||||||
|
|
||||||
|
1. Execute os seguintes comandos para instalar os pacotes pip para CounterFit. Esses pacotes incluem o aplicativo CounterFit principal, bem como shims para hardware Grove. Esses shims permitem que você escreva código como se estivesse programando usando sensores e atuadores físicos do ecossistema Grove, mas conectado a dispositivos IoT virtuais.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install CounterFit
|
||||||
|
pip install counterfit-connection
|
||||||
|
pip install counterfit-shims-grove
|
||||||
|
```
|
||||||
|
|
||||||
|
Esses pacotes pip só serão instalados no ambiente virtual e não estarão disponíveis fora dele.
|
||||||
|
|
||||||
|
### Escreva o código
|
||||||
|
|
||||||
|
Assim que o ambiente virtual Python estiver pronto, você pode escrever o código para o aplicativo 'Hello World'
|
||||||
|
|
||||||
|
#### Tarefa - escreva o código
|
||||||
|
|
||||||
|
Crie um aplicativo Python para imprimir `" Hello World "` no console.
|
||||||
|
|
||||||
|
1. Em seu terminal ou linha de comando, execute o seguinte dentro do ambiente virtual para criar um arquivo Python chamado `app.py`:
|
||||||
|
|
||||||
|
* No Windows, execute:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
type nul > app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
* No macOS ou Linux, execute:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
touch app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Abra a pasta atual no VS Code:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
code .
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 Se o seu terminal retornar `command not found` no macOS, significa que o VS Code não foi adicionado ao seu PATH. Você pode adicionar o VS Code ao seu PATH seguindo as instruções na seção [Iniciando a partir da linha de comando da documentação do código do VS](https://code.visualstudio.com/docs/setup/mac?WT.mc_id=academic-17441-jabenn#_launching-from-the-command-line) e execute o comando depois. O VS Code é instalado em seu PATH por padrão no Windows e Linux.
|
||||||
|
|
||||||
|
1. Quando o VS Code for iniciado, ele ativará o ambiente virtual Python. O ambiente virtual selecionado aparecerá na barra de status inferior:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Se o Terminal do VS Code já estiver em execução quando o VS Code for inicializado, ele não terá o ambiente virtual ativado nele. A coisa mais fácil a fazer é matar o terminal usando o botão **Kill the active terminal instance**:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Você pode dizer se o terminal tem o ambiente virtual ativado, pois o nome do ambiente virtual será um prefixo no prompt do terminal. Por exemplo, pode ser:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
(.venv) ➜ nightlight
|
||||||
|
```
|
||||||
|
|
||||||
|
Se você não tiver `.venv` como prefixo no prompt, o ambiente virtual não está ativo no terminal.
|
||||||
|
|
||||||
|
1. Inicie um novo Terminal do VS Code selecionando *Terminal -> Novo Terminal* ou pressionando `` CTRL+` ``. O novo terminal irá carregar o ambiente virtual, e a chamada para ativá-lo aparecerá no terminal. O prompt também terá o nome do ambiente virtual (`.venv`):
|
||||||
|
|
||||||
|
```output
|
||||||
|
➜ nightlight source .venv/bin/activate
|
||||||
|
(.venv) ➜ nightlight
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Abra o arquivo `app.py` no VS Code explorer e adicione o seguinte código:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print('Hello World!')
|
||||||
|
```
|
||||||
|
|
||||||
|
A função `print` imprime no console tudo o que é passado para ela.
|
||||||
|
|
||||||
|
1. No terminal do VS Code, execute o seguinte para executar seu aplicativo Python:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
O seguinte estará na saída:
|
||||||
|
|
||||||
|
```output
|
||||||
|
(.venv) ➜ nightlight python app.py
|
||||||
|
Hello World!
|
||||||
|
```
|
||||||
|
|
||||||
|
😀 Seu programa 'Hello World' foi um sucesso!
|
||||||
|
|
||||||
|
### Conecte o 'hardware'
|
||||||
|
|
||||||
|
Como uma segunda etapa 'Hello World', você executará o aplicativo CounterFit e conectará seu código a ele. Isso é o equivalente virtual de conectar algum hardware IoT a um kit de desenvolvimento.
|
||||||
|
|
||||||
|
#### Tarefa - conecte o 'hardware'
|
||||||
|
|
||||||
|
1. A partir do terminal do VS Code, inicie o aplicativo CounterFit com o seguinte comando:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
CounterFit
|
||||||
|
```
|
||||||
|
|
||||||
|
O aplicativo começará a funcionar e abrir no seu navegador da web:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Ele será marcado como *Desconectado*, com o LED no canto superior direito apagado.
|
||||||
|
|
||||||
|
1. Adicione o seguinte código ao topo de `app.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from counterfit_connection import CounterFitConnection
|
||||||
|
CounterFitConnection.init('127.0.0.1', 5000)
|
||||||
|
```
|
||||||
|
|
||||||
|
Este código importa a classe `CounterFitConnection` do módulo` counterfit_connection`, que vem do pacote pip `counterfit-connection` que você instalou anteriormente. Em seguida, ele inicializa uma conexão com o aplicativo CounterFit em execução em `127.0.0.1`, que é um endereço IP que você sempre pode usar para acessar seu computador local (muitas vezes referido como *localhost*), na porta 5000.
|
||||||
|
|
||||||
|
> 💁 Se você tiver outros aplicativos em execução na porta 5000, pode alterar isso atualizando a porta no código e executando o CounterFit usando `CounterFit --port <port_number>`, substituindo `<port_number>` pela porta que deseja usar.
|
||||||
|
|
||||||
|
1. Você precisará iniciar um novo terminal do VS Code selecionando o botão **Create a new integrated terminal**. Isso ocorre porque o aplicativo CounterFit está sendo executado no terminal atual.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Neste novo terminal, execute o arquivo `app.py` como antes. O status do CounterFit mudará para **Conectado** e o LED acenderá.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 💁 Você pode encontrar este código na pasta [code/virtual-device](../code/virtual-device).
|
||||||
|
|
||||||
|
😀 Sua conexão com o hardware foi um sucesso!
|
@ -0,0 +1,198 @@
|
|||||||
|
# Wio Terminal
|
||||||
|
|
||||||
|
O [Wio Terminal da Seeed Studios] (https://www.seeedstudio.com/Wio-Terminal-p-4509.html) é um microcontrolador compatível com Arduino, com WiFi e alguns sensores e atuadores integrados, bem como portas para adicionar mais sensores e atuadores, usando um ecossistema de hardware chamado [Grove] (https://www.seeedstudio.com/category/Grove-c-1003.html).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Configuração
|
||||||
|
|
||||||
|
Para usar o Wio Terminal, você precisará instalar algum software gratuito no computador. Você também precisará atualizar o firmware do Wio Terminal antes de conectá-lo ao WiFi.
|
||||||
|
|
||||||
|
### Tarefa - configuração
|
||||||
|
|
||||||
|
Instale o software necessário e atualize o firmware.
|
||||||
|
|
||||||
|
1. Instale o Visual Studio Code (VS Code). Este é o editor que você usará para escrever o código do seu dispositivo em C/C++. Consulte a [documentação do VS Code](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) para obter instruções sobre como instalar o VS Code.
|
||||||
|
|
||||||
|
> 💁 Outro IDE popular para o desenvolvimento do Arduino é o [Arduino IDE](https://www.arduino.cc/en/software). Se você já está familiarizado com esta ferramenta, você pode usá-la em vez do VS Code e PlatformIO, mas as lições darão instruções baseadas no uso do VS Code.
|
||||||
|
|
||||||
|
1. Instale a extensão PlatformIO do VS Code. Esta é uma extensão do VS Code que oferece suporte à programação de microcontroladores em C/C++. Consulte a [documentação da extensão PlatformIO](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide&WT.mc_id=academic-17441-jabenn) para obter instruções sobre como instalar esta extensão no VS Code. Esta extensão depende da extensão Microsoft C/C++ para funcionar com código C e C ++, e a extensão C/C++ é instalada automaticamente quando você instala a extensão PlatformIO.
|
||||||
|
|
||||||
|
1. Conecte o Wio Terminal ao computador. O Wio Terminal possui uma porta USB-C na parte inferior e ela precisa ser conectada a uma porta USB no seu computador. O Wio Terminal vem com um cabo USB-C para USB-A, mas se o seu computador tiver apenas portas USB-C, você precisará de um cabo USB-C ou de um adaptador USB-A para USB-C.
|
||||||
|
|
||||||
|
1. Siga as instruções na [documentação de visão geral de WiFi da Wiki do Wio Terminal](https://wiki.seeedstudio.com/Wio-Terminal-Network-Overview/) para configurar seu Wio Terminal e atualizar o firmware.
|
||||||
|
|
||||||
|
## Hello World
|
||||||
|
|
||||||
|
É tradicional, ao começar com uma nova linguagem de programação ou tecnologia, criar um aplicativo 'Hello World' - um pequeno aplicativo que produz algo como o texto `"Hello World"` para mostrar que todas as ferramentas estão configuradas corretamente.
|
||||||
|
|
||||||
|
O aplicativo Hello World para o Wio Terminal garantirá que você tenha o Visual Studio Code instalado corretamente com PlatformIO e configurado para desenvolvimento de microcontrolador.
|
||||||
|
|
||||||
|
### Crie um projeto PlatformIO
|
||||||
|
|
||||||
|
A primeira etapa é criar um novo projeto usando PlatformIO configurado para o Wio Terminal.
|
||||||
|
|
||||||
|
#### Tarefa - criar um projeto PlatformIO
|
||||||
|
|
||||||
|
Crie o projeto PlatformIO.
|
||||||
|
|
||||||
|
1. Conecte o Wio Terminal ao seu computador
|
||||||
|
|
||||||
|
1. Inicie o VS Code
|
||||||
|
|
||||||
|
1. O ícone PlatformIO estará na barra de menu lateral:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Selecione este item de menu e, em seguida, selecione *PIO Home -> Open*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Na tela de boas-vindas, selecione o botão **+ New Project**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Configure o projeto no *Project Wizard*:
|
||||||
|
|
||||||
|
1. Nomeie seu projeto como `nightlight`
|
||||||
|
|
||||||
|
1. No dropdown de *Board*, digite `WIO` para filtrar as placas e selecione *Seeeduino Wio Terminal*
|
||||||
|
|
||||||
|
1. Deixe o *Framework* como *Arduino*
|
||||||
|
|
||||||
|
1. Deixe a caixa de seleção *Use default location* marcada, ou desmarque-a e selecione um local para o seu projeto
|
||||||
|
|
||||||
|
1. Selecione o botão **Finish**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlatformIO baixará os componentes necessários para compilar o código para o Wio Terminal e criar seu projeto. Isso pode levar alguns minutos.
|
||||||
|
|
||||||
|
### Investigue o projeto PlatformIO
|
||||||
|
|
||||||
|
O explorador do VS Code mostrará vários arquivos e pastas criados pelo assistente PlatformIO.
|
||||||
|
|
||||||
|
#### Pastas
|
||||||
|
|
||||||
|
* `.pio` - esta pasta contém dados temporários necessários para PlatformIO, como bibliotecas ou código compilado. Ela é recriada automaticamente se excluída e você não precisa adicioná-la ao controle do código-fonte se estiver compartilhando seu projeto em sites como o GitHub.
|
||||||
|
* `.vscode` - esta pasta contém a configuração usada por PlatformIO e VS Code. Ela é recriada automaticamente se excluída e você não precisa adicioná-la ao controle do código-fonte se estiver compartilhando seu projeto em sites como o GitHub.
|
||||||
|
* `include` - esta pasta é para arquivos de cabeçalho externos necessários ao adicionar bibliotecas adicionais ao seu código. Você não usará esta pasta em nenhuma dessas lições.
|
||||||
|
* `lib` - esta pasta é para bibliotecas externas que você deseja chamar de seu código. Você não usará esta pasta em nenhuma dessas lições.
|
||||||
|
* `src` - esta pasta contém o código-fonte principal do seu aplicativo. Inicialmente, ele conterá um único arquivo - `main.cpp`.
|
||||||
|
* `test` - esta pasta é onde você colocaria quaisquer testes de unidade para o seu código
|
||||||
|
|
||||||
|
#### Arquivos
|
||||||
|
|
||||||
|
* `main.cpp` - este arquivo na pasta `src` contém o ponto de entrada para sua aplicação. Abra este arquivo e ele conterá o seguinte código:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// coloque seu código de configuração aqui, para ser executado uma vez:
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// coloque seu código principal aqui, para executar repetidamente:
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Quando o dispositivo é inicializado, a estrutura do Arduino executará a função `setup` uma vez e, em seguida, executará a função `loop` repetidamente até que o dispositivo seja desligado.
|
||||||
|
|
||||||
|
* `.gitignore` - este arquivo lista os arquivos e diretórios a serem ignorados ao adicionar seu código ao controle de código-fonte do git, como enviar para um repositório no GitHub.
|
||||||
|
|
||||||
|
* `platformio.ini` - este arquivo contém a configuração para seu dispositivo e aplicativo. Abra este arquivo e ele conterá o seguinte código:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[env:seeed_wio_terminal]
|
||||||
|
platform = atmelsam
|
||||||
|
board = seeed_wio_terminal
|
||||||
|
framework = arduino
|
||||||
|
```
|
||||||
|
|
||||||
|
A seção `[env:seeed_wio_terminal]` tem configuração para o Wio Terminal. Você pode ter várias seções `env` para que seu código possa ser compilado para várias placas.
|
||||||
|
|
||||||
|
Os outros valores correspondem à configuração do assistente de projeto:
|
||||||
|
|
||||||
|
* `platform = atmelsam` define o hardware que o Wio Terminal usa (um microcontrolador baseado em ATSAMD51)
|
||||||
|
* `board = seeed_wio_terminal` define o tipo de placa do microcontrolador (o Wio Terminal)
|
||||||
|
* `framework = arduino` define que este projeto está usando o framework Arduino.
|
||||||
|
|
||||||
|
### Escreva o aplicativo Hello World
|
||||||
|
|
||||||
|
Agora você está pronto para escrever o aplicativo Hello World.
|
||||||
|
|
||||||
|
#### Tarefa - escrever o aplicativo Hello World
|
||||||
|
|
||||||
|
Escreva o aplicativo Hello World.
|
||||||
|
|
||||||
|
1. Abra o arquivo `main.cpp` no VS Code
|
||||||
|
|
||||||
|
1. Altere o código para corresponder ao seguinte:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
while (!Serial)
|
||||||
|
; // Aguarde até que o Serial esteja pronto
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
Serial.println("Hello World");
|
||||||
|
delay(5000);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A função `setup` inicializa uma conexão com a porta serial - neste caso, a porta USB que é usada para conectar o Wio Terminal ao seu computador. O parâmetro `9600` é a [taxa de transmissão](https://wikipedia.org/wiki/Symbol_rate) (também conhecida como taxa de símbolo), ou velocidade com que os dados serão enviados pela porta serial em bits por segundo. Essa configuração significa que 9.600 bits (0s e 1s) de dados são enviados a cada segundo. Em seguida, ele espera que a porta serial esteja pronta.
|
||||||
|
|
||||||
|
A função `loop` envia a linha `Hello World!` para a porta serial, então os caracteres de `Hello World!` junto com um caractere de nova linha. Em seguida, ele dorme por 5.000 milissegundos ou 5 segundos. Depois que o `loop` termina, ele é executado novamente, e novamente, e assim por diante, o tempo todo em que o microcontrolador permanece ligado.
|
||||||
|
|
||||||
|
1. Construa e carregue o código para o Wio Terminal
|
||||||
|
|
||||||
|
1. Abra a paleta de comando do VS Code
|
||||||
|
|
||||||
|
1. Digite `PlatformIO Upload` para pesquisar a opção de upload e selecione *PlatformIO: Upload*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlatformIO construirá automaticamente o código, se necessário, antes de fazer o upload.
|
||||||
|
|
||||||
|
1. O código será compilado e enviado para o Wio Terminal
|
||||||
|
|
||||||
|
> 💁 Se você estiver usando o macOS, será exibida uma notificação sobre um *DISCO NÃO EJETADO CORRETAMENTE*. Isso ocorre porque o Wio Terminal é montado como uma unidade como parte do processo de flashing e é desconectado quando o código compilado é gravado no dispositivo. Você pode ignorar esta notificação.
|
||||||
|
|
||||||
|
⚠️ Se você receber erros sobre a porta de upload não estar disponível, primeiro certifique-se de ter o Wio Terminal conectado ao seu computador e ligado usando o botão no lado esquerdo da tela. A luz verde na parte inferior deve estar acesa. Se você ainda receber o erro, puxe o botão liga/desliga para baixo duas vezes em rápida sucessão para forçar o Wio Terminal no modo bootloader e tente fazer o upload novamente.
|
||||||
|
|
||||||
|
PlatformIO tem um monitor serial que pode monitorar os dados enviados pelo cabo USB do Wio Terminal. Isso permite que você monitore os dados enviados pelo comando `Serial.println("Hello World");`.
|
||||||
|
|
||||||
|
1. Abra a paleta de comando do VS Code
|
||||||
|
|
||||||
|
1. Digite `PlatformIO Serial` para pesquisar a opção Serial Monitor e selecione *PlatformIO: Serial Monitor*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Um novo terminal será aberto e os dados enviados pela porta serial serão transmitidos para este terminal:
|
||||||
|
|
||||||
|
```output
|
||||||
|
> Executing task: platformio device monitor <
|
||||||
|
|
||||||
|
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
|
||||||
|
--- More details at http://bit.ly/pio-monitor-filters
|
||||||
|
--- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 ---
|
||||||
|
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
||||||
|
Hello World
|
||||||
|
Hello World
|
||||||
|
```
|
||||||
|
|
||||||
|
`Hello World` será impresso no monitor serial a cada 5 segundos.
|
||||||
|
|
||||||
|
> 💁 Você pode encontrar este código na pasta [code/wio-terminal](../code/wio-terminal).
|
||||||
|
|
||||||
|
😀 Seu programa 'Hello World' foi um sucesso!
|
@ -0,0 +1,12 @@
|
|||||||
|
# 比较与对比微控制器和单板计算机
|
||||||
|
|
||||||
|
## 指示
|
||||||
|
|
||||||
|
这个课程涵盖了微控制器和单板计算机。创建一个表格来比较与对比它们,并记下至少两个微控制器和单板计算机相比之下选择微控制器的原因,还有至少两个微控制器和单板计算机相比之下选择单板计算机的原因。
|
||||||
|
|
||||||
|
## 评分表
|
||||||
|
|
||||||
|
| 标准 | 优秀 | 一般 | 需改进 |
|
||||||
|
| -------- | --------- | -------- | ----------------- |
|
||||||
|
| 创建一个表格来比较微控制器和单板计算机 | 创建一个有多项的列表来正确地比较和对比它们 | 创建一个只有几项的列表 | 只能想出一项,或一项也没有来比较和对比它们 |
|
||||||
|
| 两个相比之下选择某一个的原因 | 能够为微控制器提出至少两个原因,而且为单板计算机提出至少两个原因 | 只能为微控制器提出一两个原因,而且为单板计算机提出一两个原因 | 无法为微控制器或单板计算机提出至少一个原因 |
|
@ -0,0 +1,14 @@
|
|||||||
|
# MQTT-এর সাথে অন্যান্য কমিউনিকেশন প্রটোকলের তুলনা করে পার্থক্য দাঁড় করানো
|
||||||
|
|
||||||
|
## নির্দেশনা
|
||||||
|
|
||||||
|
এই পাঠটিতে MQTT কমিউনিকেশন প্রোটোকল নিয়ে আলোচনা হয়েছে । অন্যান্য প্রোটোকলের মধ্যে AMQP এবং HTTP/HTTPS অন্তর্ভুক্ত রয়েছে।
|
||||||
|
|
||||||
|
AMQP এবং HTTP/HTTPS উভয়টি নিয়ে গবেষণা করতে হবে এবং MQTT এর সাথে তুলনা করে পার্থক্য দাঁড় করাতে হবে । যদি কানেকশনসমূহ চলে যায় তবে পাওয়ারের ব্যবহার, সিকউরিটি এবং মেসেজ পারসিসটেন্স নিয়ে চিন্তা করি ।
|
||||||
|
|
||||||
|
## এসাইনমেন্ট মূল্যায়ন মানদন্ড
|
||||||
|
|
||||||
|
| ক্রাইটেরিয়া | দৃষ্টান্তমূলক ব্যখ্যা (সর্বোত্তম) | পর্যাপ্ত ব্যখ্যা (মাঝারি) | আরো উন্নতির প্রয়োজন (নিম্ন) |
|
||||||
|
| -------- | --------- | -------- | ----------------- |
|
||||||
|
| AMQP এর সাথে MQTT তুলনা করা। | AMQP এর সাথে MQTT তুলনা করে পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। | AMQP এর সাথে MQTT তুলনা করে আংশিক পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে দুটি প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। | AMQP এর সাথে MQTT তুলনা করে আংশিক পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে একটি প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। |
|
||||||
|
| HTTP/HTTPS এর সাথে MQTT তুলনা করা। | HTTP/HTTPS এর সাথে MQTT তুলনা করে পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। | HTTP/HTTPS এর সাথে MQTT তুলনা করে আংশিক পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে দুটি প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। | HTTP/HTTPS এর সাথে MQTT তুলনা করে আংশিক পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে একটি প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। |
|
@ -1,9 +0,0 @@
|
|||||||
# Dummy File
|
|
||||||
|
|
||||||
This file acts as a placeholder for the `translations` folder. <br>
|
|
||||||
**Please remove this file after adding the first translation**
|
|
||||||
|
|
||||||
For the instructions, follow the directives in the [translations guide](https://github.com/microsoft/IoT-For-Beginners/blob/main/TRANSLATIONS.md) .
|
|
||||||
|
|
||||||
## THANK YOU
|
|
||||||
We truly appreciate your efforts!
|
|
@ -0,0 +1,56 @@
|
|||||||
|
# ম্যানুয়াল রিলে কন্ট্রোল সংযোজন
|
||||||
|
|
||||||
|
## নির্দেশাবলি
|
||||||
|
|
||||||
|
এইচটিটিপি অনুরোধ সহ অনেকগুলি ভিন্ন উপায়ে সার্ভারলেস কোডকে ট্রিগার করা যায়। রিলে নিয়ন্ত্রণে ম্যানুয়াল ওভাররাইড যুক্ত করতে আমরা HTTP ট্রিগার ব্যবহার করতে পারি, কাউকে ওয়েব রিকুয়েস্ট দ্বারা রিলে চালু বা বন্ধ করার সুযোগ দিতে।
|
||||||
|
|
||||||
|
এই এসাইনমেন্টের জন্য, রিলে চালু এবং বন্ধ করতে ফাংশন অ্যাপটিতে দুটি এইচটিটিপি ট্রিগার যুক্ত করতে হবে। ডিভাইসে কমান্ড প্রেরণের জন্য আমরা এই পাঠটি থেকে যা শিখেছি তা ব্যবহার করেই এটি করতে পারবো।
|
||||||
|
|
||||||
|
কিছু হিন্টস:
|
||||||
|
|
||||||
|
* নিম্নলিখিত কমান্ডটি সহ আমাদের বিদ্যমান ফাংশন অ্যাপগুলিতে একটি HTTP ট্রিগার যুক্ত করতে পারি:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
func new --name <trigger name> --template "HTTP trigger"
|
||||||
|
```
|
||||||
|
|
||||||
|
এখানে `<trigger name>` এর জায়গায় আমাদের ব্যবহৃত এইচটিটিপি ট্রিগারের নাম দিতে হবে। এখানে `relay_on` এবং `relay_off` এর মতো নাম দেয়া যায়।
|
||||||
|
|
||||||
|
* HTTP trigger এ একসেস কন্ট্রোল দেয়া যায়। এগুলো রান করার জন্য function-specific API key দরকার যা URL এর সাথে পাস করতে হবে। তবে এই এসাইনমেন্টের জন্য এই রেস্ট্রিকশন রিমুভ করে দেয়া যায় যাতে যে কেউই এই ফাংশন রান করতে পারে। এজন্য `authLevel` সেটিংসটি আপডেট করতে হবে `function.json` ফাইল থেকে :
|
||||||
|
|
||||||
|
```json
|
||||||
|
"authLevel": "anonymous"
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 একসেস কন্ট্রোল নিয়ে আরো বিস্তারিত [Function access keys documentation](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?WT.mc_id=academic-17441-jabenn#authorization-keys) থেকে জানা যাবে।
|
||||||
|
|
||||||
|
* HTTP ট্রিগারগুলো বাই ডিফল্ট GET এবং POST রিকুয়েস্টগুলো সাপোর্ট করে। অর্থাৎ ওয়েব ব্রাউজার দ্বারাই কাজ করা যাবে - GET রিকুয়েস্ট দিয়ে।
|
||||||
|
|
||||||
|
লোকালি ফাংশন এপ রান করলে, ট্রিগার ইউআরএল পাওয়া যাবে:
|
||||||
|
|
||||||
|
```output
|
||||||
|
Functions:
|
||||||
|
|
||||||
|
relay_off: [GET,POST] http://localhost:7071/api/relay_off
|
||||||
|
|
||||||
|
relay_on: [GET,POST] http://localhost:7071/api/relay_on
|
||||||
|
|
||||||
|
iot-hub-trigger: eventHubTrigger
|
||||||
|
```
|
||||||
|
|
||||||
|
ইউআরএল টি ব্রাউজারে পেস্ট করে `return` এ প্রেস করতে হবে অথবা VS Code টার্মিনালে লিংকটি `Ctrl+click` (`Cmd+click` macOS এর জন্য) সিলেক্ট করলে ডিফল্ট ব্রাউজারে তা ওপেন হবে এবং ট্রিগার রান করবে।
|
||||||
|
|
||||||
|
> 💁 খেয়াল করা দরকার যে URL-টি তে `/api` রয়েছে - HTTP ট্রিগারগুলো বাই ডিফল্ট `api` সাবডোমেইনে থাকে।
|
||||||
|
|
||||||
|
* ফাংশন অ্যাপ ডেপ্লয় করলে, তখন এইচটিটিপি ট্রিগার URL হবে:
|
||||||
|
|
||||||
|
`https://<functions app name>.azurewebsites.net/api/<trigger name>`
|
||||||
|
|
||||||
|
যেখানে `<functions app name>` হলো আমাদের ফাংশন এপ এর নাম এবং `<trigger name>` হলো ট্রিগারের নাম।
|
||||||
|
|
||||||
|
## এসাইনমেন্ট মূল্যায়ন মানদন্ড
|
||||||
|
|
||||||
|
| ক্রাইটেরিয়া | দৃষ্টান্তমূলক (সর্বোত্তম) | পর্যাপ্ত (মাঝারি) | উন্নতি প্রয়োজন (নিম্নমান) |
|
||||||
|
| --------- | ------------------ | ------------- | --------------------- |
|
||||||
|
| HTTP ট্রিগার তৈরী | সঠিক নামকরণের মাধ্যমে ২টি ট্রিগার তৈরি করে রিলে অন/অফ করা হয়েছে | সঠিক নামকরণের মাধ্যমে ১টি ট্রিগার তৈরি করেছে | কোন ট্রিগার তৈরী করতে সমর্থ হয়নি |
|
||||||
|
| এইচটিটিপি ট্রিগারগুলি থেকে রিলে নিয়ন্ত্রণ করা | উভয় ট্রিগারকে আইওটি হাবের সাথে সংযুক্ত করতে এবং যথোপযুক্তভাবে রিলে নিয়ন্ত্রণ করতে সক্ষম হয়েছিল| কেবল ১টি ট্রিগারকে আইওটি হাবের সাথে সংযুক্ত করতে এবং যথোপযুক্তভাবে রিলে নিয়ন্ত্রণ করতে সক্ষম হয়েছিল | ট্রিগারকে আইওটি হাবের সাথে সংযুক্ত করতে সমর্থ হয়নি |
|
@ -1,9 +0,0 @@
|
|||||||
# Dummy File
|
|
||||||
|
|
||||||
This file acts as a placeholder for the `translations` folder. <br>
|
|
||||||
**Please remove this file after adding the first translation**
|
|
||||||
|
|
||||||
For the instructions, follow the directives in the [translations guide](https://github.com/microsoft/IoT-For-Beginners/blob/main/TRANSLATIONS.md) .
|
|
||||||
|
|
||||||
## THANK YOU
|
|
||||||
We truly appreciate your efforts!
|
|
@ -0,0 +1,15 @@
|
|||||||
|
# নতুন আইওটি ডিভাইস তৈরি
|
||||||
|
|
||||||
|
## নির্দেশাবলী
|
||||||
|
|
||||||
|
আমরা ডিজিটাল কৃষিকাজ এবং উদ্ভিদ বৃদ্ধির পূর্বাভাস দেওয়ার ডেটা সংগ্রহ করার জন্য আইওটি ডিভাইসগুলি কীভাবে ব্যবহার করব এবং মাটির আর্দ্রতা পাঠের উপর ভিত্তি করে সেচকার্য স্বয়ংক্রিয়ভাবে কীভাবে করব সে সম্পর্কে গত 6 টি পাঠে শিখেছি।
|
||||||
|
|
||||||
|
আমাদের পছন্দমতো সেন্সর এবং অ্যাকচুয়েটর ব্যবহার করে নতুন আইওটি ডিভাইস তৈরি করতে হবে। একটি আইওটি হাবে টেলিমেট্রি প্রেরণ করি এবং সার্ভারলেস কোডের মাধ্যমে কোন অ্যাকচুয়েটরকে নিয়ন্ত্রণ করতে এটি ব্যবহার করি। আমরা তো ইতিমধ্যেই বেশ কিছু সেন্সর এবং অ্যাকচুয়েটর ব্যবহার শিখেছি - সেগুলো বা একদম নতুন কিছু নিয়ে কাজ করতে পারি।
|
||||||
|
|
||||||
|
## এসাইনমেন্ট মূল্যায়ন মানদন্ড
|
||||||
|
|
||||||
|
| ক্রাইটেরিয়া | দৃষ্টান্তমূলক (সর্বোত্তম) | পর্যাপ্ত (মাঝারি) | উন্নতি প্রয়োজন (নিম্নমান) |
|
||||||
|
| --------- | ------------------ | -------------- | -------------------- |
|
||||||
|
| সেন্সর এবং অ্যাকচুয়েটর ব্যবহার করতে একটি আইওটি ডিভাইস কোড করা | সেন্সর এবং অ্যাকচুয়েটর এর সাথে কার্যকর আইওটি ডিভাইস তৈরী করেছে |হয় সেন্সর বা অ্যাকচুয়েটর এর সাথে কার্যকর আইওটি ডিভাইস তৈরী করেছে| সেন্সর এবং অ্যাকচুয়েটর এর সাথে কার্যকর আইওটি ডিভাইস তৈরী করতে ব্যার্থ |
|
||||||
|
| আইওটি ডিভাইসের সাথে আইওটি হাবের কানেকশন | একটি আইওটি হাব ডেপ্লয় করতে এবং এতে টেলিমেট্রি পাঠাতে সক্ষম হয়েছিল এবং এর থেকে নির্দেশ গ্রহণ করতে পেরেছিল | একটি আইওটি হাব ডেপ্লয় করতে এবং হয় এতে টেলিমেট্রি পাঠাতে সক্ষম হয়েছিল অথবা এর থেকে নির্দেশ গ্রহণ করতে পেরেছিল | একটি আইওটি হাব ডেপ্লয় করতে এবং সংযোগ তৈরী করতে ব্যার্থ |
|
||||||
|
| সার্ভারলেস কোড দ্বারা অ্যাকচুয়েটর নিয়ন্ত্রণ | টেলিমেট্রি ইভেন্টগুলির দ্বারা ট্রিগার হওয়া ডিভাইস নিয়ন্ত্রণ করতে একটি অ্যাজুর ফাংশন ডেপ্লয় করতে সক্ষম | টেলিমেট্রি ইভেন্টগুলি দ্বারা ট্রিগার করা একটি অ্যাজুর ফাংশন তৈরী করতে সক্ষম হয়েছিল কিন্তু অ্যাকচুয়েটর ব্যবহার করতে পারেনি | অ্যাজুর ফাংশন ডেপ্লয় করতে ব্যার্থ |
|
@ -0,0 +1,20 @@
|
|||||||
|
# आई.ओ.टी के साथ खेती
|
||||||
|
|
||||||
|
जैसे-जैसे जनसंख्या बढ़ती है, वैसे-वैसे कृषि की मांग भी बढ़ती है। मगर उपलब्ध भूमि की मात्रा नहीं बदलती है, लेकिन जलवायु बदलती है जो याहा किसानों को और भी अधिक चुनौतियाँ देती है, विशेषकर 2 बिलियन [निर्वाह किसान](https://wikipedia.org/wiki/Subsistence_agriculture) जो इस बात पर भरोसा करते हैं कि वे क्या उगाते हैं अपने परिवार का भरण-पोषण करने के लिए। आई.ओ.टी किसानों को बेहतर निर्णय लेने में मदद कर सकता है कि क्या उगाया जाए और कब कटाई की जाए, पैदावार बढ़ाई जाए, शारीरिक श्रम की मात्रा को कम किया जाए और कीटों का पता लगाया जाए और उनसे निपटा जा सके।
|
||||||
|
|
||||||
|
इन 6 पाठों में आप सीखेंगे कि खेती को बेहतर बनाने और स्वचालित करने के लिए इंटरनेट ऑफ थिंग्स को कैसे लागू किया जाए।
|
||||||
|
|
||||||
|
> 💁 ये पाठ कुछ क्लाउड संसाधनों का उपयोग करेंगे। यदि आप इस परियोजना के सभी पाठों को पूरा नहीं करते हैं, तो सुनिश्चित करें कि आप [अपनी परियोजना को साफ करें](पाठ/6-कीप-योर-प्लांट-सिक्योर/रीडएमई.एमडी#क्लीन-अप-योर-प्रोजेक्ट) चरण का पालन करें। [पाठ ६] में (पाठ/6-कीप-योर-प्लांट-सिक्योर/रीडएमई.एमडी)।
|
||||||
|
|
||||||
|
## विषय
|
||||||
|
|
||||||
|
1. [आईओटी के साथ पौधे की वृद्धि की भविष्यवाणी करें](lessons/1-predict-plant-growth/README.md)
|
||||||
|
1. [मिट्टी की नमी का पता लगाएं] (lessons/2-detect-soil-moisture/README.md)
|
||||||
|
1. [स्वचालित पौधों को पानी देना] (lessons/3-automated-plant-watering/README.md)
|
||||||
|
1. [अपने पौधे को क्लाउड में माइग्रेट करें](lessons/4-migrate-your-plant-to-the-cloud/README.md)
|
||||||
|
1. [अपने एप्लिकेशन लॉजिक को क्लाउड पर माइग्रेट करें](lessons/5-migrate-application-to-the-cloud/README.md)
|
||||||
|
1. [अपने संयंत्र को सुरक्षित रखें](lessons/6-keep-your-plant-secure/README.md)
|
||||||
|
|
||||||
|
## क्रेडिट
|
||||||
|
|
||||||
|
सभी पाठ [जिम बेनेट](https://GitHub.com/JimBobBennett) द्वारा ️♥️ साथ लिखे गए थे
|
@ -0,0 +1,24 @@
|
|||||||
|
# खेत से कारखाने तक परिवहन - खाद्य वितरण को ट्रैक करने के लिए आईओटी का उपयोग करना।
|
||||||
|
|
||||||
|
कई किसान बेचने के लिए भोजन उगाते हैं - या तो वे व्यवसायिक उत्पादक हैं जो अपने द्वारा उगाई गई हर चीज को बेचते हैं, या वे निर्वाह किसान हैं जो अपनी अतिरिक्त उपज को जरूरत की चीजें खरीदने के लिए बेचते हैं। किसी तरह भोजन को खेत से उपभोक्ता तक पहुँचाना पड़ता है, और यह आमतौर पर खेतों से थोक परिवहन द्वारा, हब या प्रसंस्करण संयंत्रों तक, फिर दुकानों पर भेजा जाता है। उदाहरण के लिए, एक टमाटर किसान टमाटर की कटाई करेगा, उन्हें बक्सों में पैक करेगा, बक्सों को ट्रक में लोड करेगा और फिर प्रसंस्करण(प्रोसेसिंग) यंत्र को देगा। फिर टमाटरों को छांटा जाएगा, और वहां से उपभोक्ताओं को विभिन्न रूप में वितरित किया जाएगा।
|
||||||
|
|
||||||
|
आईओटी भोजन को पारगमन में ट्रैक करके इस आपूर्ति श्रृंखला में मदद कर सकता है - यह सुनिश्चित करना कि ड्राइवर कहाँ जा रहे हैं, वाहन स्थानों की निगरानी कर रहे हैं, और वाहनों के आने पर अलर्ट प्राप्त कर रहे हैं ताकि भोजन को अनलोड किया जा सके, प्रसंस्करण(प्रोसेसिंग) के लिए जल्द से जल्द तैयार किया जा सके।
|
||||||
|
|
||||||
|
> 🎓 एक_आपूर्ति श्रृंखला_ कुछ बनाने और वितरित करने के लिए गतिविधियों का क्रम है। उदाहरण के लिए, टमाटर की खेती में यह बीज, मिट्टी, उर्वरक और पानी की आपूर्ति, टमाटर उगाना, टमाटर को एक केंद्रीय केंद्र तक पहुंचाना, उन्हें एक सुपरमार्केट स्थानीय केंद्र में ले जाना, व्यक्तिगत सुपरमार्केट में ले जाना, प्रदर्शन पर रखा जाना, फिर ऊपभोगता उसे बेचेगा और खाने के लिए घर लेके जाएगा। प्रत्येक चरण एक श्रृंखला में कड़ियों की तरह है।
|
||||||
|
|
||||||
|
> 🎓आपूर्ति श्रृंखला के परिवहन भाग को _लॉजिस्टिक्स_ के रूप में जाना जाता है।
|
||||||
|
|
||||||
|
इन 4 पाठों में, आप सीखेंगे कि भोजन की निगरानी करके आपूर्ति श्रृंखला को बेहतर बनाने के लिए इंटरनेट ऑफ थिंग्स को कैसे लागू किया जाए क्योंकि इसे एक (वर्चुअल) ट्रक पर लोड किया जाता है, जिसे ट्रैक किया जाता है क्योंकि यह अपने गंतव्य पर जाता है। आप जीपीएस ट्रैकिंग के बारे में सीखेंगे, जीपीएस डेटा को कैसे स्टोर और विज़ुअलाइज़ करें, और ट्रक के अपने गंतव्य पर पहुंचने पर कैसे सतर्क रहें।
|
||||||
|
|
||||||
|
> 💁 ये पाठ में हम कुछ क्लाउड संसाधनों का उपयोग करेंगे। यदि आप इस परियोजना के सभी पाठों को पूरा नहीं करते हैं, तो सुनिश्चित करें कि आप [अपना प्रोजेक्ट साफ़ करें](../clean-up.md)।
|
||||||
|
|
||||||
|
## विषय
|
||||||
|
|
||||||
|
1. [स्थान ट्रैकिंग](lessons/1-location-tracking/README.md)
|
||||||
|
1. [स्थान डेटा स्टोर करें](./3-transport/lessons/2-store-location-data/README.md)
|
||||||
|
1. [स्थान डेटा की कल्पना करें](lessons/3-visualize-location-data/README.md)
|
||||||
|
1. [जियोफेंस](lessons/4-geofences/README.md)
|
||||||
|
|
||||||
|
## क्रेडिट
|
||||||
|
|
||||||
|
सभी पाठ [जिम बेनेट](https://GitHub.com/JimBobBennett) द्वारा ️♥️ साथ लिखे गए थे
|
@ -0,0 +1,24 @@
|
|||||||
|
# निर्माण और प्रसंस्करण - भोजन के प्रसंस्करण में सुधार के लिए IoT का उपयोग करना।
|
||||||
|
|
||||||
|
एक बार जब भोजन एक केंद्रीय हब या प्रसंस्करण संयंत्र में पहुंच जाता है, तो इसे हमेशा सुपरमार्केट में नहीं भेजा जाता है। भोजन को कई बार प्रसंस्करण के कई चरणों से गुज़रना पड़ता है, जैसे गुणवत्ता के आधार पर छाँटना। यह एक प्रक्रिया है जो मैनुअल हुआ करती थी - यह खेत में शुरू होती थी जब बीनने वाले केवल पके फल चुनते थे, फिर कारखाने में फलों को एक कन्वेयर बेल्ट पर चलाया जाता था और कर्मचारी किसी भी टूटे या सड़े हुए फल को अपने हाथों से हटा देते थे। स्कूल के दौरान ग्रीष्मकालीन नौकरी के रूप में स्वयं स्ट्रॉबेरी को चुनने और छाँटने के बाद, मैं इस बात कि गवाही दे सकता हूं कि यह कोई मज़ेदार काम नहीं है।
|
||||||
|
|
||||||
|
अधिक आधुनिक सेटअप छँटाई के लिए IoT पर निर्भर करते हैं। [वीको](https://wecotek.com) के सॉर्टर्स (छँटाई के उपकरण) जैसे कुछ शुरुआती उपकरण उत्पाद की गुणवत्ता का पता लगाने के लिए ऑप्टिकल सेंसर का उपयोग करते हैं, उदाहरण के लिए हरे टमाटर को अस्वीकार करते हैं। इन्हें खेत में ही हार्वेस्टर में या प्रसंस्करण संयंत्रों में लगाया जा सकता है।
|
||||||
|
|
||||||
|
जैसे-जैसे आर्टिफिशियल इंटेलिजेंस (AI) और मशीन लर्निंग (ML) में प्रगति होती है, फल और विदेशी वस्तुओं, जैसे चट्टानों, गंदगी या कीड़ों, के बीच अंतर करने के लिए प्रशिक्षित ML मॉडल का उपयोग करके ये मशीनें और अधिक उन्नत हो सकती हैं। इन मॉडलों को फलों की गुणवत्ता का पता लगाने के लिए भी प्रशिक्षित किया जा सकता है, न केवल टूटे हुए फलों को पहचानना, बल्कि बीमारी या अन्य फसल सम्बन्धी समस्याओं का जल्द पता लगाना।
|
||||||
|
|
||||||
|
>🎓शब्द *एमएल मॉडल* डेटा के एक सेट पर प्रशिक्षण मशीन लर्निंग सॉफ्टवेयर के आउटपुट को संदर्भित करता है। उदाहरण के लिए, आप पके और कच्चे टमाटर के बीच अंतर करने के लिए एमएल मॉडल को प्रशिक्षित कर सकते हैं, फिर नई छवियों पर मॉडल का उपयोग करके देखें कि टमाटर पके हैं या नहीं।
|
||||||
|
|
||||||
|
इन 4 पाठों में आप सीखेंगे कि फलों की गुणवत्ता का पता लगाने के लिए छवि-आधारित AI मॉडल को कैसे प्रशिक्षित किया जाए, IoT डिवाइस से इनका उपयोग कैसे किया जाए, और इन्हें 'एज' पर कैसे चलाया जाए - अर्थात् क्लाउड के बजाय IoT डिवाइस पर।
|
||||||
|
|
||||||
|
> 💁 इस पाठ में हम कुछ क्लाउड संसाधनों का उपयोग करेंगे। यदि आप इस परियोजना के सभी पाठों को पूरा नहीं करते हैं, तो आप [अपने परियोजना को साफ़ करना](../clean-up.md) सुनिश्चित करें।
|
||||||
|
|
||||||
|
## विषय
|
||||||
|
|
||||||
|
1. [फल गुणवत्ता संसूचक को प्रशिक्षित करें](./lessons/1-train-fruit-detector/README.md)
|
||||||
|
1. [IoT डिवाइस से फलों की गुणवत्ता जांचें](./lessons/2-check-fruit-from-device/README.md)
|
||||||
|
1. [अपना फ्रूट डिटेक्टर एज चलाएं](./lessons/3-run-fruit-detector-edge/README.md)
|
||||||
|
1. [एक सेंसर से फलों की गुणवत्ता का पता लगाना](./lessons/4-trigger-fruit-detector/README.md)
|
||||||
|
|
||||||
|
## क्रेडिट
|
||||||
|
|
||||||
|
सभी पाठ [जिम बेनेट](https://GitHub.com/JimBobBennett) द्वारा ️♥️ साथ लिखे गए थे ।
|
@ -0,0 +1,26 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
location = os.environ['SPEECH_LOCATION']
|
||||||
|
speech_key = os.environ['SPEECH_KEY']
|
||||||
|
|
||||||
|
req_body = req.get_json()
|
||||||
|
language = req_body['language']
|
||||||
|
|
||||||
|
url = f'https://{location}.tts.speech.microsoft.com/cognitiveservices/voices/list'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Ocp-Apim-Subscription-Key': speech_key
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
voices_json = json.loads(response.text)
|
||||||
|
|
||||||
|
voices = filter(lambda x: x['Locale'].lower() == language.lower(), voices_json)
|
||||||
|
voices = map(lambda x: x['ShortName'], voices)
|
||||||
|
|
||||||
|
return func.HttpResponse(json.dumps(list(voices)), status_code=200)
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"authLevel": "function",
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req",
|
||||||
|
"methods": [
|
||||||
|
"get",
|
||||||
|
"post"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0",
|
||||||
|
"logging": {
|
||||||
|
"applicationInsights": {
|
||||||
|
"samplingSettings": {
|
||||||
|
"isEnabled": true,
|
||||||
|
"excludedTypes": "Request"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extensionBundle": {
|
||||||
|
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||||
|
"version": "[2.*, 3.0.0)"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"IsEncrypted": false,
|
||||||
|
"Values": {
|
||||||
|
"FUNCTIONS_WORKER_RUNTIME": "python",
|
||||||
|
"AzureWebJobsStorage": "",
|
||||||
|
"LUIS_KEY": "<primary key>",
|
||||||
|
"LUIS_ENDPOINT_URL": "<endpoint url>",
|
||||||
|
"LUIS_APP_ID": "<app id>",
|
||||||
|
"SPEECH_KEY": "<key>",
|
||||||
|
"SPEECH_LOCATION": "<location>"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
# Do not include azure-functions-worker as it may conflict with the Azure Functions platform
|
||||||
|
|
||||||
|
azure-functions
|
||||||
|
azure-cognitiveservices-language-luis
|
||||||
|
librosa
|
@ -0,0 +1,52 @@
|
|||||||
|
import io
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import librosa
|
||||||
|
import soundfile as sf
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
location = os.environ['SPEECH_LOCATION']
|
||||||
|
speech_key = os.environ['SPEECH_KEY']
|
||||||
|
|
||||||
|
def get_access_token():
|
||||||
|
headers = {
|
||||||
|
'Ocp-Apim-Subscription-Key': speech_key
|
||||||
|
}
|
||||||
|
|
||||||
|
token_endpoint = f'https://{location}.api.cognitive.microsoft.com/sts/v1.0/issuetoken'
|
||||||
|
response = requests.post(token_endpoint, headers=headers)
|
||||||
|
return str(response.text)
|
||||||
|
|
||||||
|
playback_format = 'riff-48khz-16bit-mono-pcm'
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
req_body = req.get_json()
|
||||||
|
language = req_body['language']
|
||||||
|
voice = req_body['voice']
|
||||||
|
text = req_body['text']
|
||||||
|
|
||||||
|
url = f'https://{location}.tts.speech.microsoft.com/cognitiveservices/v1'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + get_access_token(),
|
||||||
|
'Content-Type': 'application/ssml+xml',
|
||||||
|
'X-Microsoft-OutputFormat': playback_format
|
||||||
|
}
|
||||||
|
|
||||||
|
ssml = f'<speak version=\'1.0\' xml:lang=\'{language}\'>'
|
||||||
|
ssml += f'<voice xml:lang=\'{language}\' name=\'{voice}\'>'
|
||||||
|
ssml += text
|
||||||
|
ssml += '</voice>'
|
||||||
|
ssml += '</speak>'
|
||||||
|
|
||||||
|
response = requests.post(url, headers=headers, data=ssml.encode('utf-8'))
|
||||||
|
|
||||||
|
raw_audio, sample_rate = librosa.load(io.BytesIO(response.content), sr=48000)
|
||||||
|
resampled = librosa.resample(raw_audio, sample_rate, 44100)
|
||||||
|
|
||||||
|
output_buffer = io.BytesIO()
|
||||||
|
sf.write(output_buffer, resampled, 44100, 'PCM_16', format='wav')
|
||||||
|
output_buffer.seek(0)
|
||||||
|
|
||||||
|
return func.HttpResponse(output_buffer.read(), status_code=200)
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"authLevel": "function",
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req",
|
||||||
|
"methods": [
|
||||||
|
"get",
|
||||||
|
"post"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from azure.cognitiveservices.language.luis.runtime import LUISRuntimeClient
|
||||||
|
from msrest.authentication import CognitiveServicesCredentials
|
||||||
|
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
luis_key = os.environ['LUIS_KEY']
|
||||||
|
endpoint_url = os.environ['LUIS_ENDPOINT_URL']
|
||||||
|
app_id = os.environ['LUIS_APP_ID']
|
||||||
|
|
||||||
|
credentials = CognitiveServicesCredentials(luis_key)
|
||||||
|
client = LUISRuntimeClient(endpoint=endpoint_url, credentials=credentials)
|
||||||
|
|
||||||
|
req_body = req.get_json()
|
||||||
|
text = req_body['text']
|
||||||
|
logging.info(f'Request - {text}')
|
||||||
|
prediction_request = { 'query' : text }
|
||||||
|
|
||||||
|
prediction_response = client.prediction.get_slot_prediction(app_id, 'Staging', prediction_request)
|
||||||
|
|
||||||
|
if prediction_response.prediction.top_intent == 'set timer':
|
||||||
|
numbers = prediction_response.prediction.entities['number']
|
||||||
|
time_units = prediction_response.prediction.entities['time unit']
|
||||||
|
total_seconds = 0
|
||||||
|
|
||||||
|
for i in range(0, len(numbers)):
|
||||||
|
number = numbers[i]
|
||||||
|
time_unit = time_units[i][0]
|
||||||
|
|
||||||
|
if time_unit == 'minute':
|
||||||
|
total_seconds += number * 60
|
||||||
|
else:
|
||||||
|
total_seconds += number
|
||||||
|
|
||||||
|
logging.info(f'Timer required for {total_seconds} seconds')
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'seconds': total_seconds
|
||||||
|
}
|
||||||
|
return func.HttpResponse(json.dumps(payload), status_code=200)
|
||||||
|
|
||||||
|
return func.HttpResponse(status_code=404)
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"authLevel": "function",
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req",
|
||||||
|
"methods": [
|
||||||
|
"get",
|
||||||
|
"post"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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,91 @@
|
|||||||
|
#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 *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,127 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
Serial.println("Ready.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto timer = timer_create_default();
|
||||||
|
|
||||||
|
void say(String text)
|
||||||
|
{
|
||||||
|
Serial.println(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,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
|
@ -1,3 +1,287 @@
|
|||||||
# Set a timer - Wio Terminal
|
# Set a timer - Wio Terminal
|
||||||
|
|
||||||
Coming soon
|
In this part of the lesson, you will call your serverless code to understand the speech, and set a timer on your Wio Terminal based off the results.
|
||||||
|
|
||||||
|
## Set a timer
|
||||||
|
|
||||||
|
The text that comes back from the speech to text call needs to be sent to your serverless code to be processed by LUIS, getting back the number of seconds for the timer. This number of seconds can be used to set a timer.
|
||||||
|
|
||||||
|
Microcontrollers don't natively have support for multiple threads in Arduino, so there are no standard timer classes like you might find when coding in Python or other higher-level languages. Instead you can use timer libraries that work by measuring elapsed time in the `loop` function, and calling functions when the time is up.
|
||||||
|
|
||||||
|
### Task - send the text to the serverless function
|
||||||
|
|
||||||
|
1. Open the `smart-timer` project in VS Code if it is not already open.
|
||||||
|
|
||||||
|
1. Open the `config.h` header file and add the URL for your function app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const char *TEXT_TO_TIMER_FUNCTION_URL = "<URL>";
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<URL>` with the URL for your function app that you obtained in the last step of the last lesson, pointing to the IP address of your local machine that is running the function app.
|
||||||
|
|
||||||
|
1. Create a new file in the `src` folder called `language_understanding.h`. This will be used to define a class to send the recognized speech to your function app to be converted to seconds using LUIS.
|
||||||
|
|
||||||
|
1. Add the following to the top of this file:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
This includes some needed header files.
|
||||||
|
|
||||||
|
1. Define a class called `LanguageUnderstanding`, and declare an instance of this class:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class LanguageUnderstanding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
LanguageUnderstanding languageUnderstanding;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To call your functions app, you need to declare a WiFi client. Add the following to the `private` section of the class:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
WiFiClient _client;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. In the `public` section, declare a method called `GetTimerDuration` to call the functions app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int GetTimerDuration(String text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. In the `GetTimerDuration` method, add the following code to build the JSON to be sent to the functions app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["text"] = text;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
```
|
||||||
|
|
||||||
|
This coverts the text passed to the `GetTimerDuration` method into the following JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text" : "<text>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
where `<text>` is the text passed to the function.
|
||||||
|
|
||||||
|
1. Below this, add the following code to make the functions app call:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TEXT_TO_TIMER_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes a POST request to the functions app, passing the JSON body and getting the response code.
|
||||||
|
|
||||||
|
1. Add the following code below this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This code checks the response code. If it is 200 (success), then the number of seconds for the time is retrieved from the response body. Otherwise an error is sent to the serial monitor and the number of seconds is set to 0.
|
||||||
|
|
||||||
|
1. Add the following code to the end of this method to close the HTTP connection and return the number of seconds:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httpClient.end();
|
||||||
|
|
||||||
|
return seconds;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. In the `main.cpp` file, include this new header:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "speech_to_text.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. On the end of the `processAudio` function, call the `GetTimerDuration` method to get the timer duration:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int total_seconds = languageUnderstanding.GetTimerDuration(text);
|
||||||
|
```
|
||||||
|
|
||||||
|
This converts the text from the call to the `SpeechToText` class into the number of seconds for the timer.
|
||||||
|
|
||||||
|
### Task - set a timer
|
||||||
|
|
||||||
|
The number of seconds can be used to set a timer.
|
||||||
|
|
||||||
|
1. Add the following library dependency to the `platformio.ini` file to add a library to set a timer:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
contrem/arduino-timer @ 2.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Add an include directive for this library to the `main.cpp` file:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <arduino-timer.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Above the `processAudio` function, add the following code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto timer = timer_create_default();
|
||||||
|
```
|
||||||
|
|
||||||
|
This code declares a timer called `timer`.
|
||||||
|
|
||||||
|
1. Below this, add the following code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void say(String text)
|
||||||
|
{
|
||||||
|
Serial.print("Saying ");
|
||||||
|
Serial.println(text);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This `say` function will eventually convert text to speech, but for now it will just write the passed in text to the serial monitor.
|
||||||
|
|
||||||
|
1. Below the `say` function, add the following code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool timerExpired(void *announcement)
|
||||||
|
{
|
||||||
|
say((char *)announcement);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a callback function that will be called when a timer expires. It is passed a message to say when the timer expires. Timers can repeat, and this can be controlled by the return value of this callback - this returns `false`, to tell the timer to not run again.
|
||||||
|
|
||||||
|
1. Add the following code to the end of the `processAudio` function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (total_seconds == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutes = total_seconds / 60;
|
||||||
|
int seconds = total_seconds % 60;
|
||||||
|
```
|
||||||
|
|
||||||
|
This code checks the total number of seconds, and if it is 0, returns from teh function call so no timers are set. It then converts the total number of seconds into minutes and seconds.
|
||||||
|
|
||||||
|
1. Below this code, add the following to create a message to say when the timer is started:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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.";
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Below this, add similar code to create a message to say when the timer has expired:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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.";
|
||||||
|
```
|
||||||
|
|
||||||
|
1. After this, say the timer started message:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
say(begin_message);
|
||||||
|
```
|
||||||
|
|
||||||
|
1. At the end of this function, start the timer:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
timer.in(total_seconds * 1000, timerExpired, (void *)(end_message.c_str()));
|
||||||
|
```
|
||||||
|
|
||||||
|
This triggers the timer. The timer is set using milliseconds, so the total number of seconds is multiplied by 1,000 to convert to milliseconds. The `timerExpired` function is passed as the callback, and the `end_message` is passed as an argument to pass to the callback. This callback only takes `void *` arguments, so the string is converted appropriately.
|
||||||
|
|
||||||
|
1. Finally, the timer needs to *tick*, and this is done in the `loop` function. Add the following code at the end of the `loop` function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
timer.tick();
|
||||||
|
```
|
||||||
|
|
||||||
|
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. 4 seconds of audio will be captured, converted to text, then sent to your function app, and a timer will be set. Make sure your functions app is running locally.
|
||||||
|
|
||||||
|
You will see when the timer starts, and when it ends.
|
||||||
|
|
||||||
|
```output
|
||||||
|
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
|
||||||
|
--- More details at http://bit.ly/pio-monitor-filters
|
||||||
|
--- Miniterm on /dev/cu.usbmodem1101 9600,8,N,1 ---
|
||||||
|
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
||||||
|
Connecting to WiFi..
|
||||||
|
Connected!
|
||||||
|
Got access token.
|
||||||
|
Ready.
|
||||||
|
Starting recording...
|
||||||
|
Finished recording
|
||||||
|
Sending speech...
|
||||||
|
Speech sent!
|
||||||
|
{"RecognitionStatus":"Success","DisplayText":"Set a 2 minute and 27 second timer.","Offset":4700000,"Duration":35300000}
|
||||||
|
Set a 2 minute and 27 second timer.
|
||||||
|
{"seconds": 147}
|
||||||
|
2 minute 27 second timer started.
|
||||||
|
Times up on your 2 minute 27 second timer.
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💁 You can find this code in the [code-timer/wio-terminal](code-timer/wio-terminal) folder.
|
||||||
|
|
||||||
|
😀 Your timer program was a success!
|
||||||
|
@ -1,3 +1,522 @@
|
|||||||
# Text to speech - Wio Terminal
|
# Text to speech - Wio Terminal
|
||||||
|
|
||||||
Coming soon
|
In this part of the lesson, you will convert text to speech to provide spoken feedback.
|
||||||
|
|
||||||
|
## Text to speech
|
||||||
|
|
||||||
|
The speech services SDK that you used in the last lesson to convert speech to text can be used to convert text back to speech.
|
||||||
|
|
||||||
|
## Get a list of voices
|
||||||
|
|
||||||
|
When requesting speech, you need to provide the voice to use as speech can be generated using a variety of different voices. Each language supports a range of different voices, and you can get the list of supported voices for each language from the speech services SDK. The limitations of microcontrollers come into play here - the call to get the list of voices supported by the text to speech services is a JSON document of over 77KB in size, far to large to be processed by the Wio Terminal. At the time of writing, the full list contains 215 voices, each defined by a JSON document like the following:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Name": "Microsoft Server Speech Text to Speech Voice (en-US, AriaNeural)",
|
||||||
|
"DisplayName": "Aria",
|
||||||
|
"LocalName": "Aria",
|
||||||
|
"ShortName": "en-US-AriaNeural",
|
||||||
|
"Gender": "Female",
|
||||||
|
"Locale": "en-US",
|
||||||
|
"StyleList": [
|
||||||
|
"chat",
|
||||||
|
"customerservice",
|
||||||
|
"narration-professional",
|
||||||
|
"newscast-casual",
|
||||||
|
"newscast-formal",
|
||||||
|
"cheerful",
|
||||||
|
"empathetic"
|
||||||
|
],
|
||||||
|
"SampleRateHertz": "24000",
|
||||||
|
"VoiceType": "Neural",
|
||||||
|
"Status": "GA"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This JSON is for the **Aria** voice, which has multiple voice styles. All that is needed when converting text to speech is the shortname, `en-US-AriaNeural`.
|
||||||
|
|
||||||
|
Instead of downloading and decoding this entire list on your microcontroller, you will need to write some more serverless code to retrieve the list of voices for the language you are using, and call this from your Wio Terminal. Your code can then pick an appropriate voice from the list, such as the first one it finds.
|
||||||
|
|
||||||
|
### Task - create a serverless function to get a list of voices
|
||||||
|
|
||||||
|
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. Open the `local.settings.json` file and add settings for the speech API key and location:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"SPEECH_KEY": "<key>",
|
||||||
|
"SPEECH_LOCATION": "<location>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<key>` with the API key for your speech service resource. Replace `<location>` with the location you used when you created the speech service resource.
|
||||||
|
|
||||||
|
1. Add a new HTTP trigger to this app called `get-voices` using the following command from inside the VS Code terminal in the root folder of the functions app project:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
func new --name get-voices --template "HTTP trigger"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create an HTTP trigger called `get-voices`.
|
||||||
|
|
||||||
|
1. Replace the contents of the `__init__.py` file in the `get-voices` folder with the following:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
location = os.environ['SPEECH_LOCATION']
|
||||||
|
speech_key = os.environ['SPEECH_KEY']
|
||||||
|
|
||||||
|
req_body = req.get_json()
|
||||||
|
language = req_body['language']
|
||||||
|
|
||||||
|
url = f'https://{location}.tts.speech.microsoft.com/cognitiveservices/voices/list'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Ocp-Apim-Subscription-Key': speech_key
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
voices_json = json.loads(response.text)
|
||||||
|
|
||||||
|
voices = filter(lambda x: x['Locale'].lower() == language.lower(), voices_json)
|
||||||
|
voices = map(lambda x: x['ShortName'], voices)
|
||||||
|
|
||||||
|
return func.HttpResponse(json.dumps(list(voices)), status_code=200)
|
||||||
|
```
|
||||||
|
|
||||||
|
This code makes an HTTP request to the endpoint to get the voices. This voices list is a large block of JSON with voices for all languages, so the voices for the language passed in the request body are filtered out, then the shortname is extracted and returned as a JSON list. The shortname is the value needed to convert text to speech, so only this value is returned.
|
||||||
|
|
||||||
|
> 💁 You can change the filter as necessary to select just the voices you want.
|
||||||
|
|
||||||
|
This reduces the size of the data from 77KB (at the time of writing), to a much smaller JSON document. For example, for US voices this is 408 bytes.
|
||||||
|
|
||||||
|
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 your language as a JSON body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"language":"<language>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
1. Open the `config.h` header file and add the URL for your function app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const char *GET_VOICES_FUNCTION_URL = "<URL>";
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<URL>` with the URL for the `get-voices` 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 `get-voices` instead of `text-to-timer`.
|
||||||
|
|
||||||
|
1. Create a new file in the `src` folder called `text_to_speech.h`. This will be used to define a class to convert from text to speech.
|
||||||
|
|
||||||
|
1. Add the following include directives to the top of the new `text_to_speech.h` file:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#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"
|
||||||
|
#include "speech_to_text.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Add the following code below this to declare the `TextToSpeech` class, along with an instance that can be used in the rest of the application:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class TextToSpeech
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
TextToSpeech textToSpeech;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To call your functions app, you need to declare a WiFi client. Add the following to the `private` section of the class:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
WiFiClient _client;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. In the `private` section, add a field for the selected voice:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
String _voice;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To the `public` section, add an `init` function that will get the first voice:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To get the voices, a JSON document needs to be sent to the function app with the language. Add the following code to the `init` function to create this JSON document:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["language"] = LANGUAGE;
|
||||||
|
|
||||||
|
String 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:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, GET_VOICES_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Below this add code to check the response code, and if it is 200 (success), then extract the list of voices, retrieving the first one from the list:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. After this, end the HTTP client connection:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httpClient.end();
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Open the `main.cpp` file, and add the following include directive at the top to include this new header file:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "text_to_speech.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. In the `setup` function, underneath the call to `speechToText.init();`, add the following to initialize the `TextToSpeech` class:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
textToSpeech.init();
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Build this code, upload it to your Wio Terminal and test it out through the serial monitor. Make sure your function app is running.
|
||||||
|
|
||||||
|
You will see the list of available voices returned from the function app, along with the selected voice.
|
||||||
|
|
||||||
|
```output
|
||||||
|
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
|
||||||
|
--- More details at http://bit.ly/pio-monitor-filters
|
||||||
|
--- Miniterm on /dev/cu.usbmodem1101 9600,8,N,1 ---
|
||||||
|
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
|
||||||
|
Connecting to WiFi..
|
||||||
|
Connected!
|
||||||
|
Got access token.
|
||||||
|
["en-US-JennyNeural", "en-US-JennyMultilingualNeural", "en-US-GuyNeural", "en-US-AriaNeural", "en-US-AmberNeural", "en-US-AnaNeural", "en-US-AshleyNeural", "en-US-BrandonNeural", "en-US-ChristopherNeural", "en-US-CoraNeural", "en-US-ElizabethNeural", "en-US-EricNeural", "en-US-JacobNeural", "en-US-MichelleNeural", "en-US-MonicaNeural", "en-US-AriaRUS", "en-US-BenjaminRUS", "en-US-GuyRUS", "en-US-ZiraRUS"]
|
||||||
|
Using voice en-US-JennyNeural
|
||||||
|
Ready.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Convert text to speech
|
||||||
|
|
||||||
|
Once you have a voice to use, it can be used to convert text to speech. The same memory limitations with voices also apply when converting speech to text, so you will need to write the speech to an SD card ready to be played over the ReSpeaker.
|
||||||
|
|
||||||
|
> 💁 In earlier lessons in this project you used flash memory to store speech captured from the microphone. This lesson uses an SD card as is it easier to play audio from it using the Seeed audio libraries.
|
||||||
|
|
||||||
|
There is also another limitation to consider, the available audio data from the speech service, and the formats that the Wio Terminal supports. Unlike full computers, audio libraries for microcontrollers can be very limited in the audio formats they support. For example, the Seeed Arduino Audio library that can play sound over the ReSpeaker only supports audio at a 44.1KHz sample rate. The Azure speech services can provide audio in a number of formats, but none of them use this sample rate, they only provide 8KHz, 16KHz, 24KHz and 48KHz. This means the audio needs to be re-sampled to 44.1KHz, something that would need more resources that the Wio Terminal has, especially memory.
|
||||||
|
|
||||||
|
When needing to manipulate data like this, it is often better to use serverless code, especially if the data is sourced via a web call. The Wio Terminal can call a serverless function, passing in the text to convert, and the serverless function can both call the speech service to convert text to speech, as well as re-sample the audio to the required sample rate. It can then return the audio in the form the Wio Terminal needs to be stored on the SD card and played over the ReSpeaker.
|
||||||
|
|
||||||
|
### Task - create a serverless function to convert text to speech
|
||||||
|
|
||||||
|
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. Add a new HTTP trigger to this app called `text-to-speech` using the following command from inside the VS Code terminal in the root folder of the functions app project:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
func new --name text-to-speech --template "HTTP trigger"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create an HTTP trigger called `text-to-speech`.
|
||||||
|
|
||||||
|
1. The [librosa](https://librosa.org) Pip package has functions to re-sample audio, so add this to the `requirements.txt` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
librosa
|
||||||
|
```
|
||||||
|
|
||||||
|
Once this has been added, install the Pip packages using the following command from the VS Code terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ If you are using Linux, including Raspberry Pi OS, you may need to install `libsndfile` with the following command:
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> sudo apt update
|
||||||
|
> sudo apt install libsndfile1-dev
|
||||||
|
> ```
|
||||||
|
|
||||||
|
1. To convert text to speech, you cannot use the speech API key directly, instead you need to request an access token, using the API key to authenticate the access token request. Open the `__init__.py` file from the `text-to-speech` folder and replace all the code in it with the following:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import librosa
|
||||||
|
import soundfile as sf
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
location = os.environ['SPEECH_LOCATION']
|
||||||
|
speech_key = os.environ['SPEECH_KEY']
|
||||||
|
|
||||||
|
def get_access_token():
|
||||||
|
headers = {
|
||||||
|
'Ocp-Apim-Subscription-Key': speech_key
|
||||||
|
}
|
||||||
|
|
||||||
|
token_endpoint = f'https://{location}.api.cognitive.microsoft.com/sts/v1.0/issuetoken'
|
||||||
|
response = requests.post(token_endpoint, headers=headers)
|
||||||
|
return str(response.text)
|
||||||
|
```
|
||||||
|
|
||||||
|
This defines constants for the location and speech key that will be read from the settings. It then defines the `get_access_token` function that will retrieve an access token for the speech service.
|
||||||
|
|
||||||
|
1. Below this code, add the following:
|
||||||
|
|
||||||
|
```python
|
||||||
|
playback_format = 'riff-48khz-16bit-mono-pcm'
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
req_body = req.get_json()
|
||||||
|
language = req_body['language']
|
||||||
|
voice = req_body['voice']
|
||||||
|
text = req_body['text']
|
||||||
|
|
||||||
|
url = f'https://{location}.tts.speech.microsoft.com/cognitiveservices/v1'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + get_access_token(),
|
||||||
|
'Content-Type': 'application/ssml+xml',
|
||||||
|
'X-Microsoft-OutputFormat': playback_format
|
||||||
|
}
|
||||||
|
|
||||||
|
ssml = f'<speak version=\'1.0\' xml:lang=\'{language}\'>'
|
||||||
|
ssml += f'<voice xml:lang=\'{language}\' name=\'{voice}\'>'
|
||||||
|
ssml += text
|
||||||
|
ssml += '</voice>'
|
||||||
|
ssml += '</speak>'
|
||||||
|
|
||||||
|
response = requests.post(url, headers=headers, data=ssml.encode('utf-8'))
|
||||||
|
|
||||||
|
raw_audio, sample_rate = librosa.load(io.BytesIO(response.content), sr=48000)
|
||||||
|
resampled = librosa.resample(raw_audio, sample_rate, 44100)
|
||||||
|
|
||||||
|
output_buffer = io.BytesIO()
|
||||||
|
sf.write(output_buffer, resampled, 44100, 'PCM_16', format='wav')
|
||||||
|
output_buffer.seek(0)
|
||||||
|
|
||||||
|
return func.HttpResponse(output_buffer.read(), status_code=200)
|
||||||
|
```
|
||||||
|
|
||||||
|
This defines the HTTP trigger that converts the text to speech. It extracts the text to convert, the language and the voice from the JSON body set to the request, builds some SSML to request the speech, then calls the relevant REST API authenticating using the access token. This REST API call returns the audio encoded as 16-bit, 48KHz mono WAV file, defined by the value of `playback_format`, which is sent to the REST API call.
|
||||||
|
|
||||||
|
This is then re-sampled by `librosa` from a sample rate of 48KHz to a sample rate of 44.1KHz, then this audio is saved to a binary buffer that is then returned.
|
||||||
|
|
||||||
|
1. Run your function app locally, or deploy it to the cloud. 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 language, voice and text as the JSON body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"language": "<language>",
|
||||||
|
"voice": "<voice>",
|
||||||
|
"text": "<text>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<language>` with your language, such as `en-GB`, or `zh-CN`. Replace `<voice>` with the voice you want to use. Replace `<text>` with the text you want to convert to speech. You can save the output to a file and play it with any audio player that can play WAV files.
|
||||||
|
|
||||||
|
For example, to convert "Hello" to speech using US English with the Jenny Neural voice, with the function app running locally, you can use the following curl command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -X GET 'http://localhost:7071/api/text-to-speech' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-o hello.wav \
|
||||||
|
-d '{
|
||||||
|
"language":"en-US",
|
||||||
|
"voice": "en-US-JennyNeural",
|
||||||
|
"text": "Hello"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
1. Open the `config.h` header file and add the URL for your function app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const char *TEXT_TO_SPEECH_FUNCTION_URL = "<URL>";
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<URL>` with the URL for the `text-to-speech` 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 `text-to-speech` instead of `text-to-timer`.
|
||||||
|
|
||||||
|
1. Open the `text_to_speech.h` header file, and add the following method to the `public` section of the `TextToSpeech` class:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void convertTextToSpeech(String text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To the `convertTextToSpeech` method, add the following code to create the JSON to send to the function app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
doc["language"] = LANGUAGE;
|
||||||
|
doc["voice"] = _voice;
|
||||||
|
doc["text"] = text;
|
||||||
|
|
||||||
|
String body;
|
||||||
|
serializeJson(doc, body);
|
||||||
|
```
|
||||||
|
|
||||||
|
This writes the language, voice and text to the JSON document, then serializes it to a string.
|
||||||
|
|
||||||
|
1. Below this, add the following code to call the function app:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HTTPClient httpClient;
|
||||||
|
httpClient.begin(_client, TEXT_TO_SPEECH_FUNCTION_URL);
|
||||||
|
|
||||||
|
int httpResponseCode = httpClient.POST(body);
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates an HTTPClient, then makes a POST request using the JSON document to the text to speech HTTP trigger.
|
||||||
|
|
||||||
|
1. If the call works, the raw binary data returned from the function app call can be streamed to a file on the SD card. Add the following code to do this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This code checks the response, and if it is 200 (success), the binary data is streamed to a file in the root of the SD Card called `SPEECH.WAV`.
|
||||||
|
|
||||||
|
1. At the end of this method, close the HTTP connection:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httpClient.end();
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The text to be spoken can now be converted to audio. In the `main.cpp` file, add the following line to the end of the `say` function to convert the text to say into audio:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
textToSpeech.convertTextToSpeech(text);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
1. Add the Azure Functions certificate to `config.h`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const char *FUNCTIONS_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\r\n"
|
||||||
|
"MIIFWjCCBEKgAwIBAgIQDxSWXyAgaZlP1ceseIlB4jANBgkqhkiG9w0BAQsFADBa\r\n"
|
||||||
|
"MQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJl\r\n"
|
||||||
|
"clRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTIw\r\n"
|
||||||
|
"MDcyMTIzMDAwMFoXDTI0MTAwODA3MDAwMFowTzELMAkGA1UEBhMCVVMxHjAcBgNV\r\n"
|
||||||
|
"BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEgMB4GA1UEAxMXTWljcm9zb2Z0IFJT\r\n"
|
||||||
|
"QSBUTFMgQ0EgMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqYnfP\r\n"
|
||||||
|
"mmOyBoTzkDb0mfMUUavqlQo7Rgb9EUEf/lsGWMk4bgj8T0RIzTqk970eouKVuL5R\r\n"
|
||||||
|
"IMW/snBjXXgMQ8ApzWRJCZbar879BV8rKpHoAW4uGJssnNABf2n17j9TiFy6BWy+\r\n"
|
||||||
|
"IhVnFILyLNK+W2M3zK9gheiWa2uACKhuvgCca5Vw/OQYErEdG7LBEzFnMzTmJcli\r\n"
|
||||||
|
"W1iCdXby/vI/OxbfqkKD4zJtm45DJvC9Dh+hpzqvLMiK5uo/+aXSJY+SqhoIEpz+\r\n"
|
||||||
|
"rErHw+uAlKuHFtEjSeeku8eR3+Z5ND9BSqc6JtLqb0bjOHPm5dSRrgt4nnil75bj\r\n"
|
||||||
|
"c9j3lWXpBb9PXP9Sp/nPCK+nTQmZwHGjUnqlO9ebAVQD47ZisFonnDAmjrZNVqEX\r\n"
|
||||||
|
"F3p7laEHrFMxttYuD81BdOzxAbL9Rb/8MeFGQjE2Qx65qgVfhH+RsYuuD9dUw/3w\r\n"
|
||||||
|
"ZAhq05yO6nk07AM9c+AbNtRoEcdZcLCHfMDcbkXKNs5DJncCqXAN6LhXVERCw/us\r\n"
|
||||||
|
"G2MmCMLSIx9/kwt8bwhUmitOXc6fpT7SmFvRAtvxg84wUkg4Y/Gx++0j0z6StSeN\r\n"
|
||||||
|
"0EJz150jaHG6WV4HUqaWTb98Tm90IgXAU4AW2GBOlzFPiU5IY9jt+eXC2Q6yC/Zp\r\n"
|
||||||
|
"TL1LAcnL3Qa/OgLrHN0wiw1KFGD51WRPQ0Sh7QIDAQABo4IBJTCCASEwHQYDVR0O\r\n"
|
||||||
|
"BBYEFLV2DDARzseSQk1Mx1wsyKkM6AtkMB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoI\r\n"
|
||||||
|
"VDaGezq1BE3wMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYI\r\n"
|
||||||
|
"KwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQoMCYwJAYI\r\n"
|
||||||
|
"KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTA6BgNVHR8EMzAxMC+g\r\n"
|
||||||
|
"LaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vT21uaXJvb3QyMDI1LmNybDAq\r\n"
|
||||||
|
"BgNVHSAEIzAhMAgGBmeBDAECATAIBgZngQwBAgIwCwYJKwYBBAGCNyoBMA0GCSqG\r\n"
|
||||||
|
"SIb3DQEBCwUAA4IBAQCfK76SZ1vae4qt6P+dTQUO7bYNFUHR5hXcA2D59CJWnEj5\r\n"
|
||||||
|
"na7aKzyowKvQupW4yMH9fGNxtsh6iJswRqOOfZYC4/giBO/gNsBvwr8uDW7t1nYo\r\n"
|
||||||
|
"DYGHPpvnpxCM2mYfQFHq576/TmeYu1RZY29C4w8xYBlkAA8mDJfRhMCmehk7cN5F\r\n"
|
||||||
|
"JtyWRj2cZj/hOoI45TYDBChXpOlLZKIYiG1giY16vhCRi6zmPzEwv+tk156N6cGS\r\n"
|
||||||
|
"Vm44jTQ/rs1sa0JSYjzUaYngoFdZC4OfxnIkQvUIA4TOFmPzNPEFdjcZsgbeEz4T\r\n"
|
||||||
|
"cGHTBPK4R28F44qIMCtHRV55VMX53ev6P3hRddJb\r\n"
|
||||||
|
"-----END CERTIFICATE-----\r\n";
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
_client.setCACert(FUNCTIONS_CERTIFICATE);
|
||||||
|
```
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
location = os.environ['SPEECH_LOCATION']
|
||||||
|
speech_key = os.environ['SPEECH_KEY']
|
||||||
|
|
||||||
|
req_body = req.get_json()
|
||||||
|
language = req_body['language']
|
||||||
|
|
||||||
|
url = f'https://{location}.tts.speech.microsoft.com/cognitiveservices/voices/list'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Ocp-Apim-Subscription-Key': speech_key
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
voices_json = json.loads(response.text)
|
||||||
|
|
||||||
|
voices = filter(lambda x: x['Locale'].lower() == language.lower(), voices_json)
|
||||||
|
voices = map(lambda x: x['ShortName'], voices)
|
||||||
|
|
||||||
|
return func.HttpResponse(json.dumps(list(voices)), status_code=200)
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"authLevel": "function",
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req",
|
||||||
|
"methods": [
|
||||||
|
"get",
|
||||||
|
"post"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0",
|
||||||
|
"logging": {
|
||||||
|
"applicationInsights": {
|
||||||
|
"samplingSettings": {
|
||||||
|
"isEnabled": true,
|
||||||
|
"excludedTypes": "Request"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extensionBundle": {
|
||||||
|
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||||
|
"version": "[2.*, 3.0.0)"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"IsEncrypted": false,
|
||||||
|
"Values": {
|
||||||
|
"FUNCTIONS_WORKER_RUNTIME": "python",
|
||||||
|
"AzureWebJobsStorage": "",
|
||||||
|
"LUIS_KEY": "<primary key>",
|
||||||
|
"LUIS_ENDPOINT_URL": "<endpoint url>",
|
||||||
|
"LUIS_APP_ID": "<app id>",
|
||||||
|
"SPEECH_KEY": "<key>",
|
||||||
|
"SPEECH_LOCATION": "<location>",
|
||||||
|
"TRANSLATOR_KEY": "<key>",
|
||||||
|
"TRANSLATOR_LOCATION": "<location>"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
# Do not include azure-functions-worker as it may conflict with the Azure Functions platform
|
||||||
|
|
||||||
|
azure-functions
|
||||||
|
azure-cognitiveservices-language-luis
|
||||||
|
librosa
|
@ -0,0 +1,52 @@
|
|||||||
|
import io
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import librosa
|
||||||
|
import soundfile as sf
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
location = os.environ['SPEECH_LOCATION']
|
||||||
|
speech_key = os.environ['SPEECH_KEY']
|
||||||
|
|
||||||
|
def get_access_token():
|
||||||
|
headers = {
|
||||||
|
'Ocp-Apim-Subscription-Key': speech_key
|
||||||
|
}
|
||||||
|
|
||||||
|
token_endpoint = f'https://{location}.api.cognitive.microsoft.com/sts/v1.0/issuetoken'
|
||||||
|
response = requests.post(token_endpoint, headers=headers)
|
||||||
|
return str(response.text)
|
||||||
|
|
||||||
|
playback_format = 'riff-48khz-16bit-mono-pcm'
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
req_body = req.get_json()
|
||||||
|
language = req_body['language']
|
||||||
|
voice = req_body['voice']
|
||||||
|
text = req_body['text']
|
||||||
|
|
||||||
|
url = f'https://{location}.tts.speech.microsoft.com/cognitiveservices/v1'
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + get_access_token(),
|
||||||
|
'Content-Type': 'application/ssml+xml',
|
||||||
|
'X-Microsoft-OutputFormat': playback_format
|
||||||
|
}
|
||||||
|
|
||||||
|
ssml = f'<speak version=\'1.0\' xml:lang=\'{language}\'>'
|
||||||
|
ssml += f'<voice xml:lang=\'{language}\' name=\'{voice}\'>'
|
||||||
|
ssml += text
|
||||||
|
ssml += '</voice>'
|
||||||
|
ssml += '</speak>'
|
||||||
|
|
||||||
|
response = requests.post(url, headers=headers, data=ssml.encode('utf-8'))
|
||||||
|
|
||||||
|
raw_audio, sample_rate = librosa.load(io.BytesIO(response.content), sr=48000)
|
||||||
|
resampled = librosa.resample(raw_audio, sample_rate, 44100)
|
||||||
|
|
||||||
|
output_buffer = io.BytesIO()
|
||||||
|
sf.write(output_buffer, resampled, 44100, 'PCM_16', format='wav')
|
||||||
|
output_buffer.seek(0)
|
||||||
|
|
||||||
|
return func.HttpResponse(output_buffer.read(), status_code=200)
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"authLevel": "function",
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req",
|
||||||
|
"methods": [
|
||||||
|
"get",
|
||||||
|
"post"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from azure.cognitiveservices.language.luis.runtime import LUISRuntimeClient
|
||||||
|
from msrest.authentication import CognitiveServicesCredentials
|
||||||
|
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest) -> func.HttpResponse:
|
||||||
|
luis_key = os.environ['LUIS_KEY']
|
||||||
|
endpoint_url = os.environ['LUIS_ENDPOINT_URL']
|
||||||
|
app_id = os.environ['LUIS_APP_ID']
|
||||||
|
|
||||||
|
credentials = CognitiveServicesCredentials(luis_key)
|
||||||
|
client = LUISRuntimeClient(endpoint=endpoint_url, credentials=credentials)
|
||||||
|
|
||||||
|
req_body = req.get_json()
|
||||||
|
text = req_body['text']
|
||||||
|
logging.info(f'Request - {text}')
|
||||||
|
prediction_request = { 'query' : text }
|
||||||
|
|
||||||
|
prediction_response = client.prediction.get_slot_prediction(app_id, 'Staging', prediction_request)
|
||||||
|
|
||||||
|
if prediction_response.prediction.top_intent == 'set timer':
|
||||||
|
numbers = prediction_response.prediction.entities['number']
|
||||||
|
time_units = prediction_response.prediction.entities['time unit']
|
||||||
|
total_seconds = 0
|
||||||
|
|
||||||
|
for i in range(0, len(numbers)):
|
||||||
|
number = numbers[i]
|
||||||
|
time_unit = time_units[i][0]
|
||||||
|
|
||||||
|
if time_unit == 'minute':
|
||||||
|
total_seconds += number * 60
|
||||||
|
else:
|
||||||
|
total_seconds += number
|
||||||
|
|
||||||
|
logging.info(f'Timer required for {total_seconds} seconds')
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'seconds': total_seconds
|
||||||
|
}
|
||||||
|
return func.HttpResponse(json.dumps(payload), status_code=200)
|
||||||
|
|
||||||
|
return func.HttpResponse(status_code=404)
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"authLevel": "function",
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req",
|
||||||
|
"methods": [
|
||||||
|
"get",
|
||||||
|
"post"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue