Merge branch 'main' into hindi_quiz

pull/249/head
Jim Bennett 4 years ago committed by GitHub
commit cd386846b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,14 @@
> Sketchnote by [Nitya Narasimhan](https://github.com/nitya). Click the image for a larger version.
This lesson was taught as part of the [Hello IoT series](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) from the [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn). The lesson was taught as 2 videos - a 1 hour lesson, and a 1 hour office hour diving deeper into parts of the lesson and answering questions.
[![Lesson 1: Introduction to IoT](https://img.youtube.com/vi/bVFfcYh6UBw/0.jpg)](https://youtu.be/bVFfcYh6UBw)
[![Lesson 1: Introduction to IoT - Office hours](https://img.youtube.com/vi/YI772q5v3yI/0.jpg)](https://youtu.be/YI772q5v3yI)
> 🎥 Click the images above to watch the videos
## Pre-lecture quiz
[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/1)

@ -0,0 +1,222 @@
# Introdução à IoT
![Um sketchnote de visão geral desta lição](../../../../sketchnotes/lesson-1.jpg)
> Sketchnote por [Nitya Narasimhan](https://github.com/nitya). Clique na imagem para uma versão maior.
## Questionário pré-aula
[Questionário pré-aula](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/1)
## Introdução
Esta lição cobre alguns dos tópicos introdutórios sobre a Internet das Coisas e mostra como configurar seu hardware.
Nesta lição, vamos cobrir:
* [O que é a 'Internet das Coisas'?](#o-que-é-a-internet-das-coisas)
* [Dispositivos IoT](#dispositivos-iot)
* [Configure seu dispositivo](#configure-seu-dispositivo)
* [Aplicações de IoT](#aplicações-de-iot)
* [Exemplos de dispositivos IoT que você pode ter ao seu redor](#exemplos-de-dispositivos-iot-que-você-pode-ter-ao-seu-redor)
## O que é a 'Internet das Coisas'?
O termo 'Internet das Coisas' foi cunhado por [Kevin Ashton](https://wikipedia.org/wiki/Kevin_Ashton) em 1999, para se referir à conexão da Internet ao mundo físico por meio de sensores. Desde então, o termo tem sido usado para descrever qualquer dispositivo que interage com o mundo físico ao seu redor, seja reunindo dados de sensores ou fornecendo interações com o mundo real por meio de atuadores (dispositivos que fazem algo como ligar um interruptor ou acender um LED ), geralmente conectado a outros dispositivos ou à Internet.
> **Sensores** coletam informações do mundo, como medição de velocidade, temperatura ou localização.
>
> **Atuadores** convertem sinais elétricos em interações com o mundo real, como acionar um interruptor, acender luzes, fazer sons ou enviar sinais de controle para outro hardware, por exemplo, para ligar uma tomada.
A IoT como uma área de tecnologia é mais do que apenas dispositivos - inclui serviços baseados em nuvem que podem processar os dados do sensor ou enviar solicitações para atuadores conectados a dispositivos IoT. Também inclui dispositivos que não têm ou não precisam de conectividade com a Internet, geralmente chamados de dispositivos de borda. São dispositivos que podem processar e responder a dados de sensores eles próprios, geralmente usando modelos de IA treinados na nuvem.
A IoT é um campo de tecnologia em rápido crescimento. Estima-se que até o final de 2020, 30 bilhões de dispositivos IoT foram implantados e conectados à Internet. Olhando para o futuro, estima-se que até 2025, os dispositivos IoT estarão reunindo quase 80 zetabytes de dados ou 80 trilhões de gigabytes. São muitos dados!
![Um gráfico mostrando dispositivos IoT ativos ao longo do tempo, com uma tendência de aumento de menos de 5 bilhões em 2015 para mais de 30 bilhões em 2025](../../../../images/connected-iot-devices.svg)
✅ Faça uma pequena pesquisa: quanto dos dados gerados pelos dispositivos IoT é realmente usado e quanto é desperdiçado? Por que tantos dados são ignorados?
Esses dados são a chave para o sucesso da IoT. Para ser um desenvolvedor de IoT bem-sucedido, você precisa entender os dados que precisa coletar, como coletá-los, como tomar decisões com base neles e como usar essas decisões para interagir com o mundo físico, se necessário.
## Dispositivos IoT
O **T** em IoT significa **Coisas** - dispositivos que interagem com o mundo físico ao seu redor, seja coletando dados de sensores ou fornecendo interações com o mundo real por meio de atuadores.
Dispositivos para produção ou uso comercial, como rastreadores de condicionamento físico para consumidores ou controladores de máquinas industriais, geralmente são feitos sob medida. Eles usam placas de circuito personalizadas, talvez até processadores personalizados, projetados para atender às necessidades de uma tarefa específica, seja ela pequena o suficiente para caber em um pulso ou robusta o suficiente para funcionar em um ambiente de fábrica de alta temperatura, alto estresse ou alta vibração.
Como um desenvolvedor que está aprendendo sobre IoT ou criando um protótipo de dispositivo, você precisará começar com um kit de desenvolvedor. Esses são dispositivos IoT de uso geral projetados para serem usados por desenvolvedores, geralmente com recursos que você não teria em um dispositivo de produção, como um conjunto de pinos externos para conectar sensores ou atuadores, hardware para suportar depuração ou recursos adicionais que adicionaria custos desnecessários ao fazer uma grande rodada de fabricação.
Esses kits de desenvolvedor geralmente se enquadram em duas categorias - microcontroladores e computadores de placa única. Eles serão apresentados aqui e entraremos em mais detalhes na próxima lição.
> 💁 Seu telefone também pode ser considerado um dispositivo IoT de uso geral, com sensores e atuadores integrados, com diferentes aplicativos que usam os sensores e atuadores de maneiras diferentes com diferentes serviços em nuvem. Você pode até encontrar alguns tutoriais de IoT que usam um aplicativo de telefone como um dispositivo de IoT.
### Microcontroladores
Um microcontrolador (também conhecido como MCU, abreviação de microcontroller unit) é um pequeno computador que consiste em:
🧠 Uma ou mais unidades de processamento central (CPUs) - o 'cérebro' do microcontrolador que executa seu programa
💾 Memória (RAM e memória de programa) - onde seu programa, dados e variáveis são armazenados
🔌 Conexões de entrada/saída (I/O) programáveis - para falar com periféricos externos (dispositivos conectados), como sensores e atuadores
Microcontroladores são tipicamente dispositivos de computação de baixo custo, com preços médios para aqueles usados em hardware customizado caindo para cerca de US$0,50, e alguns dispositivos tão baratos quanto US$0,03. Os kits de desenvolvedor podem começar em US$4, com custos aumentando à medida que você adiciona mais recursos. O [Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html), um kit de desenvolvedor de microcontrolador da [Seeed studios](https://www.seeedstudio.com) que tem sensores, atuadores, Wi-Fi e uma tela custam cerca de US$30.
![Um Wio Terminal](../../../../images/wio-terminal.png)
> 💁 Ao pesquisar por microcontroladores na Internet, tenha cuidado ao pesquisar pelo termo **MCU**, pois isso trará muitos resultados para o Universo Cinematográfico Marvel (Marvel Cinematic Universe), não microcontroladores.
Os microcontroladores são projetados para serem programados para realizar um número limitado de tarefas muito específicas, em vez de serem computadores de uso geral, como PCs ou Macs. Exceto em cenários muito específicos, você não pode conectar um monitor, teclado e mouse e usá-los para tarefas de propósito geral.
Os kits de desenvolvedor de microcontroladores geralmente vêm com sensores e atuadores adicionais a bordo. A maioria das placas terá um ou mais LEDs que você pode programar, junto com outros dispositivos, como plugues padrão para adicionar mais sensores ou atuadores usando ecossistemas de vários fabricantes ou sensores embutidos (geralmente os mais populares, como sensores de temperatura). Alguns microcontroladores possuem conectividade sem fio integrada, como Bluetooth ou WiFi, ou possuem microcontroladores adicionais na placa para adicionar essa conectividade.
> 💁 Microcontroladores geralmente são programados em C/C++.
### Computadores de placa única
Um computador de placa única é um pequeno dispositivo de computação que possui todos os elementos de um computador completo contidos em uma única placa pequena. Esses são dispositivos que têm especificações próximas a um desktop ou laptop PC ou Mac, executam um sistema operacional completo, mas são pequenos, usam menos energia e são substancialmente mais baratos.
![Um Raspberry Pi 4](../../../../images/raspberry-pi-4.jpg)
O Raspberry Pi é um dos computadores de placa única mais populares.
Como um microcontrolador, os computadores de placa única têm CPU, memória e pinos de entrada/saída, mas têm recursos adicionais, como um chip gráfico para permitir a conexão de monitores, saídas de áudio e portas USB para conectar teclados a mouses e outros dispositivos USB padrão como webcams ou armazenamento externo. Os programas são armazenados em cartões SD ou discos rígidos junto com um sistema operacional, em vez de um chip de memória embutido na placa.
> 🎓 Você pode pensar em um computador de placa única como uma versão menor e mais barata do PC ou Mac em que você está lendo isso, com a adição de pinos GPIO (entrada/saída de uso geral) para interagir com sensores e atuadores.
Os computadores de placa única são computadores completos, portanto, podem ser programados em qualquer linguagem. Os dispositivos IoT são normalmente programados em Python.
### Opções de hardware para o resto das lições
Todas as lições subsequentes incluem tarefas usando um dispositivo IoT para interagir com o mundo físico e se comunicar com a nuvem. Cada lição oferece suporte a 3 opções de dispositivo - Arduino (usando um Wio Terminal da Seeed Studios) ou um computador de placa única, seja ele um dispositivo físico (um Raspberry Pi 4) ou um computador de placa única virtual rodando em seu PC ou Mac.
Você pode ler sobre o hardware necessário para completar todas as tarefas no [guia do hardware](../../../../hardware.md).
> 💁 Você não precisa comprar nenhum hardware IoT para completar as atribuições, você pode fazer tudo usando um computador de placa única virtual.
A escolha do hardware depende de você - depende do que você tem disponível em casa ou na escola e de que linguagem de programação você conhece ou planeja aprender. Ambas as variantes de hardware usarão o mesmo ecossistema de sensores, portanto, se você começar por um caminho, poderá mudar para o outro sem ter que substituir a maior parte do kit. O computador de placa única virtual será o equivalente a aprender em um Raspberry Pi, com a maior parte do código transferível para o Pi se você eventualmente conseguir um dispositivo e sensores.
### Kit de desenvolvedor do Arduino
Se você estiver interessado em aprender o desenvolvimento de microcontroladores, poderá concluir as tarefas usando um dispositivo Arduino. Você precisará de um conhecimento básico de programação C/C++, pois as lições ensinarão apenas códigos relevantes para a estrutura do Arduino, os sensores e atuadores em uso e as bibliotecas que interagem com a nuvem.
As tarefas usarão o [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn) com a [extensão PlatformIO para desenvolvimento de microcontrolador](https://platformio.org). Você também pode usar o IDE do Arduino se tiver experiência com essa ferramenta, pois as instruções não serão fornecidas.
### Kit de desenvolvedor de computador de placa única
Se estiver interessado em aprender o desenvolvimento de IoT usando computadores de placa única, você pode concluir as tarefas usando um Raspberry Pi ou um dispositivo virtual em execução no seu PC ou Mac.
Você precisará de um conhecimento básico de programação Python, já que as lições ensinarão apenas códigos relevantes para os sensores e atuadores em uso e as bibliotecas que interagem com a nuvem.
> 💁 Se você quiser aprender a codificar em Python, confira as duas séries de vídeo a seguir:
>
> * [Python para iniciantes](https://channel9.msdn.com/Series/Intro-to-Python-Development?WT.mc_id=academic-17441-jabenn)
> * [Mais Python para iniciantes](https://channel9.msdn.com/Series/More-Python-for-Beginners?WT.mc_id=academic-7372-jabenn)
As tarefas usarão o [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn).
Se estiver usando um Raspberry Pi, você pode executar seu Pi usando a versão desktop completa do Raspberry Pi OS e fazer toda a codificação diretamente no Pi usando [a versão do VS Code para o Raspberry Pi OS](https://code.visualstudio.com/docs/setup/raspberry-pi?WT.mc_id=academic-17441-jabenn) ou executar seu Pi como um dispositivo sem cabeça e codificar a partir de seu PC ou Mac usando o VS Code com a [extensão SSH remota](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn) que permite que você se conecte ao seu Pi e edite, depure e execute o código como se você estivesse codificando nele diretamente.
Se você usar a opção de dispositivo virtual, codificará diretamente no seu computador. Em vez de acessar sensores e atuadores, você usará uma ferramenta para simular esse hardware, fornecendo valores de sensor que você pode definir e mostrando os resultados dos atuadores na tela.
## Configure seu dispositivo
Antes de começar a programar seu dispositivo IoT, você precisará fazer algumas configurações. Siga as instruções relevantes abaixo, dependendo de qual dispositivo você irá usar.
> 💁 Se você ainda não tem um dispositivo, consulte o [guia de hardware](../../../../hardware.md) para ajudar a decidir qual dispositivo você vai usar e qual hardware adicional você precisa comprar. Você não precisa comprar hardware, pois todos os projetos podem ser executados em hardware virtual.
Essas instruções incluem links para sites de terceiros dos criadores do hardware ou das ferramentas que você usará. Isso é para garantir que você esteja sempre usando as instruções mais atualizadas para as várias ferramentas e hardware.
Trabalhe com o guia relevante para configurar seu dispositivo e concluir um projeto 'Hello World'. Esta será a primeira etapa na criação de uma luz noturna IoT nas 4 lições desta parte de introdução.
* [Arduino - Wio Terminal](wio-terminal.pt.md)
* [Computador de placa única - Raspberry Pi](pi.pt.md)
* [Computador de placa única - Dispositivo virtual](virtual-device.pt.md)
✅ Você usará o VS Code para o Arduino e para computadores de placa única. Se você nunca usou isso antes, leia mais sobre isso no [site do VS Code](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn)
## Aplicações de IoT
A IoT cobre uma grande variedade de casos de uso, em alguns grupos amplos:
* IoT do Consumidor
* IoT Comercial
* IoT Industrial
* IoT para Infraestrutura
✅ Faça uma pequena pesquisa: para cada uma das áreas descritas abaixo, encontre um exemplo concreto que não seja fornecido no texto.
### IoT do Consumidor
A IoT do consumidor se refere a dispositivos IoT que os consumidores comprarão e usarão em casa. Alguns desses dispositivos são incrivelmente úteis, como alto-falantes inteligentes, sistemas de aquecimento inteligentes e aspiradores de pó robóticos. Outros são questionáveis em sua utilidade, como torneiras controladas por voz, o que significa que você não pode desligá-los, pois o controle de voz não pode ouvi-lo por causa do som de água corrente.
Os dispositivos IoT do consumidor estão capacitando as pessoas a realizar mais em seus arredores, especialmente 1 bilhão de pessoas com algum tipo de deficiência. Aspiradores de pó robóticos podem fornecer pisos limpos para pessoas com problemas de mobilidade que não podem aspirar a si mesmas, fornos controlados por voz permitem que pessoas com visão ou controle motor limitados aqueçam seus fornos apenas com a voz, monitores de saúde podem permitir que os pacientes monitorem suas condições crônicas com atualizações mais regulares e mais detalhadas. Esses dispositivos estão se tornando tão onipresentes que até crianças pequenas estão usando-os como parte de suas vidas diárias, por exemplo, alunos fazendo aulas virtuais durante a pandemia do COVID configurando cronômetros em dispositivos domésticos inteligentes para rastrear seus trabalhos escolares ou alarmes para lembrá-los das próximas reuniões da turma.
✅ Quais dispositivos IoT do consumidor você tem consigo ou em casa?
### IoT comercial
A IoT comercial cobre o uso da IoT no local de trabalho. Em um ambiente de escritório, pode haver sensores de ocupação e detectores de movimento para gerenciar a iluminação e o aquecimento para apenas manter as luzes e o aquecimento desligados quando não forem necessários, reduzindo o custo e as emissões de carbono. Em uma fábrica, os dispositivos IoT podem monitorar os riscos à segurança, como trabalhadores sem capacete ou ruído que atingiu níveis perigosos. No varejo, os dispositivos IoT podem medir a temperatura do armazenamento refrigerado, alertando o proprietário da loja se uma geladeira ou freezer está fora da faixa de temperatura exigida, ou podem monitorar os itens nas prateleiras para direcionar os funcionários para reabastecer os produtos que foram vendidos. A indústria de transporte está confiando cada vez mais na IoT para monitorar a localização dos veículos, rastrear a quilometragem na estrada para cobrança do usuário da estrada, monitorar as horas do motorista e interromper a conformidade ou notificar a equipe quando um veículo se aproxima de um depósito para se preparar para carga ou descarga
✅ Quais dispositivos IoT comerciais você tem em sua escola ou local de trabalho?
### IoT Industrial (IIoT)
IoT industrial, ou IIoT, é o uso de dispositivos IoT para controlar e gerenciar máquinas em grande escala. Isso cobre uma ampla gama de casos de uso, desde fábricas até agricultura digital.
As fábricas usam dispositivos IoT de muitas maneiras diferentes. A maquinaria pode ser monitorada com vários sensores para rastrear coisas como temperatura, vibração e velocidade de rotação. Esses dados podem então ser monitorados para permitir que a máquina seja parada se ficar fora de certas tolerâncias - ela fica muito quente e é desligada, por exemplo. Esses dados também podem ser coletados e analisados ao longo do tempo para fazer a manutenção preditiva, onde os modelos de IA examinam os dados que levam a uma falha e os usam para prever outras falhas antes que elas aconteçam.
A agricultura digital é importante se o planeta deseja alimentar a população crescente, especialmente para os 2 bilhões de pessoas em 500 milhões de famílias que sobrevivem da [agricultura de subsistência](https://pt.wikipedia.org/wiki/Agricultura_de_subsist%C3%AAncia). A agricultura digital pode variar de alguns sensores de um dígito de dólar a enormes configurações comerciais. Um agricultor pode começar monitorando as temperaturas e usar o [grau-dia de crescimento](https://wikipedia.org/wiki/Growing_degree-day) para prever quando uma safra estará pronta para a colheita. Eles podem conectar o monitoramento da umidade do solo a sistemas de rega automatizados para fornecer a suas plantas a quantidade necessária de água, mas não mais para garantir que suas safras não sequem sem desperdício de água. Os agricultores estão indo mais longe e usando drones, dados de satélite e IA para monitorar o crescimento da safra, doenças e qualidade do solo em grandes áreas de terras agrícolas.
✅ Que outros dispositivos IoT podem ajudar os agricultores?
### IoT para Infraestrutura
A IoT para infraestrutura monitora e controla a infraestrutura local e global que as pessoas usam todos os dias.
[Cidades inteligentes](https://pt.wikipedia.org/wiki/Cidade_inteligente) são áreas urbanas que usam dispositivos IoT para coletar dados sobre a cidade e usá-los para melhorar o funcionamento da mesma. Essas cidades geralmente são administradas com a colaboração entre governos locais, universidades e empresas locais, rastreando e gerenciando coisas que variam de transporte a estacionamento e poluição. Por exemplo, em Copenhague, Dinamarca, a poluição do ar é importante para os residentes locais, por isso é medida e os dados são usados para fornecer informações sobre as rotas de ciclismo e corrida mais limpas.
[Redes de energia inteligentes](https://pt.wikipedia.org/wiki/Rede_el%C3%A9trica_inteligente) permitem uma melhor análise da demanda de energia, reunindo dados de uso no nível de residências individuais. Esses dados podem orientar decisões em nível de país, incluindo onde construir novas usinas de energia, e em nível pessoal, dando aos usuários insights sobre quanta energia eles estão usando, quando estão usando, e até mesmo sugestões sobre como reduzir custos, como por exemplo carregar carros elétricos à noite.
✅ Se você pudesse adicionar dispositivos IoT para medir qualquer coisa onde você mora, o que seria?
## Exemplos de dispositivos IoT que você pode ter ao seu redor
Você ficaria surpreso com a quantidade de dispositivos IoT que tem ao seu redor. Estou escrevendo isso de casa e tenho os seguintes dispositivos conectados à Internet com recursos inteligentes, como controle de aplicativos, controle de voz ou a capacidade de enviar dados para mim através do meu telefone:
* Vários alto-falantes inteligentes
* Geladeira, lava-louças, forno e micro-ondas
* Monitor de eletricidade para painéis solares
* Plugues inteligentes
* Campainha de vídeo e câmeras de segurança
* Termostato inteligente com vários sensores inteligentes de ambiente
* Abridor de porta de garagem
* Sistemas de entretenimento doméstico e TVs controladas por voz
* Luzes
* Rastreadores de condicionamento físico e de saúde
Todos esses tipos de dispositivos possuem sensores e/ou atuadores e se comunicam com a Internet. Posso dizer pelo meu telefone se a porta da garagem está aberta e pedir ao meu alto-falante inteligente para fechá-la para mim. Posso até definir um temporizador para que, se ainda estiver aberta à noite, feche automaticamente. Quando minha campainha toca, posso ver no meu telefone quem está lá em qualquer lugar do mundo e falar com eles por meio de um alto-falante e microfone embutidos na campainha. Posso monitorar minha glicemia, frequência cardíaca e padrões de sono, procurando padrões nos dados para melhorar minha saúde. Posso controlar minhas luzes por meio da nuvem e ficar sentado no escuro quando minha conexão com a Internet cair.
---
## 🚀 Desafio
Liste o máximo de dispositivos IoT que puder em sua casa, escola ou local de trabalho - pode haver mais do que você pensa!
## Questionário pós-aula
[Questionário pós-aula](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/2)
## Revisão e autoestudo
Leia sobre os benefícios e as falhas dos projetos de IoT do consumidor. Verifique os sites de notícias para obter artigos sobre quando deu errado, como questões de privacidade, problemas de hardware ou problemas causados por falta de conectividade.
Alguns exemplos:
* Verifique a conta do Twitter **[Internet of Sh*t](https://twitter.com/internetofshit) ** *(aviso de palavrões)* para obter alguns bons exemplos de falhas com a IoT do consumidor.
* [c|net - Meu Apple Watch salvou minha vida: 5 pessoas compartilham suas histórias](https://www.cnet.com/news/apple-watch-lifesaving-health-features-read-5-peoples-stories/)
* [c|net - Técnico ADT se declara culpado de espionar imagens de câmeras de clientes por anos](https://www.cnet.com/news/adt-home-security-technician-pleads-guilty-to-spying-on- customer-camera-feeds-for-years/) *(aviso de gatilho - voyeurismo não consensual)*
## Tarefa
[Investigar um projeto IoT](assignment.pt.md)

@ -129,9 +129,9 @@ Raspberry Pi 是其中最流行的单板机。
按照相关的指南来设置你的设备并完成一个“Hello World”项目。我们将用4个课程创造一个物联网夜灯而这是第一步。
* [ArduinoWio Terminal](wio-terminal.md)
* [单板机Raspberry Pi](pi.md)
* [单板机:虚拟设备](virtual-device.md)
* [ArduinoWio Terminal](wio-terminal.zh-cn.md)
* [单板机Raspberry Pi](../pi.md)
* [单板机:虚拟设备](virtual-device.zh-cn.md)
您将使用 VS Code在Arduino 和单板机上编程。如果您以前从未使用过它,请在 [VS Code 站点](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn)上阅读更多相关信息。

@ -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 |

@ -8,6 +8,6 @@
## 评分表
| 条件 | 优秀 | 一般 | 需改进 |
| 标准 | 优秀 | 一般 | 需改进 |
| -------- | --------- | -------- | ----------------- |
| 解释项目的好处与坏处 | 把项目的好处与坏处解释得很清楚 | 简要地解释项目的好处与坏处 | 没有解释项目的好处与坏处 |

@ -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.
![Um Raspberry Pi 4](../../../../images/raspberry-pi-4.jpg)
## 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.
![Ajustando o Grove Hat](../../../../images/pi-grove-hat-fitting.gif)
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)*
![O Raspberry Pi Imager com o Raspberry Pi OS Lite selecionado](../../../../images/raspberry-pi-imager.png)
> 💁 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**
![A caixa de diálogo de abertura do VS Code mostrando a pasta nightlight](../../../../images/vscode-open-nightlight-remote.png)
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,244 @@
# 树莓派(Raspberry Pi)
[树莓派](https://raspberrypi.org)是一个单板机,你可以用大量的设备和生态系统来给树莓派加上传感器和执行器,在这些课程中我们使用一个叫做[Grove](https://www.seeedstudio.com/category/Grove-c-1003.html)的硬件生态系统你将会用Python来给你的Pi编程和读取传感器。
![一个树莓派 4](../../../../images/raspberry-pi-4.jpg)
## 设置
如果你要使用树莓派来作为你的物联网硬件,那么你有两个选择来完成这些课程 - 直接在树莓派上编码,或者从你的计算机远程连接到无界面的树莓派上来编码。
在你开始之前你还需要把Grove基础扩展板连接到你的Pi上。
### 任务 - 设置
安装Grove基础扩展板并配置好你的树莓派
1. 安装Grove基础扩展板到你的树莓派扩展板上的插孔与Pi上的GPIO引脚一一对应沿着引脚一路滑下去来压住底部扩展板会在上面盖住树莓派。
![安装grove扩展板](../../../../images/pi-grove-hat-fitting.gif)
2. 决定你要如何来编码你的树莓派,并直接跳到下面相关的部分:
* [在树莓派上直接编码](#在树莓派上直接编码)
* [远程连接来编码树莓派](#远程连接来编码树莓派)
### 在树莓派上直接编码
如果你想要直接在树莓派上编码你可以使用Raspberry Pi OS的桌面版本并安装你需要的所有工具。
#### 任务 - 在树莓派上直接编码
配置树莓派的开发环境。
1. 跟着[树莓派配置指南](https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up)的步骤来配置你的树莓派,给它连上一个键盘/鼠标/显示器把它接入你的Wi-Fi或者以太网络然后更新软件。你要安装的是**Raspberry Pi OS (32 bit)**用Raspberry Pi Imager来烧写SD卡的时候一般都会推荐这个操作系统。
想要使用Grove传感器和执行器来给树莓派编程的话你需要安装一个编辑器来编写设备代码和各种用来与Grove硬件交互的函数库、工具。
1. 当你的树莓派重启后,点击上方菜单栏的**Terminal** 图标或者选择*Menu -> Accessories -> Terminal*来启动终端。
1. 运行下面的命令来确保操作系统和已安装的软件都是最新的:
```sh
sudo apt update && sudo apt full-upgrade --yes
```
1. 运行下面的命令来安装所有Grove硬件需要的函数库
```sh
curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s -
```
Python一个强大的特性是可以安装[pip包](https://pypi.org) - 这些都是其他人编写了发布到网上的软件包用一个命令你就可以把一个pip包安装到你的计算机上然后在代码里面使用这个软件包了这个Grove安装脚本会安装你用Python来操控Grove硬件时将会用到的pip软件包。
1. 用菜单点击或者运行下面的命令来重启树莓派:
```sh
sudo reboot
```
1. 树莓派重启后,重新打开终端并运行下面的命令来安装[Visual Studio Code (VS Code)](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) - 你会使用这个编辑器来编写你的设备Python代码。
```sh
sudo apt install code
```
安装完成后上面的菜单栏就会出现VS Code了。
> 💁 如果你有更喜欢的工具你也可以自由使用任意的Python IDE或者编辑器来学习课程但是课程中会基于VS Code来给出指示。
1. 安装Pylance这是给VS Code提供Python语言支持的扩展插件可以参考这个[Pylance扩展文档](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn)中的指示在VS Code中安装这个插件。
### 远程连接来编码树莓派
除了直接在树莓派上编码,它也可以无界面运行,不连接键盘/鼠标/显示器使用Visual Studio Code从你的计算机来配置和编码树莓派。
#### 配置树莓派操作系统
树莓派操作系统需要被安装在一张SD卡上才能远程编码。
##### 任务 - 配置树莓派操作系统
配置无界面树莓派系统
1. 从[树莓派操作系统软件页面](https://www.raspberrypi.org/software/)下载**Raspberry Pi Imager**并安装
1. 在你的计算机上插入一张SD卡必要时需要使用一个转换器
1. 启动Raspberry Pi Imager
1. 在Raspberry Pi Imager点击**CHOOSE OS**,选择*Raspberry Pi OS (Other)*,然后选择*Raspberry Pi OS Lite (32-bit)*
![Raspberry Pi Imager选择Raspberry Pi OS Lite](../../../../images/raspberry-pi-imager.png)
> 💁 Raspberry Pi OS Lite是一个没有桌面UI或者基于UI的工具的操作系统版本这些对于无界面树莓派来说都是不需要的而且这样可以安装更小的空间、启动速度也更快。
1. 点击**CHOOSE STORAGE** 按钮然后选择你的SD卡
1. 按下`Ctrl+Shift+X`来启动**Advanced Options**这些选项允许你在烧写到SD卡之前对Raspberry Pi OS进行一些预配置。
1. 勾选**Enable SSH**,然后给用户`pi`设置一个密码,这是你等会用来登录的密码。
2. 如果你打算通过WiFi连接到树莓派那么需要勾选**Configure WiFi**然后输入你WiFi的SSID和密码并选择你的国家码。如果打算使用以太网线缆来连接那就不需要做这一步了只需要确保树莓派和你的计算机连接的是同一个网络就行。
3. 勾选**Set locale settings**,设置你的国家和时区。
4. 点击 **SAVE** 按钮
2. 点击**WRITE**按钮把OS烧写到SD卡上如果你使用的是MacOS你会被要求输入你的密码因为底层的写磁盘镜像的工具需要访问权限。
操作系统会被烧写到SD卡上完成之后SD卡会被弹出并且你会收到通知。从你的计算机拔出SD卡再把它插到树莓派上并上电启动。
#### 连接到树莓派
接下来的一个步骤是远程连接树莓派,你可以使用`ssh`这个工具在macOS、Linux和最近几个版本的Windows上都可以直接使用。
##### 任务 - 连接到树莓派
远程连接树莓派。
1. 启动一个终端或者命令提示符,输入下面的命令来连接树莓派:
```sh
ssh pi@raspberrypi.local
```
如果你是在一个老版本没有安装`ssh`的Windows上可以使用OpenSSH你可以在[OpenSSH安装文档](https://docs.microsoft.com//windows-server/administration/openssh/openssh_install_firstuse?WT.mc_id=academic-17441-jabenn)里找到安装指南。
2. 这应该会连上你的树莓派,并且会请求密码。
通过`<hostname>.local`来寻找你的网络中的计算机是Linux和Windows最近才加入的功能如果你是在使用Linux或者Windows过程中遇到一些Hostname无法找到的问题你会需要安装一些额外的软件来启用ZeroConf网络也被Apple称为Bonjour
1. 如果你在使用Linux用下面的命令来安装Avahi
```sh
sudo apt-get install avahi-daemon
```
2. 如果你在使用Windows启用ZeroConf最简单的方法是安装[Bonjour Print Services for Windows](http://support.apple.com/kb/DL999),你也可以安装[iTunes for Windows](https://www.apple.com/itunes/download/)来获取更新版本的组件(无法独立下载安装)。
> 💁 如果你无法通过`raspberrypi.local`连接那么你也可以使用你的树莓派的IP地址参考[树莓派IP地址文档](https://www.raspberrypi.org/documentation/remote-access/ip-address.md)上大量的获取IP地址的方法。
3. 输入你在Raspberry Pi Imager高级选项中输入的密码
#### 在树莓派上配置软件
当你连接上树莓派之后你需要确保这个操作系统是最新的并且安装各类用于和Grove硬件交互的函数库和工具。
##### 任务 - 在树莓派上配置软件
配置已安装的树莓派软件并安装Grove的函数库。
1. 从你的`ssh`会话中,运行下面的命令来更新并重启树莓派:
```sh
sudo apt update && sudo apt full-upgrade --yes && sudo reboot
```
树莓派会被更新并重启,这个`ssh`会话在树莓派重启的时候会中断等待30秒后重连就行。
2. 从重连的`ssh`会话中运行下面的命令来安装Grove硬件需要的函数库
```sh
curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s -
```
Python一个强大的特性是可以安装[pip包](https://pypi.org) - 这些都是其他人编写了发布到网上的软件包用一个命令你就可以把一个pip包安装到你的计算机上然后在代码里面使用这个软件包了这个Grove安装脚本会安装你用Python来操控Grove硬件时将会用到的pip软件包。
3. 用下面的命令来重启树莓派:
```sh
sudo reboot
```
树莓派重启的时候`ssh`会话会中断,不要再重新连接。
#### 配置VS Code的远程连接
树莓派配置完以后你可以从你的计算机通过Visual Studio Code (VS Code)来连接到它 - 这是一个你将要用Python来写设备代码的免费开发者编辑器。
##### 任务 - 配置VS Code的远程连接
安装需要的软件并远程连接到你的树莓派。
1. 跟着[VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn)在你的计算机上安装 VS Code
2. 根据[VS Code远程SSH开发文档](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn)的步骤来安装需要的组件
3. 根据相同的指示连接VS Code到树莓派
4. 连接上之后,根据[管理扩展程序](https://code.visualstudio.com/docs/remote/ssh#_managing-extensions?WT.mc_id=academic-17441-jabenn)的指示来远程安装[Pylance扩展程序](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn)到树莓派上
## Hello world
开始一门新的编程语言或者技术时创建一个'Hello World'的程序是一个传统 - 一个输出类似于`"Hello World"`文本的小程序,用来证明所有的工具都已经配置正确了。
树莓派的这个Hello World程序可以确保你已经把Python和Visual Studio Code正确安装了。
这个程序会在一个叫`nightlight`的文件夹里面,他会在这个任务后面部分的不同代码里面再次使用,用来构建夜灯程序。
### 任务 - hello world
创建Hello World应用。
1. 直接在树莓派上启动VS Code或者在你的计算机上用远程SSH扩展来连接到树莓派。
2. 选择 *Terminal -> New Terminal* 或者按下`` CTRL+` `` 来启动VS Code这会打开在`pi`的home目录。
3. 运行下面的命令来为你的代码创建一个目录,并在目录里创建一个叫`app.py`的Python文件
```sh
mkdir nightlight
cd nightlight
touch app.py
```
4. 在VS Code中点击*File -> Open...*并选择*nightlight*文件夹和 **OK** 来打开这个文件夹
![VS Code打开nightlight文件夹的对话框](../../../../images/vscode-open-nightlight-remote.png)
5. 从VS Code窗口打开 `app.py` 文件并增加下面的代码:
```python
print('Hello World!')
```
`print`函数会在终端打印任何传递给它的东西。
6. 从VS Code的终端运行下面的命令来运行你的Python应用
```sh
python3 app.py
```
> 💁 你需要显式的调用`python3`来运行这个代码以防你除了Python 3最新版本还安装了Python 2如果你安装了Python 2那么调用`python`命令时会使用Python 2 而不是Python 3
终端里会出现下面的输出:
```output
pi@raspberrypi:~/nightlight $ python3 app.py
Hello World!
```
> 💁 你可以在[code/pi](../code/pi) 文件夹里找到这个代码
😀 你的'Hello World'程序成功了!

@ -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:
![VS Code mostrando o ambiente virtual selecionado](../../../../images/vscode-virtual-env.png)
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**:
![Botão Kill the active terminal instance do VS Code](../../../../images/vscode-kill-terminal.png)
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:
![O aplicativo Counter Fit em execução em um navegador](../../../../images/counterfit-first-run.png)
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.
![Botão Create a new integrated terminal do VS Code](../../../../images/vscode-new-terminal.png)
1. Neste novo terminal, execute o arquivo `app.py` como antes. O status do CounterFit mudará para **Conectado** e o LED acenderá.
![CounterFit mostrando como conectado](../../../../images/counterfit-connected.png)
> 💁 Você pode encontrar este código na pasta [code/virtual-device](../code/virtual-device).
😀 Sua conexão com o hardware foi um sucesso!

@ -1,38 +1,38 @@
# 虚拟单板机
除了买一个 IoT 设备、传感器和执行器,你也可以用你的电脑来模拟 IoT 硬件。[CounterFit 项目](https://github.com/CounterFit-IoT/CounterFit) 让你在自己的电脑上运行模拟 IoT 硬件(如传感器和执行器)的应用,以及用本地 Python 代码(就像你能在物质 Raspberry Pi 上写的代码)访问传感器和执行器
除了买一个 IoT 设备、传感器和执行器,你也可以用你的电脑来模拟 IoT 硬件。[CounterFit 项目](https://github.com/CounterFit-IoT/CounterFit) 让你在自己的电脑上运行模拟 IoT 硬件(如传感器和执行器)的应用,并从本地Python代码访问传感器和执行器代码的编写方式与使用Raspberry Pi物理硬件相同
## 设置
用 CounterFit 前,你必须在你的电脑上安装一些免费的软件。
使用 CounterFit 前,你必须在你的电脑上安装一些免费的软件。
### 任务
安装需要的软件。
1. 安装 Python。 在 [Python 的下载页](https://www.python.org/downloads/) 找安装最新 Python 版本的指示。
1. 安装 Python。 在 [Python 的下载页](https://www.python.org/downloads/) 找到最新版本Python的安装指示。
1. 安装 Visual Studio Code (VS Code)。 这是你将用来写虚拟设备的 Python代码的代码编辑器。在 [VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) 找安装VS Code 的指示。
1. 安装 Visual Studio Code (VS Code)。 这是你将用来写虚拟设备的 Python代码的代码编辑器。在 [VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) 找到VS Code的安装指示。
> 💁 如果你对其它平台比较熟悉,你当然可以用你较喜欢的 Python IDE 或 代码编辑器,但注意这个课程的指示将根据 VS Code。
> 💁 如果你对其它平台比较熟悉,你当然可以用你较喜欢的 Python IDE 或 代码编辑器但注意这个课程将根据VS Code提供说明
1. 安装 VS Code 的 Pylance 扩展。 这个 VS Code 扩展提供 Python 语言支持。在 [Pylance 扩展文档](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) 找安装扩展的指示。
我们将在合适的时间在作业指示中提供安装及设置 CounterFit 的程序,因为我们需要在每个项目中安装它。
我们将在日后的作业中提供安装及设置 CounterFit 的说明,因为我们需要在每个项目中安装它。
## Hello world你好世界
第一次用新的编程语言或通常以创建一个“Hello World”应用开始——一个输出像`"Hello World"`的字的小小应用,为了确保所有的工具被设置好。
第一次用新的编程语言或技通常以创建一个“Hello World”应用开始——一个输出类似`"Hello World"`文本的小小应用,以确保所有的工具被设置好。
这个虚拟 IoT 硬件的“Hello World”应用将确保你有安装好 Python 与 Visual Studio Code。它也将把虚拟 IoT 传感器和执行器连接到 CounterFit。它不会用到任何硬件他只会以连接来证明每个部分运作良好。
这个虚拟 IoT 硬件的“Hello World”应用将确保你安装好 Python 与 Visual Studio Code。它也会连接到 CounterFit以获取虚拟 IoT 传感器和执行器。它不会用到任何硬件,它只会以正确连接来证明每个部分运作良好。
这个应用正在被称为`夜灯`的文件夹中,而且我们等一下会跟着不同的代码再次利用它,为了在作业当中创建夜灯应用。
这个应用放在名为`nightlight`的文件夹中,稍后将和其他代码结合,以构建夜灯应用。
### 配置 Python 虚拟环境
Python 的其中一个强大功能是安装 [pip 软件包](https://pypi.org)的能力;它们是别人写并在网上上载的代码软件包。只要用着一个命令,你就可以在你的电脑上安装一个 pip 软件包,并在你的代码中使用它。你将用 pip 安装一个软件包,把它用来跟 CounterFit 沟通。
Python 的强大功能之一能够安装 [pip 软件包](https://pypi.org)这些是由其他人编写并发布到互联网上的代码包。只需一条命令就可以在你的电脑上安装pip 软件包,并在你的代码中使用它。你将用 pip 安装一个软件包,来与CounterFit 沟通。
默认情况下,当你安装一个软件包,你的电脑哪里都可以访问它,而那可以造成关于软件包版本的问题,例如:当你为新应用安装软件包的新版本,依靠旧版本的另一个应用就有可能出些状况。为了以免这种事发生,你可以用一个 [Python 虚拟环境](https://docs.python.org/3/library/venv.html),在一个专用文件夹中的 Python那当你安装 pip 软件包它们只会在那个文件夹中。
默认情况下,当你安装软件包时,在计算机的任何位置都是可用的,而这可能会造成软件包版本问题,例如:当你为新应用安装软件包的新版本,依靠旧版本的另一应用就有可能出现状况。为了避免这种问题,你可以使用 [Python 虚拟环境](https://docs.python.org/3/library/venv.html),本质上是一个专用文件夹中的 Python 副本,当你安装 pip 软件包时,它们只会安装到那个文件夹中。
#### 任务:配置一个 Python 虚拟环境
@ -82,7 +82,7 @@ Python 的其中一个强大功能是安装 [pip 软件包](https://pypi.org)的
> 💁 你的 Python 版本有可能不一样,但只要版本是 3.6 或以上就没事。不然,请删除这个文件夹,并安装较新的 Python 版本,再试一试。
5. 运行以下的命令来安装CounterFit 的 pip 软件包。这些软件包包括主要的 CounterFit 应用以及 Grove 硬件的垫片。这些垫片让你就像用来自 Grove 生态系统的物传感器和执行器一样写代码,但把它连接到虚拟 IoT 设备。
5. 运行以下的命令来安装CounterFit 软件包。这些软件包包括主要的 CounterFit 应用以及 Grove 硬件的[垫片](https://zh.wikipedia.org/wiki/%E5%9E%AB%E7%89%87_(%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1))。这些垫片让你就像用来自 Grove 生态系统的物传感器和执行器一样写代码,但把它连接到虚拟 IoT 设备。
```sh
pip install CounterFit
@ -92,11 +92,11 @@ Python 的其中一个强大功能是安装 [pip 软件包](https://pypi.org)的
这些 pip 软件包只会在虚拟环境中安装,而你无法在虚拟环境外访问它。
### 写代码
### 写代码
Python 虚拟环境被准备好,你就能为 “Hello World” 应用写代码。
一旦Python 虚拟环境被准备好,你就能为 “Hello World” 应用写代码。
#### 任务:写代码
#### 任务:写代码
创建一个 Python 应用在控制台上打印`"Hello World"` 输出。
@ -145,7 +145,7 @@ Python 虚拟环境一被准备好,你就能为 “Hello World” 应用写代
(.venv) ➜ nightlight
```
6. 从 VS Code explorer 打开 `app.py` 文件,加以下的代码:
6. 从 VS Code explorer 打开 `app.py` 文件,并添加以下的代码:
```python
print('Hello World!')
@ -170,7 +170,7 @@ Python 虚拟环境一被准备好,你就能为 “Hello World” 应用写代
### 连接“硬件”
你的第二 “Hello World” 步将是运行 CounterFit 应用,再连接你的代码。这是把一些 IoT 硬件插入开发者套件的虚拟相等
你的第二个“Hello World”步骤是运行 CounterFit 应用并连接你的代码。这相当于把一些 IoT 硬件插入开发者套件
#### 任务:连接“硬件”
@ -182,7 +182,7 @@ Python 虚拟环境一被准备好,你就能为 “Hello World” 应用写代
应用将开始运行以及在你的网页浏览器打开:
![CounterFit 应用在网页浏览器运行](../../../images/counterfit-first-run.png)
![CounterFit 应用在网页浏览器运行](../../../../images/counterfit-first-run.png)
他会有个 *Disconnected*(断开连接)的标记,右上角的 LED 也会关着。
@ -199,12 +199,12 @@ Python 虚拟环境一被准备好,你就能为 “Hello World” 应用写代
3. 你必须选择 **Create a new integrated terminal** 按钮来启动一个新 VS Code 终端。这是因为 CounterFit 应用正在当前终端运行着。
![VS Code Create a new integrated terminal 按钮](../../../images/vscode-new-terminal.png)
![VS Code Create a new integrated terminal 按钮](../../../../images/vscode-new-terminal.png)
4. 在这个新终端,像以前一样运行`app.py` 文件。CounterFit 的状态将改成 **Connected** 连接LED也会开着。
![CounterFit 被连接了](../../../images/counterfit-connected.png)
![CounterFit 被连接了](../../../../images/counterfit-connected.png)
> 💁 你可以在 [code/virtual-device](code/virtual-device) 文件夹找到这个代码。
> 💁 你可以在 [code/virtual-device](../code/virtual-device) 文件夹找到这个代码。
😀 你的硬件连接成功了!

@ -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).
![Um Wio Terminal da Seeed studios](../../../../images/wio-terminal.png)
## 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:
![A opção de menu Platform IO](../../../../images/vscode-platformio-menu.png)
Selecione este item de menu e, em seguida, selecione *PIO Home -> Open*
![A opção de Abrir do Platform IO](../../../../images/vscode-platformio-home-open.png)
1. Na tela de boas-vindas, selecione o botão **+ New Project**
![O botão de Novo Projeto](../../../../images/vscode-platformio-welcome-new-button.png)
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**
![O assistente de projeto concluído](../../../../images/vscode-platformio-nightlight-project-wizard.png)
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*
![A opção de upload do PlatformIO na paleta de comando](../../../../images/vscode-platformio-upload-command-palette.png)
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*
![A opção PlatformIO Serial Monitor na paleta de comandos](../../../../images/vscode-platformio-serial-monitor-command-palette.png)
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!

@ -55,18 +55,27 @@ Configure a Python virtual environment and install the pip packages for CounterF
1. Activate the virtual environment:
* On Windows run:
* On Windows:
* If you are using the Command Prompt, or the Command Prompt through Windows Terminal, run:
```cmd
.venv\Scripts\activate.bat
```
* If you are using PowerShell, run:
```powershell
.\.venv\Scripts\Activate.ps1
```
* On macOS or Linux, run:
```cmd
source ./.venv/bin/activate
```
> 💁 These commands should be run from the same location you ran the command to create the virtual environment. You will never need to navigate into the `.venv` folder, you should always run the activate command and any commands to install packages or run code from the folder you were in when you created the virtual environment.
1. Once the virtual environment has been activated, the default `python` command will run the version of Python that was used to create the virtual environment. Run the following to get the version:
```sh

@ -4,6 +4,14 @@
> Sketchnote by [Nitya Narasimhan](https://github.com/nitya). Click the image for a larger version.
This lesson was taught as part of the [Hello IoT series](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) from the [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn). The lesson was taught as 2 videos - a 1 hour lesson, and a 1 hour office hour diving deeper into parts of the lesson and answering questions.
[![Lesson 2: A deeper dive into IoT](https://img.youtube.com/vi/t0SySWw3z9M/0.jpg)](https://youtu.be/t0SySWw3z9M)
[![Lesson 2: A deeper dive into IoT - Office hours](https://img.youtube.com/vi/tTZYf9EST1E/0.jpg)](https://youtu.be/tTZYf9EST1E)
> 🎥 Click the images above to watch the videos
## Pre-lecture quiz
[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/3)
@ -116,7 +124,7 @@ RAM is the memory used by the program to run, containing variables allocated by
> 🎓 RAM is used to run your program and is reset when there is no power
Like with the CPU, the memory on a microcontroller is orders of magnitude smaller than a PC or Mac. A typical PC might have 8 Gigabytes (GB) of RAM, or 8,000,0000,000 bytes, with each byte enough space to store a single letter or a number from 0-255. A microcontroller would have only Kilobytes (KB) of RAM, with a kilobyte being 1,000 bytes. The Wio terminal mentioned above has 192KB of RAM, or 192,000 bytes - more than 40,000 times less than an average PC!
Like with the CPU, the memory on a microcontroller is orders of magnitude smaller than a PC or Mac. A typical PC might have 8 Gigabytes (GB) of RAM, or 8,000,000,000 bytes, with each byte enough space to store a single letter or a number from 0-255. A microcontroller would have only Kilobytes (KB) of RAM, with a kilobyte being 1,000 bytes. The Wio terminal mentioned above has 192KB of RAM, or 192,000 bytes - more than 40,000 times less than an average PC!
The diagram below shows the relative size difference between 192KB and 8GB - the small dot in the center represents 192KB.

@ -0,0 +1,12 @@
# 比较与对比微控制器和单板计算机
## 指示
这个课程涵盖了微控制器和单板计算机。创建一个表格来比较与对比它们,并记下至少两个微控制器和单板计算机相比之下选择微控制器的原因,还有至少两个微控制器和单板计算机相比之下选择单板计算机的原因。
## 评分表
| 标准 | 优秀 | 一般 | 需改进 |
| -------- | --------- | -------- | ----------------- |
| 创建一个表格来比较微控制器和单板计算机 | 创建一个有多项的列表来正确地比较和对比它们 | 创建一个只有几项的列表 | 只能想出一项,或一项也没有来比较和对比它们 |
| 两个相比之下选择某一个的原因 | 能够为微控制器提出至少两个原因,而且为单板计算机提出至少两个原因 | 只能为微控制器提出一两个原因,而且为单板计算机提出一两个原因 | 无法为微控制器或单板计算机提出至少一个原因 |

@ -4,6 +4,14 @@
> Sketchnote by [Nitya Narasimhan](https://github.com/nitya). Click the image for a larger version.
This lesson was taught as part of the [Hello IoT series](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) from the [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn). The lesson was taught as 2 videos - a 1 hour lesson, and a 1 hour office hour diving deeper into parts of the lesson and answering questions.
[![Lesson 3: Interact with the Physical World with Sensors and Actuators](https://img.youtube.com/vi/Lqalu1v6aF4/0.jpg)](https://youtu.be/Lqalu1v6aF4)
[![Lesson 3: Interact with the Physical World with Sensors and Actuators - Office hours](https://img.youtube.com/vi/qR3ekcMlLWA/0.jpg)](https://youtu.be/qR3ekcMlLWA)
> 🎥 Click the images above to watch the videos
## Pre-lecture quiz
[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/5)

@ -0,0 +1,115 @@
# 开发一个夜灯 - 树莓派
在这个部分的课程中你会把一个LED加到树莓派上并使用它来创建一个夜灯。
## 硬件
现在夜灯需要一个执行器。
这个执行器是**LED**,一个[发光二极管](https://wikipedia.org/wiki/Light-emitting_diode)当电流通过它时会发光。这是一个有两个打开或者关闭状态的数字执行器发送一个1值把灯打开发送0把灯关闭。这个LED是一个外部Grove执行器而且需要被连接到树莓派上的Grove基础扩展板。
这个夜灯的逻辑用伪代码表示是:
```output
检查光照等级。
如果光照小于300
打开LED
否则
关闭LED
```
### 连接LED
Grove LED 作为一个模块出现以及一系列可供你选择颜色的LED。
#### 任务 - 连接LED
连接LED。
![一个grove LED](../../../../images/grove-led.png)
1. 选择你最喜欢的LED然后把引脚插到LED模块的两个洞里面。
LED是发光二极管而且二极管是只允许电流单个方向通过的电子设备。这意味LED需要被连接在正确的方向不然就不会工作。
LED引脚中的一个是正极引脚另一个是负极引脚。LED不是完全的圆形而且在一边是有些平的。这略平的一边是负极引脚。当你连接LED到这个模块的时候需要确保圆形这边的引脚是连接到模块上外边标着 **+** 的插孔,而扁平的这边是连接到靠近模块中间的插孔。
1. LED模块有一个允许你控制亮度的旋转按钮用一个小十字螺丝起子逆时针旋转它拧到头来完全打开它。
1. 把Grove线缆的一端插到LED模块的插孔中这个只能从一个方向插入。
1. 在树莓派断电的情况下把Grove线缆的另一端连接到树莓派上插着的Grove基础扩展板标着 **D5** 的数字插孔。这个插孔在靠近GPIO引脚的一排左数第二个。
![连接到D5插孔的Grove LED](../../../../images/pi-led.png)
## 编写夜灯程序
现在夜灯可以用Grove光照传感器和Grove LED来编码了。
### 任务 - 编写夜灯程序
编写夜灯程序
1. 打开树莓派并等待启动完成。
1. 直接在树莓派上或者通过远程SSH扩展打开你在这个作业上一部分创建的VS Code中的夜灯项目。
1. 把下面的代码加到`app.py`文件中来导入一个需要的函数库。这一行需要加在文件顶部,在其他`import`代码行下面。
```python
from grove.grove_led import GroveLed
```
`from grove.grove_led import GroveLed`语句从Grove Python函数库中导入了`GroveLED`。这个函数库中有和Grove LED交互的代码。
1. 把下面的代码加到`light_sensor`声明之后来创建一个管理LED的类的实例
```python
led = GroveLed(5)
```
`led = GroveLed(5)`这一行创建了一个连接到 **D5** 引脚的`GroveLED`类的实例,**D5** 也就是LED连接的那个数字Grove引脚。
> 💁 所有的插孔都有唯一的引脚号引脚0、2、4和6是模拟引脚引脚5、16、18、22、24和26是数字引脚。
1. 在`while`循环中增加一个判断,在`time.sleep`之前来检查光照等级并控制LED打开或者关闭
```python
if light < 300:
led.on()
else:
led.off()
```
这块代码检查了`light`的值如果小于300就调用`GroveLED`类的`on`方法来发送一个数字值1到LED把它点亮。如果`light`值大于或等于300就调用`off`方法发送一个数字值0给LED把它关闭。
> 💁 这段代码需要放到while循环里面缩进到和`print('Light level:', light)`行一个水平。
> 💁 当发送数字值到执行器的时候0值就是0v1值就是设备的最大电压。对于插着Grove传感器和执行器的树莓派而言1的电压就是3.3V。
1. 从VS Code终端运行下面的命令来运行你的Python应用
```sh
python3 app.py
```
光照值在终端里输出。
```output
pi@raspberrypi:~/nightlight $ python3 app.py
Light level: 634
Light level: 634
Light level: 634
Light level: 230
Light level: 104
Light level: 290
```
1. 遮挡和揭开光照传感器会观察到光照等级等于300或更小时LED会点亮如果光照等级比300大LED就会关闭。
> 💁 如果LED没有点亮确保它是正确方向连接的而且旋转按钮是设置成全开的。
![连接到树莓派的LED随着光照等级改变点亮和关闭](../../../../images/pi-running-assignment-1-1.gif)
> 💁 你可以在[code-actuator/pi](../code-actuator/pi)文件夹里找到这份代码。
😀 你的夜灯程序就成功了!

@ -0,0 +1,96 @@
# 开发一个夜灯 - 树莓派
在这个部分的课程中,你会把一个光照传感器加到你的树莓派上。
## 硬件
这节课程的传感器是使用[光电二极管](https://wikipedia.org/wiki/Photodiode)来把光照转化为电子信号的光照传感器。这是一个发送从0到1,000整数值的模拟传感器表示光照值的相对量而不对应任何比如[勒克斯lux](https://wikipedia.org/wiki/Lux)的标准计量单位。
这个光照传感器是一个外部Grove传感器需要被连接到树莓派上的Grove基础扩展板。
### 连接光照传感器
用来检测光照等级的Grove光照传感器需要被连接到树莓派上。
#### 任务 - 连接光照传感器
连接光照传感器
![一个 grove 光照传感器](../../../../images/grove-light-sensor.png)
1. 把Grove线缆的一端插到光照传感器模块的插孔中这个只能从一个方向插入。
1. 在树莓派断电的情况下把Grove线缆的另一端连接到树莓派上插着的Grove基础扩展板标着 **A0** 的模拟插孔。这个插孔在靠近GPIO引脚的一排右数第二个。
![插在A0插孔的grove光照传感器](../../../../images/pi-light-sensor.png)
## 编写光照传感器程序
现在设备可以用Grove光照传感器来编码了。
### 任务 - 编写光照传感器程序
编写设备程序。
1. 打开树莓派并等待启动完成。
1. 直接在树莓派上或者通过远程SSH扩展打开你在这个作业上一部分创建的VS Code中的夜灯项目。
1. 打开`app.py`文件并删除里面的所有代码。
1. 把下面的代码加到`app.py`文件中来导入一些需要的函数库:
```python
import time
from grove.grove_light_sensor_v1_2 import GroveLightSensor
```
`import time`语句导入了`time`模块,在这个作业的后面会用到这个模块。
`from grove.grove_light_sensor_v1_2 import GroveLightSensor`语句从Grove Python函数库导入了 `GroveLightSensor`。这个函数库里有和Grove光照传感器交互的代码在设置树莓派的时候就已经全局安装了。
1. 在上面代码的后面增加下面的代码来创建一个管理光照传感器的类的实例:
```python
light_sensor = GroveLightSensor(0)
```
`light_sensor = GroveLightSensor(0)`这一行创建了一个连接到 **A0** 引脚的`GroveLightSensor`类的实例,**A0** 也就是光照传感器连接的那个引脚。
1. 在上面的代码后面增加一个无限循环代码来获取光照传感器数值并打印到终端:
```python
while True:
light = light_sensor.light
print('Light level:', light)
```
使用`GroveLightSensor`类的`light`属性可以来获取 0-1023 的当前光照等级值,这个属性从引脚读取模拟量,然后这个值会被打印到终端。
1. 在`loop`的结尾增加一个1秒的短暂休眠因为光照等级不需要一直不断地读取。一个休眠可以减少设备的能源消耗。
```python
time.sleep(1)
```
1. 从VS Code终端运行下面的命令来运行你的Python应用
```sh
python3 app.py
```
光照等级会在终端输出,遮挡和揭开光照传感器,输出的值也会相应变化:
```output
pi@raspberrypi:~/nightlight $ python3 app.py
Light level: 634
Light level: 634
Light level: 634
Light level: 230
Light level: 104
Light level: 290
```
> 💁 你可以在[code-sensor/pi](../code-sensor/pi)文件夹找到这份代码。
😀 给你的夜灯增加一个传感器程序就成功了!

@ -4,6 +4,14 @@
> Sketchnote by [Nitya Narasimhan](https://github.com/nitya). Click the image for a larger version.
This lesson was taught as part of the [Hello IoT series](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) from the [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn). The lesson was taught as 2 videos - a 1 hour lesson, and a 1 hour office hour diving deeper into parts of the lesson and answering questions.
[![Lesson 4: Connect your Device to the Internet](https://img.youtube.com/vi/O4dd172mZhs/0.jpg)](https://youtu.be/O4dd172mZhs)
[![Lesson 4: Connect your Device to the Internet - Office hours](https://img.youtube.com/vi/j-cVCzRDE2Q/0.jpg)](https://youtu.be/j-cVCzRDE2Q)
> 🎥 Click the images above to watch the videos
## Pre-lecture quiz
[Pre-lecture quiz](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/7)
@ -178,18 +186,27 @@ Configure a Python virtual environment and install the MQTT pip packages.
1. Activate the virtual environment:
* On Windows run:
* On Windows:
* If you are using the Command Prompt, or the Command Prompt through Windows Terminal, run:
```cmd
.venv\Scripts\activate.bat
```
* If you are using PowerShell, run:
```powershell
.\.venv\Scripts\Activate.ps1
```
* On macOS or Linux, run:
```cmd
source ./.venv/bin/activate
```
> 💁 These commands should be run from the same location you ran the command to create the virtual environment. You will never need to navigate into the `.venv` folder, you should always run the activate command and any commands to install packages or run code from the folder you were in when you created the virtual environment.
1. Once the virtual environment has been activated, the default `python` command will run the version of Python that was used to create the virtual environment. Run the following to get the version:
```sh

@ -16,7 +16,7 @@ lib_deps =
knolleary/PubSubClient @ 2.8
bblanchon/ArduinoJson @ 6.17.3
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1

@ -100,8 +100,7 @@ void loop()
doc["light"] = light;
string telemetry;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, telemetry);
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());

@ -15,7 +15,7 @@ framework = arduino
lib_deps =
knolleary/PubSubClient @ 2.8
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1

@ -15,8 +15,8 @@ framework = arduino
lib_deps =
knolleary/PubSubClient @ 2.8
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
bblanchon/ArduinoJson @ 6.17.3

@ -74,8 +74,7 @@ void loop()
doc["light"] = light;
string telemetry;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, telemetry);
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());

@ -0,0 +1,439 @@
# আইওটি ডিভাইসকে ইন্টারনেটে সংযুক্তিকরণ
![A sketchnote overview of this lesson](../../../../sketchnotes/lesson-4.jpg)
> [Nitya Narasimhan](https://github.com/nitya) তৈরী করছেন এই স্কেচনোটটি। এটির বড় সংস্করণ দেখতে হলে ছবিটির উপর ক্লিক করতে হবে।
## লেকচার পূর্ববর্তী কুইজ
[লেকচার পূর্ববর্তী কুইজ](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/7)
## সূচনা
IoT শব্দে **I** হলো Internet - আইওটিতে "ইন্টারনেট" বলতে ক্লাউড এর মাধ্যমে সংযোগ এবং সেবা প্রদানের মাধ্যমে আইওটি যন্ত্রের বৈশিষ্ট্যসমূহ চালু করা, যন্ত্রের সাথে সংযুক্ত সেন্সর এর মাধ্যমে পরিমাপসমূহ সংগ্রহ করা এবং বার্তা প্রেরণের মাধ্যমে একচুয়েটরসমূহকে নিয়ন্ত্রণ করাকে বুঝায়। আইওটি যন্ত্রগুলি সাধারণত একটি স্ট্যান্ডার্ড কমিউনিকেশন প্রোটোকল ব্যবহার করে একটিমাত্র ক্লাউড আইওটি সেবাতে সংযুক্ত হয় এবং এই সেবাটি সব আইওটি এপ্লিকেশন এর সাথে সংযুক্ত থাকে যা কিনা যন্ত্রসমূহ থেকে প্রাপ্ত ডেটার মাধ্যমে এআই (কৃত্রিম বুদ্ধিমত্তা) সেবার সাহায্যে গুরুত্বপূর্ণ সিদ্ধান্ত নেওয়া থেকে শুরু করে ওয়েব এপস এর মাধ্যমে নিয়ন্ত্রণ করা বা প্রতিবেদনও তৈরি করে দিতে পারে।
> 🎓 সেন্সর এর মাধ্যমে তথ্য সংগ্রহ এবং সেই তথ্য ক্লাউডে প্রেরণ করাকে টেলিমেট্রি(Telemetry) বলে।
আইওটি যন্ত্রসমূহ ক্লাউড থেকে বার্তা গ্রহণ করতে সক্ষম। তবে এই বার্তাগুলোতে আদেশ থাকে - সেটি হল অভ্যন্তরীণভাবে কোনও কাজ সম্পন্ন করার নির্দেশনাবলী (যেমন রিবুট বা ফার্মওয়্যার আপডেট করা), অথবা একচুয়েটরকে ব্যবহার করা (যেমন একটি লাইট জ্বালানো)।
এই পাঠটিতে আইওটি যন্ত্রসমূহ কমিউনিকেশন প্রোটোকল ব্যবহার করে ক্লাউডে সংযুক্ত হওয়া এবং কি ধরনের তথ্য ক্লাউডে গ্রহণ বা প্রেরণ করে তা শিখবো। নিয়ন্ত্রিত ইন্টারনেট লাইটে সংযুক্ত করা এবং এলইডি নিয়ন্ত্রনের লজিক কোডটিকে চলমান 'সার্ভার'-এ নেওয়া, এই দুটি কাজ হাতে-কলমে শিখবো।
এ পাঠ হতে যা যা শিখবোঃ
* [কমিউনিকেশন প্রটোকলসমূহ](#কমিউনিকেশন-প্রটোকলসমূহ)
* [এমকিউটিটি (Message Queueing Telemetry Transport-MQTT)](#Message-Queueing-Telemetry-Transport-MQTT)
* [টেলিমেট্রি](#টেলিমেট্রি)
* [কমান্ডসমূহ](#কমান্ডসমূহ)
## কমিউনিকেশন প্রটোকলসমূহ
আইওটি যন্ত্রসমূহ কয়েকটি জনপ্রিয় কমিউনিকেশন প্রটোকল ব্যবহার করে ইন্টারনেটের সাথে সংযুক্ত হয়। সবচেয়ে জনপ্রিয় হচ্ছে কোন ধরনের সার্ভার বা Broker এর মাধ্যমে বার্তা প্রচার/সাবস্ক্রাইব করা। আইওটি যন্ত্রসমূহ এই ব্রোকারের সাথে সংযুক্ত হয়ে টেলিমেট্রি প্রচার করে এবং কমান্ডগুলোতে সাবস্ক্রাইব করে। ক্লাউড সেবাগুলোও এই ব্রোকারের সাথে সংযুক্ত হয়ে সকল টেলিমেট্রি বার্তাগুলোতে সাবস্ক্রাইব করে এবং কমান্ডগুলোকে হয় একটি নির্দিষ্ট ডিভাইসে না হয় অনেকগুলো ডিভাইসের একটি গ্রুপে প্রেরণ করে।
![আইওটি যন্ত্রসমূহ এই মধ্যস্থতাকারীর (Broker) সাথে সংযুক্ত হয়ে টেলিমেট্রি প্রচার করে এবং আদেশগুলোতে সাবস্ক্রাইব করে। ক্লাউড সেবাগুলোও এই মধ্যস্থতাকারীর সাথে সংযুক্ত হয়ে সকল টেলিমেট্রি বার্তাগুলোতে সাবস্ক্রাইব করে এবং কমান্ডগুলোকে একটি নির্দিষ্ট ডিভাইসে প্রচার করে।](../../../../images/pub-sub.png)
আইওটি ডিভাইসগুলোর জন্য MQTT হলো সবচেয়ে জনপ্রিয় কমিউনিকেশন প্রটোকল যা এই পাঠে অন্তর্ভুক্ত করা হয়েছে। অন্যান্য প্রটোকলের মধ্যে AMQP এবং HTTP/HTTPS অন্তর্ভুক্ত আছে।
## Message Queueing Telemetry Transport-MQTT
[MQTT](http://mqtt.org) হল লাইটওয়েট, ওপেন স্ট্যান্ডার্ড মেসেজিং প্রোটোকল যা ডিভাইসগুলোর মধ্যে বার্তা প্রেরণ করতে পারে। ১৫ বছর পরে ওপেন স্ট্যান্ডার্ড হিসাবে এটি আইবিএম দ্বারা প্রকাশিত হয় যা পূর্বে তেলের পাইপলাইনগুলি পর্যবেক্ষণ করার জন্য ১৯৯৯ সালে নকশা করা হয়েছিল ।
MQTT এর একটি ব্রোকার এবং একাধিক ক্লায়েন্ট রয়েছে। সমস্ত ক্লায়েন্ট ব্রোকারের সাথে সংযুক্ত হয় এবং ব্রোকার সংশ্লিষ্ট ক্লায়েন্টদের বার্তা প্রেরণ করে। বার্তাগুলো কোনো বিশেষ গ্রাহককে সরাসরি প্রেরণ না করে বরং নামকরণ করা টপিকগুলি ব্যবহার করে একটি নির্দিষ্ট পথে পাঠানো হয়। একটি ক্লায়েন্ট একটি টপিক প্রচার করতে পারে এবং যেকোনো ক্লায়েন্ট যে ঐ টপিকে সাবস্ক্রাইব করে তা সে সম্পর্কিত বার্তা গ্রহণ করে।
![IoT device publishing telemetry on the /telemetry topic, and the cloud service subscribing to that topic](../../../../images/mqtt.png)
✅ কিছু গবেষণা করি। যদি অনেকগুলো আইওটি ডিভাইস থাকে তাহলে কিভাবে নিশ্চিত হবো যে MQTT- ব্রোকার সবগুলো বার্তা নিয়ন্ত্রন করতে পারবে কিনা?
### আইওটি ডিভাইসটি MQTT-তে সংযুক্তিকরণ
ইন্টারনেটের মাধ্যমে নাইটলাইটকে নিয়ন্ত্রণ করার প্রথম ধাপ হচ্ছে সেটিকে MQTT- ব্রোকার এর সাথে সংযুক্ত করা।
#### কাজ
আইওটি ডিভাইসটি MQTT-ব্রোকার এ সংযুক্ত করা।
পাঠের এই অংশটিতে আইওটি নাইটলাইটিকে ইন্টারনেটে সংযুক্ত করি যাতে করে সেটিকে দূর থেকে নিয়ন্ত্রণ করা যায়। এই পরবর্তী পাঠে, আইওটি ডিভাইসটি MQTT-র মাধ্যমে একটি টেলিমেট্রি বার্তা লাইটের লেভেলসহ(সেন্সর এর ভ্যালু) পাবলিক MQTT ব্রোকারে পাঠাবে এবং সেটি কতিপয় সার্ভার দ্বারা নেওয়া হবে যেটাতে কোডটি লেখা হয়েছিলো। এই কোডটি লাইটের লেভেল/সেন্সর ভ্যালু যাচাই করবে এবং যাচাই করার পর একটি আদেশমূলক বার্তা আইওটি লাইটটিতে/ডিভাইসে পাঠাবে যাতে আদেশ হিসেবে বলা থাকবে যে এলইডিটি অন না অফ হবে।
বাস্তবিক ক্ষেত্রে এমন অবস্থা হতে পারে যেখানে অনেক লাইট সেন্সর রয়েছে (যেমন স্ট্যাডিয়াম) এবং সেই লাইটগুলো অন করার সিদ্ধান্ত নেওয়ার পূর্বে ওই একাধিক লাইট সেন্সর এর তথ্য সংগ্রহ করার প্রয়োজন হতে পারে। শুধুমাত্র একটি সেন্সর মেঘ বা পাখি দ্বারা আবৃত থাকে লাইটগুলি অন হওয়া থেকে বন্ধ রাখতে পারে, যদিও অন্য সেন্সরগুলি পর্যাপ্ত আলো শনাক্ত করেও।
✅ কমান্ড প্রেরণের আগে একাধিক সেন্সর এর তথ্য মূল্যায়নের জন্য অন্য কি পরিস্থিতিগুলো বিবেচিত হতে পারে?
এসাইনমেন্টের অংশ হিসেবে MQTT ব্রোকার সেটাপের এই জটিলতার সাথে মোকাবেলা করার চেয়ে বরং চাইলে সেটাপটি পাবলিক টেস্ট সার্ভার ব্যবহার করা যবে যেটি [Eclipse Mosquitto](https://www.mosquitto.org)(একটি ওপেন সোর্স MQTT ব্রোকার)-এ রান হবে। এই টেস্ট ব্রোকারটি [test.mosquitto.org](https://test.mosquitto.org)-এ পাওয়া যাবে যা জনসাধারনের জন্য উন্মুক্ত। MQTT ক্লায়েন্ট এবং সার্ভার এর জন্য এটি একটি অসাধারণ টুল কারণ এটিতে সেটাপ করতে কোনো একাউন্টের প্রয়োজন নেই।
> 💁 এই টেস্ট ব্রোকারটি উন্মুক্ত যা মোটেই সুরক্ষিত নয়। যে কেউ বুঝতে পারবে এতে কি পাবলিশ করা হয়েছে, তাই যে তথ্যগুলোতে গোপনীয় রাখা জরুরি সেগুলো এতে ব্যবহার না করার পরামর্শ রইল।
![A flow chart of the assignment showing light levels being read and checked, and the LED begin controlled](../../../../images/assignment-1-internet-flow.png)
ডিভাইসটি MQTT ব্রোকারে সংযুক্ত করতে সংশ্লিষ্ট ধাপগুলো অনুসরণ করিঃ
* [আরডুইনো Wio টার্মিনাল](wio-terminal-mqtt.bn.md)
* [সিংগেল বোর্ড কম্পিউটার - রাস্পবেরি পাই/ভার্চুয়াল আইওটি ডিভাইস](single-board-computer-mqtt.bn.md)
### MQTT এর আরো গভীরে
টপিকগুলোতে শ্রেণিবিন্যাস থাকতে পারে যাতে ক্লায়েন্টরা ওয়াইল্ডকার্ড ব্যবহার করে এই শ্রেণিবিন্যাসের বিভিন্ন স্তরে সাবস্ক্রাইব করতে পারে। যেমন, তাপমাত্রার টেলিমেট্রি বার্তাগুলো `/telemetry/temperature` এই টপিকে এবং আর্দ্রতার বার্তাগুলো `/telemetry/humidity` এই টপিকে পাঠানো, তারপর ক্লাউড এপটি `/telemetry/*` এই টপিকে সাবস্ক্রাইব করে তাপমাত্রা এবং আর্দ্রতা উভয়ের টেলিমেট্রি বার্তাগুলো গ্রহণ করবে।
বার্তা গ্রহণের নিশ্চয়তা প্রদান করতে বার্তাগুলো কোয়ালিটি অফ সার্ভিস(QoS) এর সাথে পাঠানো হয়।
* সর্বাধিক একবার বার্তা শুধুমাত্র একবারই পাঠানো হয় এবং ক্লায়েন্ট আর ব্রোকার বার্তাটি ডেলিভারীর প্রাপ্তি স্বীকার করতে কোনো অতিরিক্ত পদক্ষেপ নেয় না (fire and forget)।
* অন্তত একবার স্বীকারোক্তি গৃহীত না হওয়া পর্যন্ত বার্তা প্রেরক একাধিকবার বার্তা প্রেরণের চেষ্টা করেছিল (acknowledged delivery)।
* ঠিক একবার শুধু একটি বার্তা গৃহীত হয়েছে তা নিশ্চিত করতে প্রাপক এবং প্রেরক একটি দ্বি-স্তরের যোগাযোগ করে যাকে হ্যান্ডশেক এর সাথে তুলনা করা যায় (assured delivery)।
✅ কোন পরিস্থিতিতে fire and forget বার্তার পরেও একটি assured delivery এর বার্তা প্রয়োজন হতে পারে?
যদিও নামটি মেসেজ কিউয়িং (ইংরেজি প্রথম অক্ষরগুলো নিয়ে MQTT), এটি আসলে বার্তার সারিকে বুঝায় না। এর অর্থ হল যদি কোন ক্লায়েন্ট সংযোগ বিচ্ছিন্ন করে পুনরায় সংযোগ স্থাপন করার সময় QoS প্রসেস ব্যবহার করে ইতোমধ্যে প্রসেসকৃত বার্তাগুলো বাদে বিচ্ছিন্ন অবস্থায় প্রেরিত বার্তাগুলো গৃহীত হবে না। ওই বার্তাগুলোকে মনে রাখাতে একটি ফ্ল্যাগ সেট করা হয়। যদি এই ফ্ল্যাগ সেট করা থাকে তবে MQTT-ব্রোকার একটি টপিকে প্রেরিত সর্বশেষ বার্তাটি ওই ফ্ল্যাগসহিত জমা রাখবে এবং পরবর্তীতে কোনো ক্লায়েন্ট যদি এই টপিকে সাবস্ক্রাইব করে তাকে প্রেরণ করা হয়। এই পদ্ধতিতে ক্লায়েন্টগুলো সবর্দা সর্বশেষ বার্তাগুলো পেয়ে থাকে।
MQTT একটি এলাইভ(alive) ফাংশনকে সমর্থন করে এবং এই ফাংশনটি বার্তাগুলোর মধ্যে দীর্ঘ বিরতির সময় সংযোগটি চালু আছে কিনা তা পরীক্ষা করে।
> 🦟 [Eclipse Foundation এর Mosquitto-তে](https://mosquitto.org) একটি ফ্রী MQTT ব্রোকার রয়েছে যাতে MQTT-সম্পর্কিত পরীক্ষা করা যাবে, MQTT-ব্রোকার এর সাথে কোড টেস্ট করা যাবে যা এই [test.mosquitto.org](https://test.mosquitto.org) হোস্ট করা থাকে।
MQTT- সংযোগসমূহ পাবলিক ও উন্মুক্ত থাকতে পারে অথবা ইউজারনেইম, পাসওয়ার্ড এবং সার্টিফিকেট ব্যবহারের মাধ্যমে এনক্রিপটেড ও সুরক্ষিত থাকতে পারে।
> 💁 MQTT TCP/IP এর মাধ্যমে যোগাযোগ করে, এটি HTTP এর মতোই একটি নেটওয়ার্ক প্রোটকল তবে একটি ভিন্ন পোর্টবেইজড। ব্রাউজারে চলমান ওয়েব অ্যাপ্লিকেশনগুলির সাথে যোগাযোগের জন্য ওয়েবসকেটের পরিবর্তে বা ফায়ারওয়ালগুলি বা অন্যান্য নেটওয়ার্কিং রুলস যা স্ট্যান্ডার্ড সংযোগগুলিতে MQTT ব্লক করে এমন পরিস্থিতিতে MQTT ব্যবহার করা যাবে।
## টেলিমেট্রি
টেলিমেট্রি শব্দটি গ্রীক থেকে উদ্ভূত হয়েছিলো যার অর্থ দূরবর্তী থেকে পরিমাপ করা। টেলিমেট্রি হল সেন্সর থেকে ডেটা সংগ্রহ এবং সেই ডেটা ক্লাউডে প্রেরণ করা ।
> 💁 প্রাচীনতম টেলিমেট্রি ডিভাইসগুলি ফ্রান্সে ১৮৭৪ সালে উদ্ভাবিত হয়েছিল এবং Mont Blanc থেকে প্যারিসে রিয়েল-টাইম আবহাওয়া এবং তুষারের গভীরতা প্রেরণ করেছিল। এটি বাস্তবিক তারগুলি ব্যবহার করত কারণ ওয়্যারলেস প্রযুক্তিগুলি তখন ছিল না।
পাঠ 1 থেকে স্মার্ট থার্মোস্টেটের উদাহরণটি দেখি।
![একাধিক রুম সেন্সর ব্যবহার করে ইন্টারনেটে সংযুক্ত একটি থার্মোস্ট্যাট](../../../../images/telemetry.png)
টেলিমেট্রি সংগ্রহের জন্য থার্মোস্ট্যাট এর তাপমাত্রাভিত্তিক সেন্সর রয়েছে। এটিতে একটি তাপমাত্রাভিত্তিক সেন্সর বিল্টইন থাকে এবং ওয়ারলেস প্রোটোকল যেমন [ব্লুটুথ লো এনার্জি](https://wikipedia.org/wiki/Bluetooth_Low_Energy) (BLE) এর মাধ্যমে একাধিক তাপমাত্রাভিত্তিক সেন্সর এর সাথে সংযুক্ত হতে পারে।
উদাহরণস্বরূপ একটি টেলিমেট্রি ডেটা যা প্রেরণ করা হবে, তা হলো
| নাম | মান | বর্ণনা |
| ---- | ----- | ----------- |
| `thermostat_temperature` | 18°C | থার্মোস্ট্যাট এর বিল্ট-ইন তাপামাত্রাভিত্তিক সেন্সর দ্বারা তাপমাত্রা পরিমাপ করা। |
| `livingroom_temperature` | 19°C | দূরবর্তী তাপমাত্রাভিত্তিক সেন্সর(remote temperature sensor) দ্বারা তাপমাত্রা পরিমাপ করা হয় যেটিকে `livingroom` নামকরণ করা হয়েছে যাতে এটি যেই রুমে আছে সেই রুমকে শনাক্ত করতে পারে। |
| `bedroom_temperature` | 21°C | দূরবর্তী তাপমাত্রাভিত্তিক সেন্সর(remote temperature sensor) দ্বারা তাপমাত্রা পরিমাপ করা হয় যেটিকে ` bedroom ` নামকরণ করা হয়েছে যাতে এটি যেই রুমে আছে সেই রুমকে শনাক্ত করতে পারে। |
ক্লাউড সেবা এই টেলিমেট্রি ডেটা ব্যবহার করে তাপকে নিয়ন্ত্রণ করতে কী আদেশ পাঠাবে তার সিদ্ধান্ত নিতে পারে।
### টেলিমেট্রি আইওটি ডিভাইসে প্রেরণ
নাইটলাইটটিকে ইন্টারনেটের মাধ্যমে নিয়ন্ত্রণ করার পরবর্তী অংশটি হলো লাইট লেভেল টেলিমেট্রি MQTT- ব্রোকারের টেলিমেট্রি টপিকে পাঠানো।
#### কাজ
লাইট লেভেল টেলিমেট্রি MQTT- ব্রোকারে পাঠানো।
ডেটা JSON হিসাবে এনকোড করে পাঠানো হয় JSON হলো JavaScript Object Notation এর সংক্ষিপ্ত রূপ, যা কী/ভ্যালু পেয়ার ব্যবহার করে ডেটাকে এনকোডেড টেক্সট এ রূপান্তরের জন্য একটি স্ট্যান্ডার্ড ।
✅ JSON সম্পর্কে জ়েনে না থাকলে তা এই [JSON.org documentation](https://www.json.org/) থেকে শিখতে পারবো।
ডিভাইস থেকে MQTT-ব্রোকারের কাছে টেলিমেট্রি প্রেরণের জন্য নীচের পদক্ষেপটি অনুসরণ করিঃ
* [আরডুইনো Wio টার্মিনাল](wio-terminal-telemetry.bn.md)
* [সিংগেল বোর্ড কম্পিউটার - রাস্পবেরি পাই/ভার্চুয়াল আইওটি ডিভাইস](single-board-computer-telemetry.bn.md)
### MQTT ব্রোকার হতে টেলিমেট্রি গ্রহণ
টেলিমেট্রি পাঠানোর কোন অর্থ নেই যদি অন্য প্রান্তে এটিকে গ্রহণ করার মতো কিছু না থাকে। লাইট লেভেল টেলিমেট্রিটির ডেটা প্রক্রিয়া করার জন্য এটিকে কিছু গ্রাহকের এর প্রয়োজ়নীয়তা আছে। এই 'সার্ভার' কোডটি সেই ধরণের কোড যা বৃহত্তর আইওটি অ্যাপ্লিকেশনের অংশ হিসাবে একটি ক্লাউড সেবাতে স্থাপন করবো, তবে এখানে স্থানীয়ভাবে এই কোডটি লোকাল কম্পিউটারে চালাতে পারবো (বা আমাদের Pi-তে সরাসরি কোডিং করতে পারবো)। সার্ভার কোডে একটি পাইথন অ্যাপ থাকে যা MQTT-র মাধ্যমে লাইটে লেভেলগুলোর টেলিমেট্রি বার্তা গ্রহণ করতে পারে। এই পাঠের পরবর্তীতে, এটিতে কমান্ডের মেসেজ রিপ্লেতে পাঠাবো যাতে নির্দেশনা থাকবে যে এলইডি অন না অফ হবে।
✅ কিছু গবেষনা করিঃ MQTT বার্তাগুলোর কী হবে যদি কোনো গ্রাহক না থাকে?
#### পাইথন এবং ভিএস কোড ইন্সটল করি
লোকালি Python এবং VS Code ইনস্টল করা না থাকলে সার্ভারে কোড দেওয়ার জন্য দুইটিকেই ইনস্টল করবো। যদি ভার্চুয়াল ডিভাইস ব্যবহার করি বা রাস্পবেরি পাইতে কাজ করি তবে এই পদক্ষেপটি এড়িয়ে যেতে পারি ।
##### কাজ
Python এবং VS Code ইন্সটল করি।
1. পাইথন ইন্সটল করি। [Python downloads page](https://www.python.org/downloads/) থেকে পাইথনে সর্বশেষ ভার্সনটি নির্দেশনা মোতাবেক ইনস্টল করি।
1. Visual Studio Code (VS Code) ইন্সটল করি। আমরা এই ইডিটরটি আমাদের ভার্চুয়াল ডিভাইসে পাইথনে কোড করার জন্য ব্যবহার করবো। [VS Code documentation](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) থেকে Visual Studio Code (VS Code) নির্দেশনাগুলো অনসুরণ করে Visual Studio Code (VS Code) ইন্সটল করি।
> 💁 এই পাঠের নির্দেশনাগুলো VS Code এর উপর ভিত্তি করে লেখা হলেও এই পাঠের জন্য আমরা আমাদের সুবিধামত টুল অর্থাৎ পাইথনবেইজ়ড যেকেনো আইডি বা ইডিটর ব্যবহার করতে পারি।
1. VS Code এর Pylance এক্সটেনশনটি ইন্সটল করি। পাইথন লেঙ্গুয়েজের সাপোর্টের জন্য এটি VS Code এর একটি এক্সটেনশন। এটি এক্সটেনশনটি VS Code-এ কিভাবে ইন্সটল করতে হয় [Pylance extension documentation](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance&WT.mc_id=academic-17441-jabenn) থেকে দেখে নেই।
#### পাইথনের ভার্চুয়াল এনভায়রনমেন্ট কনফিগারেশন
পাইথনের অন্যতম শক্তিশালী ফিচারটি হল [পিপ প্যাকেজ](https://pypi.org) ইনস্টল করার ক্ষমতা এই কোডের প্যাকেজগুলি অন্যদের দ্বারা লেখা হয় যা পরবর্তীতে ইন্টারনেটে প্রকাশ করা হয়। কম্পিউটারে একটি কমান্ডের এর মাধ্যমে পিপ প্যাকেজ ইন্সটল করা যায়, তারপরে নির্দিষ্ট কোডটিতে সেই প্যাকেজটি ব্যবহার করতে পারবো। MQTT- এর মাধ্যমে যোগাযোগ করতে আমরা পিপ ব্যবহার করে প্যাকেজ ইন্সটল করব।
আমরা যখন কোনো প্যাকেজ ইন্সটল করি তখন তা পুরো কম্পিউটার জুড়ে থাকে এবং তা থেকে প্যাকেজ়ের ভার্সনজনিত সমস্যা দেখা দিতে পারে যেমন একটি অ্যাপ্লিকেশন যখন প্যাকেজ়ের একটি ভার্সনের উপর ভিত্তি করে চলে কিন্তু ভিন্ন এপ্লিকেশনের জন্য ওই একি প্যাকেজের নতুন ভার্সন ইন্সটল করলে আগের ভার্সনে ব্যাঘাত ঘটতে পারে। এই সমস্যা হতে উত্তরণের জন্য আমরা [Python virtual environment](https://docs.python.org/3/library/venv.html) ব্যবহার করবো এবং তাতে পাইথনের জন্য একটি ডেডিকেটেড ফোল্ডার থাকবে যাতে যখন আমরা আমাদের প্রয়োজনীয় পিপ প্যাকেজসমূহ ইন্সটল করলে তা এই ফোল্ডারে ইন্সটল হবে।
##### কাজ
পাইথনের ভার্চুয়াল এনভায়রনমেন্ট কনফিগারেশন এবং MQTT পিপ প্যাকেজ ইন্সটল।
1. টার্মিনাল বা কমান্ড লাইন হতে আমাদের পছন্দসই লোকেশনে ডিরেক্টরি তৈরি এবং নেবিগেইট করতে নিচের কমান্ডগুলো রান দিইঃ
```sh
mkdir nightlight-server
cd nightlight-server
```
1. ভার্চুয়াল এনভায়রনমেন্ট `.venv` ফোল্ডারে বানানোর জন্য নিম্নের কমান্ডটি রান করি।
```sh
python3 -m venv .venv
```
> 💁 যদি Python 2 এর সাথে Python 3(লেটেস্ট ভার্সন) ইন্সটল করা থাকে তবে ভার্চুয়াল এনভায়রনমেন্ট বানানোর জন্য সঠিকভাবে `python3`-কে কল করতে হবে। যদি Python 2 ইন্সটল করা থাকে তবে `python` কল করলে তা Python 3 এর পরিবর্তে Python 2-কে কল করবে।
1. ভার্চুয়াল এনভায়রনমেন্ট চালু করতে নিম্নের কমান্ড দিইঃ
* Windows-এ রান করতে নিম্নের কমান্ডটি দিইঃ
```cmd
.venv\Scripts\activate.bat
```
* macOS বা Linux এ রান করতে নিম্নের কমান্ডটি দিইঃ
```cmd
source ./.venv/bin/activate
```
1. ভার্চুয়াল এনভারনমেন্টটি একবার চালু হওয়ার পর ভার্চুয়াল এনভারনমেন্ট বানাতে পাইথনের যে ভার্সনটি ব্যবহৃত হয়েছিলো তা ডিফল্টভাবে `python` কমান্ডটি সেই ভার্সনটিকে রান করাবে। পাইথনে ভার্সন জানতে নিম্নের কমান্ডটি রান করবোঃ
```sh
python --version
```
আউটপুটটি নিম্নের মতো হবেঃ
```output
(.venv) ➜ nightlight-server python --version
Python 3.9.1
```
> 💁 পাইথনের ভার্সন ভিন্ন হতে পারে তবে ভার্সন ৩.৬ বা তারও বেশি হলে ভালো। যদি এই ভার্সন ইন্সটল না থাকে তবে এই ফোল্ডারটি ডিলিট করে পাইথনের নতুন ভার্সনটি ইন্সটল করে আবার চেষ্টা করি।
1. [Paho-MQTT](https://pypi.org/project/paho-mqtt/)(একটি জনপ্রিয় MQTT লাইব্রেরি) পিপ প্যাকেজটি ইন্সটল করতে নিম্নের কমান্ডটি রান করি।
```sh
pip install paho-mqtt
```
এই পিপ প্যাকেজটি শুধুমাত্র ভার্চুয়াল এনভায়রনমেন্টে ইন্সটল হবে।
#### সার্ভার কোডটি লিখি
এখন সার্ভার কোডটি পাইথনে লিখবো।
##### কাজ
সার্ভার কোডটি লিখি।
1. ভার্চুয়াল এনভায়রনমেন্টের ভিতরে `app.py` নামে একটি পাইথন ফাইল বানাতে টার্মিনাল বা কমান্ড লাইন হতে নিম্নের কমান্ডটি দিইঃ
* Windows-এ রান করতে নিম্নের কমান্ডটি দিইঃ
```cmd
type nul > app.py
```
* macOS বা Linux এ রান করতে নিম্নের কমান্ডটি দিইঃ
```cmd
touch app.py
```
1. কারেন্ট ফোল্ডারটি VS Code-এ ওপেন করিঃ
```sh
code .
```
1. যখন VS Code-টি ওপেন হবে তখন তা পাইথনের ভার্চুয়াল এনভায়রনমেন্টটিকে চালু করবে। এটি উপরে স্ট্যাটাস বারে দেখা যাবেঃ
![VS Code showing the selected virtual environment](../../../../images/vscode-virtual-env.png)
1. যদি VS Code স্টার্ট হওয়ার সময় VS Code টার্মিনালটি চালুরত অবস্থায় থাকে তবে ভার্চুয়াল এনভায়রনমেন্ট VS Code-এ একটিভেট হবে না। এর থেকে উত্তরণের সহজ উপায় হচ্ছে **Kill the active terminal instance** বাটনটিতে ক্লিক করে টার্মিনালটিকে বন্ধ করে দিবো।
![VS Code Kill the active terminal instance button](../../../../images/vscode-kill-terminal.png)
1. নতুন VS Code টার্মিনাল চালু করতে *Terminal -> New Terminal এ সিলেক্ট করবো বা `` CTRL+` `` প্রেস করবো। এই নতুন টার্মিনালটি একটি কল করে ভার্চুয়াল এনভায়রনমেন্টটি এক্টিভেট করবে যার ফলে এটি টার্মিনালে লোড হয়ে আসবে। ভার্চুয়াল এনভায়রনমেন্টের নামটি (`.venv`) প্রম্পটে দেখা যাবেঃ
```output
➜ nightlight source .venv/bin/activate
(.venv) ➜ nightlight
```
1. VS Code explorer থেকে `app.py` ফাইলটি ওপেন করি এবং নিম্নের কোডটি এড করিঃ
```python
import json
import time
import paho.mqtt.client as mqtt
id = '<ID>'
client_telemetry_topic = id + '/telemetry'
client_name = id + 'nightlight_server'
mqtt_client = mqtt.Client(client_name)
mqtt_client.connect('test.mosquitto.org')
mqtt_client.loop_start()
def handle_telemetry(client, userdata, message):
payload = json.loads(message.payload.decode())
print("Message received:", payload)
mqtt_client.subscribe(client_telemetry_topic)
mqtt_client.on_message = handle_telemetry
while True:
time.sleep(2)
```
আমদের ডিভাইস বানানোর সময় যে ইউনিক আইডিটি ব্যবহার করেছিলাম তা ৬নং লাইনের `<ID>`-তে বসিয়ে দিই।
⚠️ এই আইডিটি **অব্যশই** আমাদের ডিভাইসে ব্যবহৃত একই আইডি হতে হবে নয়ত সার্ভার কোড সঠিক টপিক সাবস্ক্রাইব বা পাবলিশ কোনোটিই করবে না।
এই কোডটি একটি ইউনিক নামসহ একটি MQTT ক্লায়েন্ট তৈরি করে যা * test.mosquitto.org * ব্রোকারের সাথে সংযুক্ত হয়। পরবর্তীতে এটি একটি প্রসেসিং লুপ চালু করে যেকোনো সাবস্ক্রাইব টপিকের মেসেজ লিসেনিং এর জন্য ব্যাকগ্রাউন্ড থ্রেডে রান করে করে।
ক্লায়েন্ট পরবর্তীতে টেলিমেট্রি টপিকের মেসেজগুলিতে সাবস্ক্রাইব করে এবং একটি ফাংশন সংজ্ঞায়িত করে আর যখন মেসেজ গৃহীত হয় তখনই ফাংশনটিকে কল করা হয়। যখন একটি টেলেমেট্রি মেসেজ গৃহীত হয় তখনই `handle_telemetry` ফাংশনটি কল করা হয় এবং কনসোলে গৃহীত বার্তাটি প্রিন্ট হয়।
সবশেষে একটি ইনফিনিট লুপে অ্যাপ্লিকেশনটি রান হয়। ব্যাকগ্রাউন্ড থ্রেডে MQTT ক্লায়েন্ট বার্তাগুলোতে লিসেন করে এবং মেইন এপ্লিকেশনটি রান হওয়া অবস্থায় এটি সবসময় রান হয়।
1. পাইথন এপটি রান করতে VS Code টার্মিনাল হতে নিম্নের কোডটি রান করিঃ
```sh
python app.py
```
এপটি আইওটি ডিভাইসের মেসেজসমূহ লিসেন করতে শুরু করবে।
1. আমাদের অব্যশই নিশ্চিত হতে হবে যে ডিভাইসটি রান করছে কিনা এবং টেলিমেট্রি মেসেজ সেন্ড করছে কিনা। বাহ্যিক বা ভার্চুয়াল ডিভাইস হতে শনাক্তকৃত লাইট লেভেলটিকে এডজাস্ট করি। গৃহীত বার্তাসমূহ নিচের মতো টার্মিনালে প্রিন্ট হবে।
```output
(.venv) ➜ nightlight-server python app.py
Message received: {'light': 0}
Message received: {'light': 400}
```
প্রেরিত মেসেজ গ্রহণের জন্য নাইটলাইট ভার্চুয়াল এনভায়রনমেন্টে app.py ফাইলটিকে অবশ্যই রান অবস্থায় থাকতে হবে।
> 💁 কোডটি [code-server/server](code-server/server) ফোল্ডারে পাওয়া যাবে।
### টেলিমেট্রি কতবার পাঠানো উচিত?
টেলিমেট্রিতে একটি গুরুত্বপূর্ণ বিষয় হল কতবার ডেটা পরিমাপ এবং প্রেরণ করা উচিত? উত্তরটি হল, এটি পরিস্থিতিভেদে নির্ভরশীল। যেমনঃ আমরা যদি প্রায়ই পরিমাপ করি তবে পরিমাপ পরিবর্তনের প্রতি দ্রুত সারা দিতে পারবো কিন্তু আমরা যদি আরও পাওয়ার, ব্যান্ডউইথ, ডেটা ব্যাবহার করি তাহলে এইগুলো প্রসেস করার জন্য আরও ক্লাউড রিসোর্সের প্রয়োজন হবে যার ফলে আমাদের প্রায়ই যথেষ্ট পরিমাণে পরিমাপ করা প্রয়োজন তবে তা খুব বেশি নয়।
একটি থার্মোস্ট্যাট যদি কয়েক মিনিট পর পর তাপমাত্রা পরিমাপ করে তবে তা প্রয়োজনের অতিরিক্ত কারণ তাপমাত্রা প্রতিনিয়ত পরিবর্তিত হয় না। যদি আমরা দৈনিক একবার তাপমাত্রা পরিমাপ করি তবে ভরদুপুরে নাইটটাইমের তাপমাত্রায় আমাদের ঘরটিকে উত্তপ্ত করে বসবো। অপরপক্ষে আমরা যদি প্রতি সেকেন্ডে পরিমাপ করি তবে অপ্রয়োজনীয় সহস্রাধিক তাপমাত্রার পরিমাপের ডেটা তৈরি হবে যাতে ইউজারের ইন্টারনেট এবং ব্যান্ডউইথ বেশি ব্যবহৃত হবে (যা লিমিটেড ব্যান্ডউইথ প্ল্যানে চলা ইউজারদের সমস্যা হতে পারে) এবং আরও পাওয়ার ব্যবহার করবে যা ব্যাটারি চালিত ডিভাইসের জন্য সমস্যা হতে পারে (যেমনঃ রিমোট সেন্সর) যার ফলে ক্লাউড প্রোভাইডারে কম্পিউটিং রিসোর্সের প্রসেসিং এবং স্টোরিং এর ব্যয় বেড়ে যাবে।
যদি কোন ফ্যাক্টরিতে কোন মেসিনারি ডেটা পর্যবেক্ষণ করা হয় এবং যদি এই মেসিনটি নষ্ট হয় তবে বিপজ্জনক ক্ষতি হতে পারে তার সাথে কয়েক মিলিয়ন ডলারের রাজস্বও নষ্ট হতে পারে। তাই এমন পরিস্থিতে প্রতি সেকেন্ডে একাধিকবার পরিমাপ করা অবশ্যই যুক্তিযুক্ত। টেলিমেট্রিযেকোন যন্ত্র নষ্ট হওয়ার আগে সেটিকে বন্ধ এবং ঠিক করা প্রয়োজন তা ইন্ডিকেট করে দেয়, তাই টেলিমেট্রি মিস হওয়ার চেয়ে ব্যান্ডউইথ নষ্ট হওয়া ভাল।
> 💁 এই পরিস্থিতিতে,ইন্টারনেটের উপর নির্ভরতা হ্রাস করতে প্রথম টেলিমেট্রি প্রসেস করার জন্য একটি এজ(edge) ডিভাইস রাখা যেতে পারে।
### লস অফ কানেক্টিভিটি
ইন্টারনেট সংযোগসমূহ হতে পারে অনির্ভরযোগ্য,বিভ্রাটপূর্ণ । এই অবস্থায় আইওটি ডিভাইসের কি করা উচিত এটি কি ডেটাকে হারাতে দিবে নাকি কানেক্টিভিটি পুনরায় না আসা পর্যন্ত ডেটাকে স্টোর করে রাখবে? আবারো উত্তরটি হচ্ছে এটি পরিস্থিতিভেদে নির্ভরশীল।
একটি থার্মোস্ট্যাট এর নতুন তাপমাত্রা পরিমাপ করা মাত্রই আগের পরিমাপ করা ডেটাটি হারিয়ে যেতে পারে। ২০ মিনিট পূর্বে তাপমাত্রা ২০.৫°C ছিল বা এখন তাপমাত্রা ১৯°C তা নিয়ে হিটিং সিস্টেম পরোয়া করে না, বর্তমান মুহূর্তের তাপমাত্রাই নির্ধারণ করবে যে হিটিং সিস্টেমটি অন হবে নাকি অফ থাকবে।
মেশিনারির জন্য ডেটা রাখা যেতে পারে বিশেষত যদি এটি ট্রেন্ডস(trends) সন্ধানে ব্যবহৃত হয়। এমন কিছু মেশিন লার্নিং মডেল রয়েছে যা নির্ধারিত সময়ের মধ্যে(যেমন শেষ ঘন্টায়) ডেটা সন্ধান করে ডেটার স্ট্রিমসমূহে এনোম্যালি চিহ্নিত করে এনোম্যালাস ডেটা শনাক্ত করতে পারে। এটি প্রায়ই predictive maintenance এর জন্য ব্যবহৃত হয়, এমন কিছু লক্ষণের সন্ধান করে যাতে দ্রুত কোনও কিছু ভেঙে যাওয়ার আগেই এটি মেরামত বা রিপ্লেস করা যায় । এনোমলি ডিটেকশনের জন্য একটি মেশিনের প্রেরিত টেলিমেট্রিটির প্রতিটি বিটকে প্রসেস করা হয় তাই যখন আইওটি ডিভাইসটি পুনরায় ইন্টারনেটের সাথে সংযোগ স্থাপন করে তখন ইন্টারনেট আউটেজের সময় তৈরি হওয়া সমস্ত টেলিমেট্রি প্রেরণ করতে পারে।
আইওটি ডিভাইস ডিজাইনারদের বিবেচনা করা উচিত যাতে ইন্টারনেট আউটেজের সময় বা অবস্থানজনিত কারণে সিগন্যাল লসের সময় আইওটি ডিভাইস ব্যবহার করা যাবে কিনা। একটি স্মার্ট থার্মোস্ট্যাট যদি আউটেজের কারনে ক্লাউডে টেলিমেট্রি প্রেরণ করতে না পারে তবে হিটিং সিস্টেমকে কন্ট্রোল করতে তাতে অবশ্যই সীমিত সংখ্যক সিদ্ধান্ত নেওয়ার সক্ষমতা থাকতে হবে।
[![এই ফেরারীটি আচ্ছাদিত হয়ে আছে কারণ কেউ একজন আন্ডারগ্রাউন্ডে এটিকে আপগ্রেড করতে চেয়েছিল যেখানে কোনো সেল অপারেশন নেই ](../../../../images/bricked-car.png)](https://twitter.com/internetofshit/status/1315736960082808832)
লস অফ কানেক্টিভিটি MQTT-কতৃক সামালানোর জন্য প্রয়োজনে ডিভাইস এবং সার্ভার কোডকে মেসেজ ডেলিভারি নিশ্চিত করার দায়ভার গ্রহণ করতে হবে। উদারণস্বরূপ, একটি রিপ্লাই টপিকে অতিরিক্ত মেসেজসমূহের মাধ্যমে প্রেরিত সমস্ত মেসেজসমূহ প্রয়োজনে চাওয়া এবং তা যদি না হয় তবে সেগুলো ম্যানুয়ালি একটি সারিতে থাকবে যাতে পরবর্তী রিপ্লেতে দিবে।
## কমান্ডসমূহ
কমান্ডস হচ্ছে মেসেজ যা ক্লাউড দ্বারা কোন ডিভাইসে প্রেরণ করা হয় যাতে কিছু করার নির্দেশনা দেওয়া থাকে। বেশিরভাগক্ষেত্রে একচুয়েটরের আউটপুট সম্বলিত কিছু থাকে কিন্তু এতে ডিভাইসের নিজের জন্য কিছু নির্দেশনাও থাকতে পারে (যেমনঃ রিব্যুট করা) বা এক্সট্রা টেলিমেট্রি জড়ো করা এবং রেসপন্স হিসেবে কমান্ডকে রিটার্ন করা।
![ইন্টারনেটে সংযুক্ত একটি থার্মোস্ট্যাট কমান্ড রিসিভের মাধ্যমে হিটিং সিস্টেমকে চালু করছে](../../../../images/commands.png)
হিটিং সিস্টেম চালু করার জন্য একটি থার্মোস্ট্যাট ক্লাউ থেকে কমান্ড গ্রহণ করতে পারে। যদি ক্লাউড সার্ভিস সমস্ত সেন্সর হতে প্রাপ্ত টেলিমেট্রি ডাটার উপর ভিত্তি করে সিদ্ধান্ত নেয় যে হিটিং সিস্টেমটি চালু করা জরুরি তবে তা সে অনুযায়ী কমান্ড প্রেরণ করবে।
### কমান্ডসমূহ MQTT ব্রোকারে প্রেরণ
ইন্টারনেটের মাধ্যমে নাইটলাইটকে নিয়ন্ত্রনের পরবর্তী ধাপটি হলো সার্ভার কোড কতৃক আইওটি ডিভাইসে কমান্ড প্রেরণ করা যাতে লাইটের লেভেল উপলদ্ধি মাধ্যমে লাইটকে কন্ট্রোল করা যায়।
1. সার্ভার কোডটি VS Code এ ওপেন করি।
1. নিচের লাইনটি `client_telemetry_topic` ডিক্লেয়ারের পর এড করি যা নির্ধারণ করবে কোন টপিকে কমান্ড সেন্ড করবেঃ
```python
server_command_topic = id + '/commands'
```
1. নিচের কোডটি `handle_telemetry` ফাংশেন শেষে এড করিঃ
```python
command = { 'led_on' : payload['light'] < 300 }
print("Sending message:", command)
client.publish(server_command_topic, json.dumps(command))
```
এই কোডটি একটি JSON মেসেজ `led_on` এর ভ্যলুসহ কমান্ড টপিকে পাঠায় যা লাইটের ভ্যালু ৩০০ এর বেশি বা কমের উপর ভিত্তি করে তা ট্রু বা ফলস-এ সেট হয়। যদি লাইটের ভ্যালু (`led_on`<৩০০) ৩০০ এর কম হয় তবে ট্রু সেন্ড করা হয় যাতে এলইডি অন করার নির্দেশনা থাকে।
1. কোডটি পূর্বের মতো রান করি।
1. আমাদের বাহ্যিক বা ভার্চুয়াল ডিভাইসে কতৃক শনাক্তকৃত লাইটের লেভেল অনুসারে লেভেলটি এডজাস্ট করি। গ্রহীত মেসেজ এবং প্রেরিত কমান্ডগুলো টার্মিনালে আউটপুট হিসেবে বর্ণিত হবেঃ
```output
(.venv) ➜ nightlight-server python app.py
Message received: {'light': 0}
Sending message: {'led_on': True}
Message received: {'light': 400}
Sending message: {'led_on': False}
```
> 💁 প্রতিটি সিংগেল টপিকে টেলিমেট্রি এবং কমান্ডসমূহ প্রেরণ করা হচ্ছে। যার অর্থ দাঁড়ায় একাধিক ডিভাইস থেকে টেলিমেট্রি একই টেলিমেট্রি টপিকের উপর প্রকাশিত হবে এবং একাধিক ডিভাইসের কমান্ডগুলিও একই কমান্ডের টপিকে প্রকাশিত হবে। যদি কোন নির্দিষ্ট ডিভাইসে কমান্ড প্রেরণ করতে চাই তবে একটি ইউনিক ডিভাইস আইডি নামকরণ (যেমনঃ `/commands/device1`, `/commands/device2`) করে একাধিক টপিক ব্যবহার করে পারবো। এইভাবে কোন ডিভাইস কেবল সেই এক ডিভাইসের জন্য বরাদ্দকৃত বার্তাগুলি লিসেন করতে পারে।
> 💁 আমরা [code-commands/server](code-commands/server) এই ফোল্ডারে কোডটি পাবো।
### আইওটি ডিভাইসে কমান্ডসমূহের পরিচালনা করা
এখনে যেহেতু সার্ভার হতে কমান্ডসমূহ প্রেরিত হচ্ছে সেহেতু আমরা আইওটি ডিভাইসকে পরিচালনা করার জন্য এবং এলইডিকে নিয়ন্ত্রণের জন্য তাতে কোড এড করতে পারবো।
MQTT ব্রোকার হতে কমান্ডসমূহ গ্রহণের জন্য নিচের পদক্ষেপগুলো অনুসরণ করিঃ
* [আরডুইনো Wio টার্মিনাল](wio-terminal-commands.bn.md)
* [সিংগেল বোর্ড কম্পিউটার - রাস্পবেরি পাই/ভার্চুয়াল আইওটি ডিভাইস](single-board-computer-commands.bn.md)
এই কোডটি লেখা এবং রান করা হয়ে গেলে আমরা লাইটের লেভেল চেঞ্জ করে এক্সপেরিমেন্ট করবো। লাইটের লেভেল চেঞ্জের মাধ্যমে এলইডিটিতে এবং সার্ভার আর ডিভাইসের আউটপুটটিতে লক্ষ্য রাখি।
### লস অফ কানেক্টিভিটি
যদি আইওটি ডিভাইসে কমান্ড প্রেরণের প্রয়োজন হয় কিন্তু ডিভাইসটি অফলাইনে থাকে তবে এমতাবস্থায় ক্লাউড সার্ভিসের কি করা উচিত? আবারও, উত্তরটি হলো তা নির্ভরশীল।
যদি লেটেস্ট কমান্ডটি তার পূর্বের কমান্ডটিকে ওভাররাইট করে তবে পূর্বের কমান্ডটি উপেক্ষিত হবে। যদি কোন ক্লাউড সার্ভিস প্রথমে হিটিং সিস্টেমটি চালু করার জন্য একটি কমান্ড পাঠায় তারপর হিটিং সিস্টেমটি বন্ধ করার জন্য দ্বিতীয় আরেকটি কমান্ড পাঠায় তবে অন কমান্ডটি অর্থাৎ ১ম কমান্ডটি উপেক্ষা করা হবে এবং তা রিসেন্ট হবে না।
যদি কমান্ডসমূহের ক্রমানুসারে প্রসেসের প্র্য়োজন হয় যেমন হতে পারে প্রথমে একটি রোবটের হাত উপরে উঠানো দ্বিতীয়ত সেটির গ্র্যাবার বন্ধ করা, তাই কানেক্টিভিটি পুনরায় চালু হলে কমান্ডসমূহকে নিয়মানুযায়ী প্রেরণ করা প্রয়োজন।
✅ কীভাবে ডিভাইস বা সার্ভার কোডটি নিশ্চিত হবে যে কমান্ডসমূহ সর্বদা প্রেরিত হবে এবং প্রয়োজন পরলে তা MQTT-র মাধ্যমে নিয়মানুযায়ী প্রকাশিত হবে?
---
## 🚀 চ্যালেঞ্জ
শেষ তিনটি পাঠ্যের মধ্যে চ্যালেঞ্জটি ছিল আমাদের বাড়ি, স্কুল বা কর্মক্ষেত্রে যতগুলো আইওটি ডিভাইস রয়েছে তার একটি তালিকা তৈরি করা এবং তারা মাইক্রোকন্ট্রোলার বা একক-বোর্ড কম্পিউটার বা উভয়ের মিশ্রণে নির্মিত কিনা তার সিদ্ধান্তে উপনিত হওয়া এবং তারা কী ধরনের সেন্সর ও একচুয়েটর ব্যবহার করছে তা নিয়ে চিন্তা করা।
চিন্তা করে দেখি যে এই ডিভাইসগুলো কী ধরনের মেসেজ প্রেরণ বা গ্রহণ করছে। কি ধরনের টেলিমেট্রি প্রেরণ করছে? কি মেসেজ বা কমান্ড রিসিভ করতে পারে? চিন্তা করে দেখি এগুলো কি সত্যিই সুরক্ষিত?
## লেকচার পরবর্তী কুইজ
[লেকচার পরবর্তী কুইজ](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/8)
## রিভিউ এবং স্ব-অধ্যয়ন
[MQTT Wikipedia page](https://wikipedia.org/wiki/MQTT) টি পড়ে MQTT সম্পর্কে আরো জানতে পারবো।
[Mosquitto](https://www.mosquitto.org) ব্যবহার করে MQTT ব্রোকার রান করতে ট্রাই করি এবং এটিকে আইওটি ডিভাইস ও সার্ভার কোডের সাথে সংযুক্ত করি।
> 💁 টিপ বাই ডিফল্ট Mosquitto কখনো anonymous কানেকশন অনুমোদন করে না (anonymous কানেকশনের অর্থ হচ্ছে ইউজারনেম এবং পাসওয়ার্ড ব্যাতীত কানেক্ট হওয়া) এবং যেই কম্পিউটারে এটি রান হচ্ছে সেই কম্পিউটার ব্যাতীত অন্য কানেকশন অনুমোদন করে না।
> এটিকে [`mosquitto.conf` config file](https://www.mosquitto.org/man/mosquitto-conf-5.html) এর মাধ্যমে ফিক্স করতে নিম্নের কমান্ডটি দিইঃ
>
> ```sh
> listener 1883 0.0.0.0
> allow_anonymous true
> ```
## এসাইনমেন্ট
[MQTT-এর সাথে অন্যান্য কমিউনিকেশন প্রটোকলের তুলনা করে পার্থক্য দাঁড় করানো](assignment.bn.md)

@ -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 তুলনা করে আংশিক পার্থক্য দাঁড় করাতে এবং পাওয়ার, সিক্যুরিটি এবং মেসেজ পারসিসটেন্স নিয়ে একটি প্রতিবেদন তৈরি করতে সক্ষম হয়েছে। |

@ -25,8 +25,8 @@ Install the Arduino libraries.
```ini
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
```

@ -53,8 +53,7 @@ Publish telemetry to the MQTT broker.
doc["light"] = light;
string telemetry;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, telemetry);
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());

@ -17,7 +17,7 @@ lib_deps =
knolleary/PubSubClient @ 2.8
bblanchon/ArduinoJson @ 6.17.3
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1

@ -77,8 +77,7 @@ void loop()
doc["temperature"] = temp_hum_val[1];
string telemetry;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, telemetry);
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());

@ -16,7 +16,7 @@ lib_deps =
knolleary/PubSubClient @ 2.8
bblanchon/ArduinoJson @ 6.17.3
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1

@ -100,8 +100,7 @@ void loop()
doc["soil_moisture"] = soil_moisture;
string telemetry;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, telemetry);
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());

@ -15,8 +15,8 @@ framework = arduino
lib_deps =
bblanchon/ArduinoJson @ 6.17.3
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -120,8 +120,7 @@ void loop()
doc["soil_moisture"] = soil_moisture;
string telemetry;
JsonObject obj = doc.as<JsonObject>();
serializeJson(obj, telemetry);
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());

@ -131,18 +131,27 @@ The Azure Functions CLI can be used to create a new Functions app.
1. Activate the virtual environment:
* On Windows run:
* On Windows:
* If you are using the Command Prompt, or the Command Prompt through Windows Terminal, run:
```cmd
.venv\Scripts\activate.bat
```
* If you are using PowerShell, run:
```powershell
.\.venv\Scripts\Activate.ps1
```
* On macOS or Linux, run:
```cmd
source ./.venv/bin/activate
```
> 💁 These commands should be run from the same location you ran the command to create the virtual environment. You will never need to navigate into the `.venv` folder, you should always run the activate command and any commands to install packages or run code from the folder you were in when you created the virtual environment.
1. Run the following command to create a Functions app in this folder:
```sh

@ -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,607 @@
# অ্যাপ্লিকেশন লজিককে ক্লাউডে স্থানান্তর
![A sketchnote overview of this lesson](../../../../sketchnotes/lesson-9.jpg)
> স্কেচনোটটি তৈরী করেছেন [Nitya Narasimhan](https://github.com/nitya). বড় সংস্করণে দেখার জন্য ছবিটিতে ক্লিক করতে হবে।
## লেকচার-পূর্ববর্তী কুইজ
[লেকচার-পূর্ববর্তী কুইজ](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/17)
## সূচনা
গত পাঠে আমরা শিখেছি কীভাবে ক্লাউড-ভিত্তিক আইওটি পরিষেবাতে আমাদের উদ্ভিদকে সংযুক্ত করতে হবে এবং মাটির আর্দ্রতা পর্যবেক্ষণ এবং রিলে নিয়ন্ত্রণ এর মতো কাজগুলো কীভাবে ক্লাউড থেকে করা যায়। এখন পরবর্তী পদক্ষেপটি হল সার্ভার কোডকে ক্লাউডে স্থানান্তর করা যা রিলে এর টাইমিং নিয়ন্ত্রণ করে। এই পাঠে আমরা সার্ভারবিহীন (serverless) ফাংশন দ্বারা এই কাজটি করতে শিখব।
এই লেসনে রয়েছেঃ
* [সার্ভারলেস বলতে কী বোঝায়?](#সার্ভারলেস-বলতে-কী-বোঝায়)
* [একটি সার্ভারলেস অ্যাপ্লিকেশন তৈরি](#একটি-সার্ভারলেস-অ্যাপ্লিকেশন-তৈরি-করা)
* [একটি IoT Hub ইভেন্ট ট্রিগার তৈরি](#একটি-আইওটি-হাব-ইভেন্ট-ট্রিগার-তৈরি-করা)
* [সার্ভারলেস কোড থেকে ডিরেক্ট মেথড রিকুয়েস্ট পাঠানো](#সার্ভারলেস-কোড-থেকে-ডিরেক্ট-মেথড-রিকুয়েস্ট-পাঠানো)
* [ক্লাউডে সার্ভারলেস কোড ডেপ্লয় করা](#ক্লাউডে-সার্ভারলেস-কোড-ডেপ্লয়-করা)
## সার্ভারলেস বলতে কী বোঝায়?
সার্ভারলেস বা সার্ভারবিহীন কম্পিউটিং বলতে বোঝানো হয় বিভিন্ন ধরণের ইভেন্টের প্রতিক্রিয়া হিসাবে ক্লাউডে চালিত কোডের ছোট ছোট ব্লক । ইভেন্ট ঘটলে কোড চালিত হয় এবং এটি ইভেন্ট সম্পর্কিত ডেটা পাস করে। এই ইভেন্টগুলি বিভিন্ন বিষয় সম্পর্কিত হতে পারে যেমন, ওয়েব রিকোয়েস্ট, সারিতে অপেক্ষারত বার্তাসমূহ, একটি ডাটাবেসে ডেটা পরিবর্তন করা বা আইওটি ডিভাইসগুলির মাধ্যমে আইওটি পরিষেবাতে বার্তা প্রেরণ ইত্যাদি।
![Events being sent from an IoT service to a serverless service, all being processed at the same time by multiple functions being run](../../../../images/iot-messages-to-serverless.png)
> 💁 যদি আগে 'ডাটাবেস ট্রিগার' ব্যবহারের অভিজ্ঞতা থাকে, তবে এটাকেও একই জিনিস হিসাবে ভাবা যায় যা কোন ইভেন্টের মাধ্যমে কোড ট্রিগার করার মাধ্যমে কাজ করছে, যেমন একটি সারি বা row যোগ করা ।
![When many events are sent at the same time, the serverless service scales up to run them all at the same time](../../../../images/serverless-scaling.png)
আমাদের কোডটি কেবল তখনই রান হয়, যখন ইভেন্টটি ঘটে ; অন্য সময় কোড সক্রিয় থাকেনা। ইভেন্টটি ঘটামাত্র কোডটি লোড হয় এবং তা চালানো হয়। এটি সার্ভারলেসকে খুব স্কেলেবল (scalable) করে তোলে - যদি একই সাথে অনেকগুলি ইভেন্ট ঘটে, তবে ক্লাউড সরবরাহকারী যতবার প্রয়োজন ততবার কোড চালাতে পারে। তবে এটির অনেক সুবিধা থাকলেও, এর নেতিবাচক দিকটি হল যদি আমাদেরকে ইভেন্টগুলির মধ্যে তথ্য আদান-প্রদানের দরকার হয়, তবে এটি ডাটাবেসের মতো কোথাও সংরক্ষণ করতে হবে।
আমাদের কোড একটি ফাংশন আকারে লেখা হয়েছে, যা প্যারামিটার হিসাবে ইভেন্টটির তথ্য গ্রহণ করে। এই সার্ভারলেস ফাংশনগুলি লিখতে আমরা অনেকগুলো প্রোগ্রামিং ভাষা ব্যবহার করতে পারি।
> 🎓 সার্ভারলেসকে অনেকসময় Functions as a service (FaaS) হিসাবেও উল্লেখ করা হয় কারণ প্রতিটি ইভেন্ট ট্রিগারকে কোড এ ফাংশন হিসাবে প্রয়োগ করা হয়।
নাম "সার্ভারলেস" হলেও, এটি আসলে সার্ভার ব্যবহার করে। নামকরণটি এমন হওয়ার পিছনে কারণ হল আমরা ডেভলাপার হিসাবে কোড চালানোর জন্য সার্ভার বিষয়ে কোন চিন্তাই করি না, আমাদের সকল মনোযোগ থাকে কোন ইভেন্টের প্রতিক্রিয়াতে কোডটি চালানো হচ্ছে কিনা সে বিষয়ে। ক্লাউড সরবরাহকারীর একটি সার্ভারলেস *রানটাইম* রয়েছে যা সার্ভার, নেটওয়ার্কিং, স্টোরেজ, সিপিইউ, মেমরি এবং কোড চালানোর জন্য প্রয়োজনীয় সমস্ত কিছুর ব্যবস্থা করে। এই মডেলটিতে সার্ভার নেই বলে, আমরা এই সার্ভিসের জন্য সার্ভার ব্যবস্থাপনার অর্থ প্রদান করতে পারব না। এর পরিবর্তে আমাদেরকে বরং কোডটির চলমান সময় এবং মেমরির ব্যবহারের পরিমাণের উপর অর্থ প্রদান করতে হবে।
> 💰 ক্লাউডে কোড কম খরচে রান করার সবচেয়ে ভালো উপায় হলো সার্ভারলেস। উদাহরণস্বরূপ, (এই লেসন লেখার সময়) যেকোন ক্লাউড সরবরাহকারী এই ফাংশনগুলিতে চার্জ শুরু করার আগে একমাসে 1000,000 বার রান করার সুযোগ দেয় এবং তারপরে তারা প্রতিটি 1,000,000 বার কোড এক্সেকিউট করার জন্য $0.20 চার্জ করে। যখন কোড চলছে না, তখন আমাদেরকে অর্থ প্রদান করতে হবেনা।
আইওটি ডেভলাপার হিসাবে সার্ভারলেস মডেলটি সর্বোত্তম। এখানে চাইলে আমরা এমন ফাংশন লিখতে পারি যা আমাদের ক্লাউড-হোস্টেড আইওটি পরিষেবাতে সংযুক্ত যেকোন আইওটি ডিভাইস থেকে প্রেরিত বার্তাগুলির সাথে যুক্ত থাকবে। আমাদের কোড এইসব প্রেরিত ম্যাসেজগুলোকে পরিচালনা করবে, তবে প্রয়োজন ব্যাতীত রান করবেনা।
✅ এমকিউটিটি-তে বার্তা গ্রহণের জন্য যে কোডটিকে সার্ভার কোড হিসাবে লিখেছি, সেটির দিকে আরেকবার লক্ষ্য করা যাক! কীভাবে এটি সার্ভারলেস ব্যবহার করে ক্লাউডে চলতে পারে? কীভাবে পরিবর্তন করলে, এই কোডটি সার্ভারলেস কম্পিউটিং সাপোর্ট করতে পারে?
> 💁 সার্ভারলেস মডেল কোড রান করার পাশপাশি কিছু বিষয়ে অন্যান্য ক্লাউড পরিষেবার দিকে যাচ্ছে। উদাহরণস্বরূপ, সার্ভারহীন ডাটাবেসগুলি ক্লাউডে পাওয়া যাচ্ছে যেখানে ডাটাবেসে পাঠানো রিকুয়েস্টের সংখ্যা অনুসারে ফী প্রদান করতে হবে, যেমন একটি query বা row যুক্ত করা, তবে সাধারণত কতটা কাজ করা হচ্ছে তার ভিত্তিতে মূল্য নির্ধারণ করা হয়। উদাহরণস্বরূপ, প্রাইমারি কী এর ভিত্তিতে একটি সারি সিলেক্ট করা হলে, তার খরচ অনেক কম হবে - অনেকগুলো টেবল যোগ করে একটি জটিল কাজ করার তুলনায়।
## একটি সার্ভারলেস অ্যাপ্লিকেশন তৈরি করা
মাইক্রোসফটের সার্ভারলেস অ্যাপ্লিকেশন কে বলা হয় Azure Function ।
![The Azure Functions logo](../../../../images/azure-functions-logo.png)
নীচের সংক্ষিপ্ত ভিডিওটিতে অ্যাজুর ফাংশনগুলির একটি ওভারভিউ রয়েছে
[![Azure Functions overview video](https://img.youtube.com/vi/8-jz5f_JyEQ/0.jpg)](https://www.youtube.com/watch?v=8-jz5f_JyEQ)
> 🎥 ভিডিও দেখতে উপরের ছবিতে ক্লিক করতে হবে
✅ এই পর্যায়ে একটু সময় নিয়ে কিছু পড়াশোনা করা উচিত। আর অ্যাজুর ফাংশন এর ব্যপারে জানতে হলে [Microsoft Azure Functions documentation](https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=academic-17441-jabenn) পড়া একটি ভালো উপায়।
অ্যাজুর ফাংশন লিখতে হলে, আমাদের পছন্দমতো কোন ল্যাংগুয়েজে অ্যাজুর ফাংশন এপ এ কাজ করা শুরু করতে হবে। Python, JavaScript, TypeScript, C#, F#, Java, এবং Powershell খুবসহজেই ব্যবহার করা যায়। এই অধ্যায়ে আমরা পাইথন ব্যবহার করা অ্যাজুর ফাংশন এপ লেখা শিখবো।
> 💁 অ্যাজুর ফাংশন আবার কাস্টম হ্যান্ডলারও সাপোর্ট করে, তাই যেকোন ভাষায় এমনকি COBOLএর মতো পুরনো ল্যাঙ্গুয়েজেও তা লেখা যায়, যদি HTTP রিকোয়েস্ট সাপোর্ট করে।
ফাংশন অ্যাপ্লিকেশনগুলিতে এক বা একাধিক *ট্রিগার* থাকে - এমন ক্রিয়াকলাপ যা ইভেন্টগুলিতে প্রতিক্রিয়া জানায়। আমাদের একটি ফাংশন অ্যাপ্লিকেশনের মধ্যে একাধিক ট্রিগার থাকতে পারে, সাধারণ একটি কনফিগারেশন নিয়েই। উদাহরণস্বরূপ, ফাংশন অ্যাপ্লিকেশনটির জন্য কনফিগারেশন ফাইলে আইওটি হাবের সংযোগের সকল তথ্য থাকতে পারে এবং অ্যাপ্লিকেশনটি্র সমস্ত ফাংশন এটি সংযোগ করতে এবং ইভেন্টগুলির তথ্য গ্রহণে এটি ব্যবহার করতে পারে
### কাজ - Azure Functions tooling ইন্সটল করা
অ্যাজুর ফাংশনগুলির একটি দুর্দান্ত বৈশিষ্ট্য হল এগুলি আমরা লোকালি চালাতে পারি। ক্লাউডের সমান রানটাইমে এটি আমাদের কম্পিউটারে চালানো যেতে পারে, যা আমাদেরকে আইওটি বার্তাগুলির প্রতিক্রিয়া জানায় এবং লোকালি চলতে পারে এমন কোড লেখার সুযোগ দেয়। এমনকি ইভেন্টগুলি হ্যান্ডেল হওয়ার সাথে সাথে আমরা নিজের কোডটি ডিবাগ করতে পারব। একবার কোড নিয়ে সন্তুষ্ট হলেই, এটি আমরা ক্লাউডে স্থাপন করতে পারবো।
Azure Functions tooling আমরা CLI এর মাধ্যমে ব্যবহার করতে পারি যাকে Azure Functions Core Tools ও বলা হয়।
1. Azure Functions Core Tools ইনস্টল করার জন্য [Azure Functions Core Tools documentation](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=academic-17441-jabenn) নির্দেশাবলী অনুসরণ করি।
1. VS Code এ Azure Functions extension ইন্সটল করতে হবে। এই এক্সটেনশনে মাধ্যমে Azure functions তৈরী, ডিবাগ এবং ডেপ্লয় করা যাবে। প্রয়োজনীয় নির্দেশনা [Azure Functions extension documentation](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions&WT.mc_id=academic-17441-jabenn) এ রয়েছে।
আমরা যখন ক্লাউডে অ্যাজুর ফাংশন অ্যাপ্লিকেশন শুরু করবো, তখন অ্যাপ্লিকেশন ফাইল এবং লগ ফাইলের মতো জিনিসগুলি সেভ রাখতে খুব অল্প পরিমাণে ক্লাউড স্টোরেজ ব্যবহার করা দরকার। যখন লোকালি আমরা ফাংশন অ্যাপ্লিকেশন চালাই তখন আমাদেরকে ক্লাউড স্টোরেজের সাথে সংযোগ স্থাপন করতে হবে, তবে প্রকৃত ক্লাউডের স্টোরেজ ব্যবহারের পরিবর্তে একটি স্টোরেজ এমুলেটর ব্যবহার করতে হবে যেমন [Azurite](https://github.com/Azure/Azurite)। এটি লোকাল ভাবে চলে, তবে ক্লাউড স্টোরেজের মতো কাজ করে।
> 🎓 অ্যাজুরে, অ্যাজুর ফাংশনগুলি যে স্টোরেজ ব্যবহার করে তা একটি অ্যাজুর স্টোরেজ অ্যাকাউন্ট। এই অ্যাকাউন্টগুলি ফাইল, ব্লবস, টেবিলগুলিতে ডেটা সংরক্ষণ করতে পারে। এখানে চাইলেই অনেকগুলি অ্যাপ্লিকেশনের মধ্যে একটি স্টোরেজ অ্যাকাউন্ট ভাগ করা যায় যেমন একটি ফাংশন অ্যাপ এবং একটি ওয়েব অ্যাপ্লিকেশনে একই স্টোরেজ ব্যবহার করা।
1. Azurite হলো একটি Node.js এপ। তাই এটির জন্য আগে Node.js ইন্সটল করতে হবে [Node.js website](https://nodejs.org/)এ সকল নির্দেশনা আছে। ম্যাক ব্যবহারকারীরা [Homebrew](https://formulae.brew.sh/formula/node) থেকেও ইন্সটল করতে পারবে।
1. নিচের কমান্ড ব্যবহার করে Azurite ইন্সটল করি :
```sh
npm install -g azurite
```
1. `azurite` নামে ফোল্ডার খুলি, এটির ডেটা স্টোর করার জন্য
```sh
mkdir azurite
```
1. Azurite রান করি -
```sh
azurite --location azurite
```
Azurite স্টোরেজ ইম্যুলেটর launch হয়ে লোকাল ফাংশন রানটাইমের জন্য অপেক্ষা করবে
```output
➜ ~ azurite --location azurite
Azurite Blob service is starting at http://127.0.0.1:10000
Azurite Blob service is successfully listening at http://127.0.0.1:10000
Azurite Queue service is starting at http://127.0.0.1:10001
Azurite Queue service is successfully listening at http://127.0.0.1:10001
Azurite Table service is starting at http://127.0.0.1:10002
Azurite Table service is successfully listening at http://127.0.0.1:10002
```
### কাজ - একটি অ্যাজুর ফাংশন প্রজেক্ট তৈরি
Azure Functions CLI দ্বারা নতুন Functions app তৈরী করা যাবে
1. ফাংশন এপ এর জন্য ফোল্ডার খুলে সেখানে যাই, নাম দিই `soil-moisture-trigger`
```sh
mkdir soil-moisture-trigger
cd soil-moisture-trigger
```
1. ফোল্ডারে পাইথন ভার্চুয়াল এনভায়রনমেন্ট তৈরী করি
```sh
python3 -m venv .venv
```
1. ভার্চুয়াল এনভায়রনমেন্ট এক্টিভেট করি
* উইন্ডোজে:
```cmd
.venv\Scripts\activate.bat
```
* macOS বা Linux এ:
```cmd
source ./.venv/bin/activate
```
1. এই ফোল্ডারে Functions app তৈরীর জন্য নিম্নের কমান্ড রান করি:
```sh
func init --worker-runtime python soil-moisture-trigger
```
এটি বর্তমান ফোল্ডারের ভিতরে তিনটি ফাইল তৈরি করবে:
* `host.json` - এই JSON ডকুমেন্টে ফাংশন অ্যাপ্লিকেশনের জন্য সেটিংস রয়েছে। এই সেটিংস পরিবর্তন করতে হবে না।
* `local.settings.json` - এই JSON ডকুমেন্টে লোকালি চলাকালীন আমাদের অ্যাপ্লিকেশন যে সেটিংস ব্যবহার করবে সেগুলি আছে, যেমন আইওটি হাবের জন্য সংযোগ স্ট্রিং । এই সেটিংসটি কেবল লোকাল, এবং সোর্স কোড নিয়ন্ত্রণে যুক্ত করা উচিত নয়। আমরা যখন ক্লাউডে অ্যাপ স্থাপন করব তখন এই সেটিংসটি স্থাপন করা হবে না, এর পরিবর্তে আমাদের সেটিংস অ্যাপ্লিকেশন সেটিংস থেকে লোড হবে। এই পাঠের পরবর্তী অংশে বিষয়টি আলোচনা করা হবে।
* `requirements.txt` - এটি একটি [Pip requirements file](https://pip.pypa.io/en/stable/user_guide/#requirements-files) যা প্রয়োজনীয় পিপ ফাইলগুলো ধারণ করে।
1. এখানে `local.settings.json` ফাইলটির স্টোরেজ অ্যাকাউন্টের জন্য একটি সেটিংস রয়েছে যা অ্যাপ্লিকেশন ব্যবহার করবে। এটির ডিফল্ট হিসেবে একটি ফাঁকা সেটিং আছে, সুতরাং এটি সেট করা দরকার। Azurite স্থানীয় স্টোরেজ এমুলেটর সাথে সংযোগ করতে, এই মানটি নিম্নলিখিত ভাবে সেট করতে হবে:
```json
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
```
1. প্রয়োজনীয় ফাইলগুলি ব্যবহার করে পিপ প্যাকেজ ইনস্টল করতে হবে:
```sh
pip install -r requirements.txt
```
> 💁 প্রয়োজনীয় পিপ প্যাকেজগুলি এই ফাইলে থাকা দরকার, যাতে ফাংশন অ্যাপ্লিকেশনটি যখন ক্লাউডে স্থাপন করা হবে, তখন রানটাইম যেন এটি নিশ্চিত করতে পারে যে সঠিক প্যাকেজগুলি ইনস্টল হয়েছে।
1. সবকিছু সঠিকভাবে কাজ করছে তা পরীক্ষা করতে আমরা ফাংশন রানটাইম শুরু করতে পারি। এটি করার জন্য নিম্নলিখিত কমান্ডটি রান দিই:
```sh
func start
```
দেখা যাবে যে রানটাইম শুরু হয়েছে কিন্তু তা রিপোর্ট করবে যে এটি কোন জব ফাংশন (ট্রিগার) খুঁজে পায় নি।
```output
(.venv) ➜ soil-moisture-trigger func start
Found Python version 3.9.1 (python3).
Azure Functions Core Tools
Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit)
Function Runtime Version: 3.0.15417.0
[2021-05-05T01:24:46.795Z] No job functions found.
```
> ⚠️ কোন ফায়ারওয়াল নোটিফিকেশন আসলে, একসেস দিতে হবে কারণ `func` এপ্লিকেশনটির এই নেটওয়ার্কে কাজ করতে হবে।
> ⚠️ ম্যাক-ওএস ব্যবহারকারীদের আউটপুটটিতে ওয়ার্নিং থাকতে পারে:
>
> ```output
> (.venv) ➜ soil-moisture-trigger func start
> Found Python version 3.9.1 (python3).
>
> Azure Functions Core Tools
> Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit)
> Function Runtime Version: 3.0.15417.0
>
> [2021-06-16T08:18:28.315Z] Cannot create directory for shared memory usage: /dev/shm/AzureFunctions
> [2021-06-16T08:18:28.316Z] System.IO.FileSystem: Access to the path '/dev/shm/AzureFunctions' is denied. Operation not permitted.
> [2021-06-16T08:18:30.361Z] No job functions found.
> ```
>
> যতক্ষণ ফাংশন অ্যাপ্লিকেশনটি সঠিকভাবে শুরু হচ্ছে এবং চলমান ফাংশনগুলির তালিকা ঠিকভাবে দেখাচ্ছে, ততক্ষণ এসব উপেক্ষা করা যায়। এখানে [Microsoft Docs Q&A](https://docs.microsoft.com/answers/questions/396617/azure-functions-core-tools-error-osx-devshmazurefu.html?WT.mc_id=academic-17441-jabenn) তেও এমনই বলা হয়েছে।
1. এবার `ctrl+c` ব্যবহার করে Functions app বন্ধ করি।
1. VS Code এ বর্তমান ফোল্ডারটি ওপেন করি:
```sh
code .
```
ভিএস কোড আমাদের ফাংশন প্রজেক্ট সনাক্ত করে একটি নোটিফিকেশন দেখিয়ে বলবে:
```output
Detected an Azure Functions Project in folder "soil-moisture-trigger" that may have been created outside of
VS Code. Initialize for optimal use with VS Code?
```
![The notification](../../../../images/vscode-azure-functions-init-notification.png)
এখানে **Yes** সিলেক্ট করতে হবে।
1. ভিএস কোড টার্মিনালে ভার্চুয়াল এনভায়রনমেন্ট রান করছে - এটি নিশ্চিত করতে হবে
## একটি আইওটি হাব ইভেন্ট ট্রিগার তৈরি করা
ফাংশন অ্যাপ্লিকেশন হলো আমাদের সার্ভারলেস কোডের শেল। আইওটি হাব ইভেন্টগুলির প্রতিক্রিয়া জানাতে, এই অ্যাপ্লিকেশনটিতে একটি আইওটি হাব ট্রিগার যুক্ত করা যায়। এই ট্রিগারটি আইওটি হাবে আসা বার্তাপ্রবাহের সাথে সংযুক্ত থাকা দরকার এবং তাদের প্রতিক্রিয়া জানানোর সক্ষমতা অর্জন করতে হবে। নিয়মিতভাবে বার্তা পেতে আমাদের ট্রিগারটি আইওটি হাবে *event hub compatible endpoint* এর সাথে সংযোগ স্থাপন করা দরকার।
আইওটি হাবটি Azure Event Hubs নামে পরিচিত আরেকটি অ্যাজুর সার্ভিসের সাথে যুক্ত। ইভেন্ট হাবস এমন একটি পরিষেবা যা আমাদেরকে বার্তা প্রেরণ এবং গ্রহণ করতে দেয়, আইওটি হাবটি তখন আইওটি ডিভাইসের জন্য অতিরিক্ত বৈশিষ্ট্য যুক্ত করতে পারে। আইওটি হাব থেকে যেভাবে আমরা বার্তা গ্রহণ করেছি, ইভেন্ট হাবেও একদম একইভাবে ব্যবহার করা যায়।
✅ কিছু গবেষণা করা যাক: [Azure Event Hubs documentation](https://docs.microsoft.com/azure/event-hubs/event-hubs-about?WT.mc_id=academic-17441-jabenn) থেকে ইভেন্ট হাব সম্পর্কে একটি সামগ্রিক ধারণা লাভ করে, এটির সাধারণ পর্যায়ের বৈশিষ্ট্যগুলোকে আইওটি হাবের সাথে তুলনা করি।
আইওটি হাবের সাথে সংযোগ স্থাপনের জন্য যেকোন আইওটি ডিভাইসকে একটি গোপন কী ব্যবহার করতে হবে যা কেবলমাত্র অনুমোদিত ডিভাইসগুলি সংযোগ করতে পারে। বার্তাগুলি পড়ার জন্য সংযোগ করার সময়ও একই বিষয় প্রযোজ্য, আমাদের কোডটিতে আইওটি হাবের বিবরণ সহ একটি গোপন কী যুক্ত connection string প্রয়োজন হবে।
> 💁 আমরা ডিফল্ট যে connection string পাই, সেটিতে **iothubowner** এর পার্মিশন থাকে যা কোন কোডে থাকলে - সম্পূর্ণ আইওটি হাবে একসেস/অনুমতি প্রদান করে। এটিকে আসলে আমাদের প্রয়োজনীয় অনুমতিগুলির সর্বনিম্ন স্তরে অর্থাৎ কম পার্মিশনে রাখা উচিত। পরবর্তী পাঠে এটি নিয়ে বিষদ আলোচনা হবে।
ট্রিগারটি একবার সংযুক্ত হয়ে গেলেই, আইওটি হাবের কাছে প্রেরিত প্রতিটি বার্তার জন্য - ফাংশনের কোডটি কল করা হবে,তা সেই ম্যাসেজ যে ডিভাইস থেকেই প্রেরিত হোক না কেন।
### কাজ - Event Hub compatible endpoint connection string তৈরী
1. VS Code টার্মিনাল থেকে নিম্নের কমান্ড রান করি:
```sh
az iot hub connection-string show --default-eventhub \
--output table \
--hub-name <hub_name>
```
এখানে `<hub_name>` এর পরিবর্তে আমাদের ব্যবহৃত হাবের নামটি ব্যবহার করতে হবে।
1. এবার VS Code এ `local.settings.json` ফাইলটি ওপেন করে, `Values` অংশের নিম্নের অতিরিক্ত ভ্যালুগুলো যোগ করি:
```json
"IOT_HUB_CONNECTION_STRING": "<connection string>"
```
এখানে আগের স্টেপ থেকে পাওয়া ভ্যালু্টি `<connection string>` এর জায়গায় বসাই। JSON ফাইলটি সঠিকভাবে তৈরী করতে আমাদেরকে উপরের লাইনের পরে কমা যুক্ত করতে হবে।
### কাজ - ইভেন্ট ট্রিগার তৈরী
আমরা এখন ইভেন্ট ট্রিগার তৈরী করার কাজ শুরু করতে পারি।
1. এখন `soil-moisture-trigger` ফোল্ডার থেকে VS Code terminal চালু করে নিম্নের কমান্ড রান করি:
```sh
func new --name iot-hub-trigger --template "Azure Event Hub trigger"
```
এটি `iot-hub-trigger` নামে নতুন একটি ফাংশন তৈরী করবে। এই ট্রিগারটি Event Hub compatible endpoint এর সাথে সংযুক্ত হবে। এখন আমরা ইভেন্ট হাব ট্রিগার নিয়ে কাজ করতে পারবো।
এখন দেখা যাবে `soil-moisture-trigger` ফোল্ডারের ভেতরে `iot-hub-trigger` নামে আরেকটি ফোল্ডার তৈরী হবে যেটিতে ফাংশন রয়েছে। এই ফোল্ডারে নিম্নলিখিত ফাইলগুলো থাকবে:
* `__init__.py` - এই পাইথন ফাইলে ট্রিগার রয়েছে এবং এটিকে মডিউল হিসাবে ব্যবহারযোগ্য করার জন্য সাধারণ নীতি অনুসারে এভাবে নামকরণ করা হয়েছে।
এই ফাইলে অন্তর্ভুক্ত কোড:
```python
from typing import List
import logging
import azure.functions as func
def main(events: List[func.EventHubEvent]):
for event in events:
logging.info('Python EventHub trigger processed an event: %s',
event.get_body().decode('utf-8'))
```
এই ট্রিগারের মূল চাবিকাঠি রয়েছে `main` ফাংশনে । আইওটি হাব থেকে ইভেন্টের সাথে এই ফাংশনকেই কল করা হয়। ফাংশনটিতে `events` নামে একটি প্যারামিটার রয়েছে যেটিতে `EventHubEvent` লিস্ট রয়েছে। এই লিস্টের প্রতিটি ইভেন্ট মূলত আইওটি হাবে পাঠানো এক একটি ম্যাসেজ যাতে এনোটেশনের মত প্রপার্টিও অন্তর্ভূক্ত থাকে যেমনটা আমরা গত লেসনে দেখেছি।
এই ট্রিগারটি প্রতিটি ইভেন্ট একটি একটি করে নয়, বরং অনেকগুলো ইভেন্টের একটি লিস্ট একসাথে নিয়ে কাজ করে। যখন প্রথমবার ট্রিগার রান করা হয়, তখন এটি আইওটি হাবের অসমাপ্ত ইভেন্টগুলোর কাজ আগে সমাপ্ত করে। তারপর যদি খুব অল্প সময়ের ভেতরে হাবে অনেকগুলো ইভেন্ট পাঠানো না হয়, তাহলে এটি একটি ইভেন্ট সম্বলিত লিস্ট নিয়ে কাজ শুরু করে দিবে।
এই ফাংশন মূলত লিস্ট ধরে কাজ করে এবং ইভেন্টগুলো নথিবদ্ধ রাখে।
* `function.json` - এটিতে ট্রিগারের কনফিগারেশন থাকে যা মূলত `bindings` অংশে আমরা দেখি। বাইন্ডিং হলো মূলত Azure Functions এবং অন্যান্য Azure services এর মধ্যকার সংযোগ। এটিতে input binding থাকে, কোন একটি ইভেন্ট হাবের জন্য - যা ইভেন্ট হাবের সাথে সংযুক্ত হয় এবং ডেটা গ্রহণ করে।
> 💁 এছাড়াও আমরা আউটপুট বাইন্ডিং ব্যবহার করতে পারি যা কোন ফাংশনের আউটপুটকে আরেকটি ডিভাইসে প্রেরণ করতে পারে। যেমন, কোন ডেটাবেসের সাথে আউটপুট বাইন্ডিং যোগ করে ফাংশন দ্বারা আইওটি হাবের সাথে রিটার্ন করতে দিলে - সকল ডেটা স্বংক্রিয়ভাবেই সেই ডেটাবেস এ চলে আসবে।
✅ এবার কিছু গবেষণা করা যাক: বাইন্ডিংস নিয়ে [Azure Functions triggers and bindings concepts documentation](https://docs.microsoft.com/azure/azure-functions/functions-triggers-bindings?tabs=python&WT.mc_id=academic-17441-jabenn)পড়ে আরো জেনে নিই এই বিষয়ে ।
`bindings` অংশে এর কনফিগারেশনগুলো রয়েছে। এর গুরুত্বপূর্ণ কিছু ভ্যালু হলো :
* `"type": "eventHubTrigger"` - এটির অর্থ হলো ফাংশনকে ইভেন্ট হাব থেকে ইভেন্টের ডেটা গ্রহণ করতে হবে।
* `"name": "events"` - এই প্যারামিটারটি ইভেন্ট হাবের ইভেন্টের জন্য ব্যবহৃত হয়। এটি পাইথন কোডের `main` function এর সাথে প্যারামিটার মিলিয়ে কাজ করে।
* `"direction": "in"` - এটি ইনপুট বাইন্ডিং, যেখানে ইভেন্ট হাব থেকে ফাংশনে ডেটা আসে।
* `"connection": ""` - কানেকশন স্ট্রিং থেকে ডেটা গ্রহণের যে সেটিং - সেটির সংজ্ঞা নির্ধারণ করে। লোকালি রান করলে, সেটি `local.settings.json` ফাইল থেকে সেটিংস রীড করে।
> 💁 এই connection string কিন্তু `function.json` ফাইলে স্টোর করা যাবেনা, এটি সেটিংস থেকেই রীড করতে হবে। এটি এভাবে সাজানো হয়েছে যাতে আকস্মিকভাবে কানেকশন স্ট্রিং প্রকাশিত হয়ে না যায়।
1. `"connection"` এর ভ্যালু `function.json` ফাইল থেকে নিতে হবে যাতে নতুন ভ্যালুগুলো `local.settings.json` ফাইলে থাকে:
```json
"connection": "IOT_HUB_CONNECTION_STRING",
```
> 💁 মনে রাখতে হবে - এটি যেন সেটিংস এ পয়েন্ট করে, এবং কানেকশন স্ট্রিং যেন এখানে না থাকে।
### কাজ - ইভেন্ট ট্রিগার রান করা
1. এটা নিশ্চিত করতে হবে যে আমরা যেন আইওটি হাব ইভেন্ট মনিটরে রান না করি। এটি এবং ফাংশন এপ্লিকেশন যদি একসাথে রান করে, তবে ফাংশন এপ যথাযথভাবে ইভেন্টের সাথে সঠিকভাবে কানেক্ট হতে পারবেনা, ফলে ইভেন্টের ডেটাও ঠিকমতো পাওয়া যাবেনা।
> 💁 একাধিক এপ্লিকেশন এখানে বিভিন্ন *consumer groups* ব্যবহার করে আইওটি হাব এন্ডপয়েন্টের সাথে যুক্ত হবে। এই সংক্রান্তে আরো বিস্তারিত আমরা পরবর্তী একটি অধ্যায়ে জানবো।
1. Functions app রান করার জন্য, VS Code terminal থেকে নিম্নের কোডগুলো রান দিই
```sh
func start
```
ফাংশন এপ চালু হয়ে, `iot-hub-trigger` ফাংশনটি খুঁজে নিবে । তারপর এটি আগে থেকেই আইওটি হাবে আসা ইভেন্টসমূহ প্রসেস করবে।
```output
(.venv) ➜ soil-moisture-trigger func start
Found Python version 3.9.1 (python3).
Azure Functions Core Tools
Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit)
Function Runtime Version: 3.0.15417.0
Functions:
iot-hub-trigger: eventHubTrigger
For detailed output, run func with --verbose flag.
[2021-05-05T02:44:07.517Z] Worker process started and initialized.
[2021-05-05T02:44:09.202Z] Executing 'Functions.iot-hub-trigger' (Reason='(null)', Id=802803a5-eae9-4401-a1f4-176631456ce4)
[2021-05-05T02:44:09.205Z] Trigger Details: PartionId: 0, Offset: 1011240-1011632, EnqueueTimeUtc: 2021-05-04T19:04:04.2030000Z-2021-05-04T19:04:04.3900000Z, SequenceNumber: 2546-2547, Count: 2
[2021-05-05T02:44:09.352Z] Python EventHub trigger processed an event: {"soil_moisture":628}
[2021-05-05T02:44:09.354Z] Python EventHub trigger processed an event: {"soil_moisture":624}
[2021-05-05T02:44:09.395Z] Executed 'Functions.iot-hub-trigger' (Succeeded, Id=802803a5-eae9-4401-a1f4-176631456ce4, Duration=245ms)
```
এই ফাংশনের প্রতিটি কলে `Executing 'Functions.iot-hub-trigger'` অথবা `Executed 'Functions.iot-hub-trigger'` ব্লকগুলো আউটপুটে আসবে। এতে করে আমরা জানতে পারবো প্রতিটি ফাংশন কলে কতটি ম্যাসেজ প্রসেস করা হয়েছে।
> যদি নিচের এই এররটি আসে:
```output
The listener for function 'Functions.iot-hub-trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused.
```
তাহলে, এটা দেখতে হবে যে Azurite চলছে কিনা এবং আমরা `local.settings.json`ফাইলে `AzureWebJobsStorage` কে `UseDevelopmentStorage=true` করেছি কিনা সেই বিষয়টিও আমাদের নিশ্চিত করতে হবে।
1. এখন আমাদেরকে আমাদের আইওটি ডিভাইস চলছে কিনা খেয়াল রাখটে হবে এবং দেখতে পাব যে ফাংশন এপ এ মাটির আর্দ্রতার নতুন মানগুলো দেখাচ্ছে।
1. Functions app বন্ধ করে তা Restart করি। দেখা যাবে এটি আর আগের ম্যাসেজগুলো প্রসেস করছেনা, কেবল নতুন ম্যাসেজগুলো নিয়েই কাজ করছে।
> 💁 VS Code থেকেই ফাংশন ডিবাগ করা যায়। প্রতিটি লাইনের শুরুতে বর্ডারের অংশে ক্লিক করে অথবা কার্সরকে কোন লাইনে রেখে তারপর *Run -> Toggle breakpoint* এ গিয়ে বা `F9` প্রেস করার মাধ্যমে ব্রেকপয়েন্ট সেট করা যায়। এছাড়াও ডিবাগার launch করার জন্য *Run -> Start debugging* এ গিয়ে বা `F5` প্রেস করে অথবা আমরা *Run and debug* এ গিয়ে **Start debugging** এ ক্লিক করতে হবে। এখান থেকে ইভেন্ট প্রসেসিং এর ডিটেইলস জানা যাবে।
## সার্ভারলেস কোড থেকে ডিরেক্ট মেথড রিকুয়েস্ট পাঠানো
এখন পর্যন্ত আমাদের ফাংশন অ্যাপ্লিকেশনটি আইওটি হাব থেকে ইভেন্ট হাবের সামঞ্জস্যপূর্ণ এন্ড পয়েন্টটি ব্যবহার করে ডেটা গ্রহণ করছে। আমাদেরকে এখন আইওটি ডিভাইসে কমান্ড প্রেরণ করতে হবে। এটি *রেজিস্ট্রি ম্যানেজার* এর মাধ্যমে আইওটি হাবের সাথে একটি আলাদা সংযোগ দ্বারা করা হয়। রেজিস্ট্রি ম্যানেজার এমন একটি ট্যুল যা আমাদেরকে আইওটি হাবের সাথে কী কী ডিভাইসগুলি নিবন্ধভুক্ত রয়েছে তা দেখতে এবং ডিভাইসগুলোর সাথে যোগাযোগ করার জন্য ক্লাউড থেকে ডিভাইসে ম্যাসেজ পাঠানোর সুযোগ দেয়। এক্ষেত্রে direct method requests বা ডিভাইস টুইন আপডেট করার মাধ্যমে তা করা হয়। এছাড়াও আমরা এটি দ্বারা আইওটি হাব থেকে আইওটি ডিভাইসগুলি নিবন্ধকরণ, আপডেট করতে বা ডিলিট করতে পারবো।
Registry Manager এর সাথে কানেক্ট করার জন্য Connection String দরকারঃ
### কাজ - Registry Manager এর জন্য connection string নেয়া
1. নিচের কমান্ড রান করি:
```sh
az iot hub connection-string show --policy-name service \
--output table \
--hub-name <hub_name>
```
এখানে `<hub_name>` এর জায়গায় আমাদের ব্যবহৃত নামটি বসাই।
*ServiceConnect* পলিসির `--policy-name service` প্যারামিটারের মাধ্যমে কানেকশন স্ট্রিং চাওয়া হয়েছে। আমরা যখন connection string এর রিকুয়েস্ট করি, পার্মিশনগুলো প্রয়োজনমতো ঠিক করতে পারবো। এখানে ServiceConnect পলিসি আইওটি ডিভাইসে কানেক্ট করে ম্যাসেজ পাঠানোর সুযোগ দেয়।
✅ কিছু গবেষণা করা যাক: [IoT Hub permissions documentation](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-security#iot-hub-permissions?WT.mc_id=academic-17441-jabenn) থেকে বিভিন্ন পলিসি সম্পর্কে জানতে হবে।
1. VS Code এ `local.settings.json` ফাইলটি ওপেন করি। তারপর `Values` অংশে নিচের ব্যালুগুলো যোগ করি:
```json
"REGISTRY_MANAGER_CONNECTION_STRING": "<connection string>"
```
এখানে আগের স্টেপ থেকে পাওয়া ভ্যালু্টি `<connection string>` এর জায়গায় বসাই। JSON ফাইলটি সঠিকভাবে তৈরী করতে আমাদেরকে উপরের লাইনের পরে কমা যুক্ত করতে হবে।
### কাজ - ডিভাইসে direct method request পাঠানো
1. Registry Manager এর SDK সহজেই Pip package এর মাধ্যমে পাওয়া যাবে। `requirements.txt` ফাইলে নিচের লাইনগুলো যোগ করি প্যাকেজের ডিপেন্ডেন্সি এড করার জন্য:
```sh
azure-iot-hub
```
1. Pip package ইন্সটল করার জন্য এটি নিশ্চিত করতে হবে যে আমরা virtual environment এক্টিভেট করেই ভিএস কোডে কাজ করছি :
```sh
pip install -r requirements.txt
```
1. `__init__.py` ফাইলে নিচের ইম্পোর্টগুলো যুক্ত করি:
```python
import json
import os
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import CloudToDeviceMethod
```
এটি কিছু সিস্টেম লাইব্রেরি এবং Registry Manager সাথে ইন্টারঅ্যাক্ট করতে এবং direct method requests প্রেরণের জন্য আরো কিছু লাইব্রেরি ইম্পোর্ট করে।
1. `main` মেথড থেকে কোডগুলো সরিয়ে ফেলি, তবে মেথডটি রাখতে হবে।
1. যখন একাধিক বার্তা গৃহীত হয়, কেবলমাত্র শেষেরটিকেই এটি প্রসেস করে কারণ এটি হল বর্তমান সময়ে মাটির আর্দ্রতা। এটির আগে থেকে আসা বার্তাগুলি প্রক্রিয়া করা নিষ্প্রয়োজন। এক্ষন `events` প্যারামিটার থেকে সর্বেশেষ ম্যাসেজ পেতে হলে নিচের কোডগুলো যুক্ত করতে হবে:
```python
event = events[-1]
```
1. তারপর নিম্নের কোডগুলো যুক্ত করি:
```python
body = json.loads(event.get_body().decode('utf-8'))
device_id = event.iothub_metadata['connection-device-id']
logging.info(f'Received message: {body} from {device_id}')
```
এই কোডটি আইওটি ডিভাইস থেকে আসা JSON ম্যাসেজের যে ইভেন্ট বডি রয়েছে তা সংগ্রহ করে।
তারপর এটি ম্যাসেজের সাথে আসা এনোটেশন থেকে ডিভাইস আইডি পেয়ে যায়। ইভেন্টের বডি তে টেলিমেট্রি হিসেবে আসা ম্যাসেজগুলো, `iothub_metadata` ডিকশনারি যেটিতে আবার প্রেরক (Sender) এর ডিভাইস আইডি এবং সময় উল্লেখিত থাকে।
সকল তথ্য সংরক্ষিত (logged) থাকে। Function app কে লোকালি রান করলে এই সংরক্ষণের বিষয়টি (logging) টার্মিনালে দেখা যাবে।
1. তারপর নিচের কোডগুলো যুক্ত করি:
```python
soil_moisture = body['soil_moisture']
if soil_moisture > 450:
direct_method = CloudToDeviceMethod(method_name='relay_on', payload='{}')
else:
direct_method = CloudToDeviceMethod(method_name='relay_off', payload='{}')
```
এই কোডটি ম্যাসেজ থেকে মাটির আর্দ্রতা পায়। তারপর এটি মাটির আর্দ্রতা চেক করে এবং আর্দ্রতার মানের উপর নির্ভর করে একটি helper class তৈরী করে direct method request পাঠানোর জন্য যেখানে `relay_on` বা `relay_off` করা যাবে। এটিতে payload এর প্রয়োজন নেই, তাই একটি ফাঁকা JSON document পাঠানো যাবে।
1. নিচের কোড যুক্ত করি:
```python
logging.info(f'Sending direct method request for {direct_method.method_name} for device {device_id}')
registry_manager_connection_string = os.environ['REGISTRY_MANAGER_CONNECTION_STRING']
registry_manager = IoTHubRegistryManager(registry_manager_connection_string)
```
এই কোডটি `local.settings.json` ফাইল থেকে `REGISTRY_MANAGER_CONNECTION_STRING` ওপেন করবে। এই ফাইলের ভ্যালুগুলো এনভায়রনমেন্ট ভ্যারিয়েবল হিসেবে প্রদর্শন করা হয় এবং এগুলো `os.environ` ফাংশন (সকল এনভায়রনমেন্ট ভ্যারিয়েবল ডিকশনারি) দ্বারা ব্যবহারযোগ্য করা যায় ।
> 💁 এই কোডটি ক্লাউডে চলতে থাকলে,`local.settings.json` এর ভ্যালুগুলো *Application Settings* হিসেবে পাঠানো হয় যা এনভায়রনমেন্ট ভ্যারিয়েবল থেকে ব্যবহার করা যায়।
কোডটি পরে সংযোগ স্ট্রিংটি ব্যবহার করে রেজিস্ট্রি ম্যানেজার হেল্পার ক্লাসের একটি পরিস্থিতি তৈরি করে।
1. নিম্নের কোড যোগ করি:
```python
registry_manager.invoke_device_method(device_id, direct_method)
logging.info('Direct method request sent!')
```
এই কোডটি রেজিস্ট্রি ম্যানেজারকে টেলিমেট্রি প্রেরণকারী ডিভাইসে direct method request প্রেরণ করার নির্দেশ দেয়।
> 💁 এমকিউটিটি ব্যবহার করে আমাদের পূর্ববর্তী পাঠগুলিতে তৈরি করা অ্যাপ্লিকেশনগুলির সংস্করণগুলিতে, রিলে নিয়ন্ত্রণ কমান্ডগুলি সমস্ত ডিভাইসে প্রেরণ করা হয়েছিল। কোড ধরে নিয়েছে যে আমাদের কেবল একটি ডিভাইস থাকবে। কোডটির এই সংস্করণটি কোন একটিমাত্র ডিভাইসে রিকুয়েস্ট প্রেরণ করে, তাই আমাদের যদি আর্দ্রতা সেন্সর এবং রিলে এর একাধিক সেটআপ থাকে, তবে এটি সঠিক ডিভাইসে সংযুক্ত হয়ে কাজ করবে।
1. Functions app রান করে এটি নিশ্চিত করতে হবে যে IoT device ডেটা পাঠাচ্ছে। আমরা দেখতে পাব যে ম্যাসেজগুলো প্রসেস হচ্ছে এবং ডিরেক্ট মেথড রিকুয়েস্ট পাঠানো হচ্ছে। সেন্সরটি নাড়ালেই আমরা ভ্যালু চেঞ্জ হতে দেখব এবং রিলে তেও এই পরিবর্তন আসবে।
> 💁 সকল কোড [code/functions](code/functions) ফোল্ডারে রয়েছে।
## ক্লাউডে সার্ভারলেস কোড ডেপ্লয় করা
আমাদের কোডটি এখন লোকালি কাজ করছে, তাই পরবর্তী পদক্ষেপে আমরা ক্লাউডে ফাংশন অ্যাপ স্থাপন করব।
### কাজ - ক্লাউড রিসোর্স তৈরী
আমাদের ফাংশন অ্যাপটি কে একটি Azure Functions App রিসোর্সে ডেপ্লয় করতে হবে,যা আমাদের আইওটি হাবে তৈরী করা রিসোর্স গ্রুপে থাকবে। এছাড়াও ইম্যুলেটেড এর পরিবর্তে আমাদের একটি স্টোরেজ একাউন্ট প্রয়োজন।
1. স্টোরেজ একাউন্ট তৈরীর জন্য নিম্নের কমান্ড রান দিই:
```sh
az storage account create --resource-group soil-moisture-sensor \
--sku Standard_LRS \
--name <storage_name>
```
এখানে `<storage_name>` এর জায়গায় আমাদের স্টোরেজ একাউন্টের নাম দিতে হবে। এটি গ্লোবালি ইউনিক হতে হবে কেননা এটি URL হিসেবেও ব্যবহৃত হবে। এটির নাম ২৪ ক্যারেক্টারের মধ্যে হতে হবে এবং এখানে ছোট হাতের (lower case) ইংরেজি বর্ণ এবং সংখ্যা ব্যবহার করা যাবে। নাম হিসেবে `sms` এর সাথে কোন সংখ্যা বা নাম লেখা যেতে পারে।
এখানে `--sku Standard_LRS` -ই মূল্যমান নির্ধারণ করে যা এক্ষেত্রে সর্বনিম্ন দামের জেনারেল পারপাস একাউন্ট সিলেক্ট করছে। এখানে কোন ফ্রী সার্ভিস নেই এবং আমাদেরকে ব্যবহার অনুসারে ফী দিতে হবে। তবে এখানে খরচ বেশ কম, সবথেকে দামি সার্ভিসও .০৫ মার্কিন ডলার প্রতি মাসে প্রতি গিগাবাইটের জন্য।
✅ মূল্যমানের ব্যপারে [Azure Storage Account pricing page](https://azure.microsoft.com/pricing/details/storage/?WT.mc_id=academic-17441-jabenn) থেকে বিস্তারিত জানা যাবে।
1. ফাংশন এপ তৈরীর জন্য নিম্নের কমান্ড রান করি:
```sh
az functionapp create --resource-group soil-moisture-sensor \
--runtime python \
--functions-version 3 \
--os-type Linux \
--consumption-plan-location <location> \
--storage-account <storage_name> \
--name <functions_app_name>
```
`<location>` এর স্থলে আগের লেসনে রিসোর্স গ্রুপ তৈরীর সময় যে লোকেশন ব্যবহার করেছি, তা দিতে হবে।
এছাড়াও `<storage_name>` এর জায়গায় আগের অংশে ব্যবহৃত নামটিই দিতে হবে।
তারপর `<functions_app_name>` এও একটি ইউনিক নাম দিতে হবে। এটি গ্লোবালি ইউনিক হতে হবে কেননা ফাংশন এপ একসেস করার জন্য URL এ এটি ব্যবহৃত হবে। এখানে `soil-moisture-sensor-` বা এই ধরণের কিছুর পরে কোন শব্দ বা নাম দেয়া যেতে পারে।
এখানে `--functions-version 3` এই অপশনটি ব্যবহার্য Azure Functions এর ভার্সন ঠিক করে। ভার্সন-৩ ই হলো সর্বশেষ সংস্করণ।
আর `--os-type Linux` Functions runtime কে এই ফাংশনগুলো হোস্ট করার জন্য OS হিসেবে Linux ব্যবহারের নির্দেশ দেয়। Function গুলো প্রোগ্রামিং ভাষার উপর ভিত্তি করে, Linux বা Windows এ হোস্ট করা যাবে। পাইথন ভাষার এপ্লিকেশন হলে, তা কেবল Linux এই রান করবে।
### কাজ - Application settings আপলোড করা
যখন আমরা ফাংশন এপ গুলো তৈরী করি, তখন `local.settings.json` ফাইলে আইওটি হাবের কানেকশন স্ট্রিংয়ের জন্য কিছু সেটিংস স্টোর হয়। এই সেটিংস গুলো Azure এ ফাংশন এপ এর এপ্লিকেশন সেটিং এও আসতে হবে যাতে আমাদের কোড তা ব্যবহার করতে পারে।
> 🎓 `local.settings.json` ফাইলটি কেবল লোকাল ডেভলাপমেন্ট সেটিংস এর জন্য যা সোর্স কোড কন্ট্রোলেও চেক করা হয়না। যখন আমরা পুরো কার্যক্রম ক্লাউডে আনি, তখন এপ্লিকেশন সেটিংসই ব্যবহৃত হয়। এগুলো কী/ভ্যালু পেয়ার হিসেবে ক্লাউডে থাকে যা এনভায়রনমেন্ট ভ্যারিয়েবল থেকেও গ্রহণ করা যায় কোডের মাধ্যমে অথবা রানটাইমে যখন আইওটি হাবের সাথে যুক্ত করা হয়,তখন।
1. নিচের কমান্ড রান করে, `IOT_HUB_CONNECTION_STRING` সেটিংসটি ফাংশন এপ এর এপ্লিকেশন সেটিং এ ঠিক করি:
```sh
az functionapp config appsettings set --resource-group soil-moisture-sensor \
--name <functions_app_name> \
--settings "IOT_HUB_CONNECTION_STRING=<connection string>"
```
এক্ষেত্রে `<functions_app_name>` এর জায়গায় আমাদের ব্যবহৃত নামটি দিতে হবে।
আর `<connection string>` এর স্থলাভিষিক্ত হবে `IOT_HUB_CONNECTION_STRING` এর ভ্যালু যা আমরা `local.settings.json` ফাইল থেকে পাব।
1. পূর্ববর্তী ধাপটি পুনরায় করি, তবে `REGISTRY_MANAGER_CONNECTION_STRING` ভ্যালু সেট করতে হবে `local.settings.json` ফাইলের ভিত্তিতে।
আমরা যখন এই আদেশগুলি পরিচালনা করি, তখন এগুলো ফাংশন অ্যাপ্লিকেশনের জন্য সমস্ত অ্যাপ্লিকেশন সেটিংসের একটি তালিকা আউটপুট হিসেবে প্রকাশ করে। আমাদের মানগুলি সঠিকভাবে সেট করা আছে কিনা তা পরীক্ষা করতে আমরা এই সুবিধা কাজে লাগাতে পারি।
> 💁 আমরা `AzureWebJobsStorage` তে আগে থেকেই ভ্যালু সেট করা দেখবো। এক্ষেত্রে `local.settings.json` ফাইলে যা লোকাল স্টোরেজ ইম্যুলেটর ব্যবহারের জন্য সেট করা হয়েছে। আমরা যখন ফাংশন অ্যাপ তৈরী করি, তখন এই স্টোরেজ একাউন্টটি প্যারামিটার হিসেবে পাস করা হয় যা অটোমেটিক্যালি সেটিংস এ চলে আসে।
### কাজ - ক্লাউডে ফাংশন অ্যাপ ডেপ্লয় করা
এখন যেহেতু আমাদের ফাংশন অ্যাপ রেডি রয়েছে, আমরা তা ক্লাউডে ডেপ্লয় করতে পারবো।
1. VS Code terminal এ নীচের কমান্ড রান করে Functions App পাবলিশ করি:
```sh
func azure functionapp publish <functions_app_name>
```
এক্ষেত্রে `<functions_app_name>` এর জায়গায় আমাদের ব্যবহৃত নামটি দিতে হবে।
কোডটি প্যাকেজ আকারে ফাংশন অ্যাপে প্রেরণ করা হবে, যেখানে এটি ডেপ্লয় এবং ব্যবহার করা শুরু করা হবে। প্রচুর কনসোল আউটপুট থাকবে, এটির ডেপ্লয়মেন্টের নিশ্চয়তা এবং ক্রিয়াকলাপগুলির একটি তালিকা দেখানো হবে। তবে এক্ষেত্রে তালিকায় কেবল ট্রিগার থাকবে।
```output
Deployment successful.
Remote build succeeded!
Syncing triggers...
Functions in soil-moisture-sensor:
iot-hub-trigger - [eventHubTrigger]
```
আমাদের আইওটি ডিভাইসটি চলছে কিনা তা আগে নিশ্চিত করি । সেন্সরটিকে বারবার মাটির অভ্যন্তরে এবং বাইরে সরিয়ে আর্দ্রতার স্তর পরিবর্তন করি। মাটির আর্দ্রতা পরিবর্তনের সাথে সাথে আমরা রিলেটি চালু এবং বন্ধ হতে দেখবো।
---
## 🚀 চ্যালেঞ্জ
পূর্ববর্তী পাঠে, রিলে চালু থাকা অবস্থায় এবং এটি বন্ধ হওয়ার পরে অল্প সময়ের জন্য - আমরা এমকিউটিটি বার্তাগুলি থেকে আনসাবস্ক্রাইব করে রিলে এর সময় ম্যানেজ করেছিলাম । আমরা এই পদ্ধতিটি এখানে ব্যবহার করতে পারব না - আইওটি হাব ট্রিগার আমরা আন-সাবস্ক্রাইব করতে পারব না।
আমাদের ফাংশন অ্যাপে এই সমস্যা মোকাবেলা করতে বিভিন্ন উপায় সম্পর্কে চিন্তা করি।
## লেকচার পরবর্তী কুইজ
[লেকচার পরবর্তী কুইজ](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/18)
## রিভিউ এবং স্ব-অধ্যয়ন
* সার্ভারলেস কম্পিউটিং নিয়ে [Serverless Computing page on Wikipedia](https://wikipedia.org/wiki/Serverless_computing) থেকে আরো জানা যাবে।
* উদাহরণসহ সার্ভারলেস নিয়ে আরো জানা যাবে [Go serverless for your IoT needs Azure blog post](https://azure.microsoft.com/blog/go-serverless-for-your-iot-needs/?WT.mc_id=academic-17441-jabenn) থেকে।
* Azure Functions নিয়ে [Azure Functions YouTube channel](https://www.youtube.com/c/AzureFunctions) থেকে আরো জানা যাবে।
## এসাইনমেন্ট
[ম্যানুয়াল রিলে কন্ট্রোল সংযোজন](assignment.bn.md)

@ -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,245 @@
# উদ্ভিদের নিরাপত্তা নিশ্চিতকরণ
![A sketchnote overview of this lesson](../../../../sketchnotes/lesson-10.jpg)
> স্কেচনোটটি তৈরী করেছেন [Nitya Narasimhan](https://github.com/nitya). বড় সংস্করণে দেখার জন্য ছবিটিতে ক্লিক করতে হবে।
## লেকচার-পূর্ববর্তী কুইজ
[লেকচার-পূর্ববর্তী কুইজ](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/19)
## সূচনা
গত কয়েকটি পাঠে আমরা মাটি পর্যবেক্ষণের জন্য একটি আইওটি ডিভাইস তৈরি করেছি এবং এটিকে ক্লাউডের সাথে সংযুক্ত করেছি। তবে যদি হ্যাকাররা আমাদের আইওটি ডিভাইসগুলির নিয়ন্ত্রণ দখল করতে সক্ষম হয় তবে কী হবে? কী হবে যদি তারা মাটির আর্দ্রতার সঠিক মান পরিবর্তন করে, উচ্চমান পাঠায় - যাতে আমাদের গাছপালা কখনই সেচ না পায় অথবা আমাদের সেচব্যবস্থা সবসময় চালু রেখে যদি আমাদের গাছগুলিকে বেশি পানিতে ডুবিয়ে রাখে ?
এই পাঠে আমরা আইওটি ডিভাইসগুলি নিরাপত্তার বিষয়ে শিখব। যেহেতু এটি এই প্রকল্পের শেষ পাঠ, আমরা আমাদের ক্লাউড সার্ভিসগুলি কীভাবে গুছিয়ে রাখতে পারি যাতে ব্যয় হ্রাস হয় - তা শিখব।
এই পাঠে আমরা দেখবো:
* [কেন আমাদের আইওটি ডিভাইসগুলি সুরক্ষিত করা দরকার?](#কেন-আমাদের-আইওটি-ডিভাইসগুলি-সুরক্ষিত-করা-দরকার)
* [সংকেতলিপি (Cryptography)](#সংকেতলিপি)
* [আমাদের আইওটি ডিভাইস নিরাপদ রাখা](#আইওটি-ডিভাইস-নিরাপত্তা)
* [X.509 Certificate তৈরী ও ব্যবহার](#X.509-Certificate-তৈরী-ও-ব্যবহার)
> 🗑 এটি এই প্রজেক্টের শেষ লেসন, সুতরাং এই পাঠ এবং এর অ্যাসাইনমেন্ট শেষ করার পরে, আমাদের ক্লাউড ্সার্ভিসগুলি আমাদেরকে অবশ্যই গুছিয়ে রেখে দিতে হবে বা clean up করতে হবে। অ্যাসাইনমেন্টটি সম্পন্ন করার জন্য আমাদের যেসব সার্ভিসগুলির প্রয়োজন হবে, সেগুলো আগে নিশ্চিত করতে হবে।
> এক্ষেত্রে [প্রজেক্ট ক্লীন-আপ গাইডে](../../../../../translations/clean-up.bn.md) নির্দেশনা পাওয়া যাবে যাতে আমরা ক্লাউড সার্ভিস গুছিয়ে রাখতে পারি।
## কেন আমাদের আইওটি ডিভাইসগুলি সুরক্ষিত করা দরকার?
আইওটি সুরক্ষার মধ্যে এটি নিশ্চিত করা হয় যে কেবলমাত্র নির্দিষ্ট কিছু ডিভাইসই আমাদের ক্লাউড আইওটি ্সার্ভিসতে সংযোগ করতে পারে এবং তাদের টেলিমেট্রি পাঠাতে পারে। এটিও নিশ্চিত করা হয় যেন কেবল আমাদের ক্লাউড সার্ভিসই আমাদের ডিভাইসে নির্দেশ পাঠাতে পারে। আইওটি ডেটা চিকিত্সা বা বেশ অন্তরঙ্গ ডেটা সহ ব্যক্তিগতও হতে পারে, তাই এই তথ্য ফাঁস হওয়া বন্ধ করতে আমাদের পুরো ব্যবস্থাপনার সুরক্ষা বিবেচনা করা উচিত।
যদি আমাদের আইওটি অ্যাপ্লিকেশনটি সুরক্ষিত না হয় তবে বিভিন্ন ঝুঁকি রয়েছে:
* একটি নকল ডিভাইস আমাদের অ্যাপ্লিকেশনটিকে ভুলভাবে প্রতিক্রিয়া দিতে পারে। উদাহরণস্বরূপ, মাটির আর্দ্রতার সঠিক মান পরিবর্তন করে, উচ্চমান পাঠালে আমাদের সেচ ব্যবস্থা কখনই চালু হবেনা এবং আমাদের গাছপালা পানির অভাবে মারা যাবে।
* অননুমোদিত ব্যবহারকারীরা ব্যক্তিগত বা ব্যবসায়িক গুরুত্বপূর্ণ ডেটা পড়তে পারবে।
* হ্যাকাররা কোন ডিভাইসে এমনভাবে কমান্ডগুলি প্রেরণ করতে পারে যাতে ডিভাইসটি বা এর সাথে হার্ডওয়ার এর ক্ষতি করতে পারে।
* আইওটি ডিভাইসের সাথে সংযোগ স্থাপনের মাধ্যমে, হ্যাকাররা এটি অতিরিক্ত নেটওয়ার্কগুলিতে প্রবেশের অনুমতি পেয়ে যেতে পারে।
* ক্ষতিকারক ব্যবহারকারীরা ব্যক্তিগত ডেটা অ্যাক্সেস করতে এবং এটি ব্ল্যাকমেইলের জন্য ব্যবহার করতে পারে
এগুলি বাস্তব পরিস্থিতি এবং সর্বদাই ঘটে। পূর্ববর্তী পাঠগুলিতে কিছু উদাহরণ দেওয়া হয়েছিল, এখানে আরও কিছু রয়েছে:
* 2018 সালে, হ্যাকাররা ডেটা চুরির জন্য ক্যাসিনোর নেটওয়ার্কে অ্যাক্সেস পেতে একটি ফিশ ট্যাঙ্ক থার্মোস্টেটে একটি ওপেন ওয়াইফাই অ্যাক্সেস পয়েন্ট ব্যবহার করেছিল। [সূত্রঃ The Hacker News - Casino Gets Hacked Through Its Internet-Connected Fish Tank Thermometer](https://thehackernews.com/2018/04/iot-hacking-thermometer.html)
* ২০১৬ সালে মিরাই বটনেট denial of service (Dos) এর মাধ্যমে Dyn নামক একটি ইন্টারনেট সার্ভিস প্রদানকারী সরবরাহকারীর বিরুদ্ধে আক্রমণ করে। এই বটনেট সাধারণ আইওটি ডিভাইস যেমন ডিভিআর এবং ক্যামেরাগুলিতে ডিফল্ট ব্যবহারকারীর নাম এবং পাসওয়ার্ড ব্যবহার করে এবং সেখান থেকে আক্রমণ চালিয়েছিল connect [সূত্রঃ The Guardian - DDoS attack that disrupted internet was largest of its kind in history, experts say](https://www.theguardian.com/technology/2016/oct/26/ddos-attack-dyn-mirai-botnet)
* Spiral Toys এর কাছে ইন্টারনেটে সকলের জন্য এভেইলেবল ডেটাবেস ছিলো যেটিতে তাদের ক্লাউডপেটস সংযুক্ত খেলনাগুলির ব্যবহারকারীর তথ্য ছিলো । [সূত্রঃ Troy Hunt - Data from connected CloudPets teddy bears leaked and ransomed, exposing kids' voice messages](https://www.troyhunt.com/data-from-connected-cloudpets-teddy-bears-leaked-and-ransomed-exposing-kids-voice-messages/).
* Strava তে একজন এথলিট আরেকজনকে পাশাপাশি অতিক্রম করে গেলে, একে অপরের রূট সহ অনেক ব্যক্তিগত তথ্য ফাঁস করে দেয় [সূত্রঃ Kim Komndo - Fitness app could lead a stranger right to our home — change this setting](https://www.komando.com/security-privacy/strava-fitness-app-privacy/755349/).
✅ কিছু গবেষণা করা যাক: আরও উদাহরণের জন্য অনুসন্ধান করি আইওটি হ্যাকস এবং আইওটি ডেটা লঙ্ঘনের ঘটনাগুলি, বিশেষত ব্যক্তিগত বিষয়াদি যেমন ইন্টারনেট সংযুক্ত টুথব্রাশ বা স্কেল ব্যবহার করে হ্যাক। এই হ্যাকগুলি ভুক্তভোগী বা গ্রাহকদের উপর কী প্রভাব ফেলতে পারে সে সম্পর্কে চিন্তা করি।
> 💁 নিরাপত্তা একটি বিশাল বিষয় এবং এই পাঠটি কেবলমাত্র আমাদের ডিভাইসটিকে ক্লাউডের সাথে সংযুক্ত করার জন্য কয়েকটি প্রাথমিক বিষয় শেখাবে। অন্যান্য বিষয় যা আলোচনা করা হবে না তার মধ্যে রয়েছে ট্রানজিটে ডেটা পরিবর্তনের জন্য নজরদারি, সরাসরি ডিভাইস হ্যাকিং, বা ডিভাইস কনফিগারেশনে পরিবর্তন ইত্যাদি । IoT হ্যাকিং এর মত সমস্যা মোকাবেলা করতে [Azure Defender for IoT](https://azure.microsoft.com/services/azure-defender-for-iot/?WT.mc_id=academic-17441-jabenn) তৈরী করা হয়েছে। এটি আমাদের কম্পিউটারে ব্যবহৃত এন্টিভাইরাসেরই মতো, যা ছোট এবং কম পাওয়ারে চলমান আইওটি ডিভাইসের জন্য বানানো।
## সংকেতলিপি
যখন কোন ডিভাইস আইওটি পরিষেবাতে সংযুক্ত থাকে, তখন এটি নিজেকে সনাক্ত করতে একটি আইডি ব্যবহার করে। সমস্যা হল এই আইডিটি ক্লোন করা যায় - হ্যাকার একটি নকল ডিভাইস সেট আপ করতে পারে যা একই আইডিটিকে আসল ডিভাইস হিসাবে ব্যবহার করে তবে ভুল ডেটা প্রেরণ করে।
![Both valid and malicious devices could use the same ID to send telemetry](../../../../images/iot-device-and-hacked-device-connecting.png)
এই সমস্যার সমাধানের জন্য কেবলমাত্র ডিভাইস এবং ক্লাউডের পরিচিত কিছু ভ্যালু বা গোপন সংকেত দ্বারা ডেটা আদান প্রদানের সময় তা অগোছালো করে একধরণের গোপন সংকেত-নির্ভর করে দেয়া। এই প্রক্রিয়াকে বলে *encryption* এবং যে ভ্যালু বা গোপন সংকেত দ্বারা ডেটাকে পরিবর্তিত করা হয়, তাকে বলে *encryption key*
![If encryption is used, then only encrypted messages will be accepted, others will be rejected](../../../../images/iot-device-and-hacked-device-connecting-encryption.png)
যে ক্লাউড পরিষেবাটি একটি প্রক্রিয়া ব্যবহার করে ডেটাটিকে একটি পঠনযোগ্য ফর্ম্যাটে রূপান্তর করতে পারে সেই প্রক্রিয়াকে বলে *decryption (ডিক্রিপশন)* এবং এই কাজে একই এনক্রিপশন কী বা একটি *ডিক্রিপশন কী* ব্যবহার করা হয়। যদি এনক্রিপ্ট করা বার্তা কী দ্বারা ডিক্রিপ্ট করা না যায়, সেক্ষেত্রে ধরে নেয়া হয় যে ডিভাইসটি হ্যাক হয়ে গেছে এবং বার্তাটি তখন প্রত্যাখ্যান করা হয়।
এনক্রিপশন এবং ডিক্রিপশনের টেকনিককে একসাথে বলা হয় - *সংকেতলিপি (Cryptography)*
### আদিপর্যায়ের সংকেতলিপি
সবথেকে আদিযুগের সংকেতলিপি (Cryptography) ছিলো সাইফার প্রতিস্থাপন যা প্রায় ৩৫০০ বছর আগে ব্যবহার করা হতো। এগুলোতে একটি বর্ণের পরিবর্তে আরেকটি বসানো হত। উদাহরণস্বরূপ, [সিজার সাইফারে](https://wikipedia.org/wiki/Caesar_cipher) বর্ণগুলো সামনে বা পেছনে নির্দিষ্ট সংখ্যক ঘর অবস্থান পরিবর্তন করা হতো যে পরিবর্তনের মান কেবল প্রেরক ও গ্রাহক জানতো।
আবার [Vigenère cipher](https://wikipedia.org/wiki/Vigenère_cipher) এর ক্ষেত্রে বর্ণগুলো ভিন্ন ভিন্ন ঘর পর্যন্ত মান পরিবর্তন করতো যা সাইফার টেক্সট থেকেও কঠিন হয়ে যায়।
ক্রিপ্টোগ্রাফি বিভিন্ন উদ্দেশ্যে যেমন প্রাচীন মেসোপটেমিয়ায় একটি কুমার গ্লাইজ রেসিপি রক্ষা করা বা ভারতে প্রেমের গোপন চিঠি লেখার জন্য বা প্রাচীন মিশরীয় যাদুকরী মন্ত্রকে গোপন রাখার মতো কাজে ব্যবহার করা হয়েছিল।
### আধুনিক সংকেতলিপি (Cryptography)
আধুনিক ক্রিপ্টোগ্রাফি অনেক বেশি উন্নত, এটি প্রাথমিক পদ্ধতির চেয়ে ক্র্যাক করা আরও অনেক কঠিন করে তোলে। আধুনিক ক্রিপ্টোগ্রাফি ব্রুট ফোর্স আক্রমণকে অকার্যকর করার জন্য অনেকগুলি সম্ভাব্য কী(key) দিয়ে ডেটা এনক্রিপ্ট করতে জটিল গণিত ব্যবহার করে।
সুরক্ষিত যোগাযোগের জন্য এটি বিভিন্ন উপায়ে ব্যবহার করা হয়। যদি আমরা এই লেসনটি যদি গিটহাব এ পড়ি, তবে আমরা লক্ষ্য করতে পারি যে ওয়েব সাইটের ঠিকানাটি *https* দিয়ে শুরু হয়, যার অর্থ আমাদের ব্রাউজার এবং গিটহাবের ওয়েব সার্ভারের মধ্যে যোগাযোগ এনক্রিপ্ট করা আছে। যদি কেউ আমাদের ব্রাউজার এবং গিটহাবের মধ্যে প্রবাহিত ইন্টারনেট ট্র্যাফিক পড়তে সক্ষম হয়ও তবুও তারা এনক্রিপ্ট করা ডেটা পড়তে সক্ষম হবে না। আমাদের কম্পিউটার এমনকি আমাদের হার্ডড্রাইভে সমস্ত ডেটা এনক্রিপ্ট করতে পারে যাতে কেউ যদি এটি চুরি করে, তবুও যেন তারা আমাদের পাসওয়ার্ড ছাড়াই আমাদের ডেটা পড়তে না পারে।
> 🎓 HTTPS হলো HyperText Transfer Protocol **Secure**
দুর্ভাগ্যক্রমে, সবকিছুই নিরাপদ নয়। কিছু ডিভাইসের কোন সুরক্ষা নেই, অন্যকিছু ডিভাইসে আবার সহজেই ক্র্যাক করা যায় এমন কী(KEY) গুলি ব্যবহার করে বা কখনও কখনও একই কী(KEY) ব্যবহার করে একই ধরণের সমস্ত ডিভাইসে। খুব ব্যক্তিগত আইওটি ডিভাইসগুলির জন্য অ্যাকাউন্ট রয়েছে যেগুলির সাথে ওয়াইফাই বা ব্লুটুথের মাধ্যমে সংযোগ করার জন্য পাসওয়ার্ড রয়েছে। আমরা যদি আমাদের নিজস্ব ডিভাইসে সংযোগ করতে পারি তবে কিন্তু আমরা চাইলে অন্য কারও ডিভাইসেও সংযোগ করতে পারি (অনুমতি ব্যাতিরেকে এটি করা অপরাধ)। একবার সংযুক্ত হয়ে আমরা কিছু খুব প্রাইভেট ডেটা অ্যাক্সেস করতে পারি, বা তাদের ডিভাইসে নিয়ন্ত্রণ রাখতে পারি।
> 💁 আধুনিক সংকেতলিপি (Cryptography) এর জটিলতা এবং এনক্রিপশন ভাঙতে বিলিয়ন বিলিয়ন বছর লাগবে - এমন দাবি স্বত্বেও কোয়ান্টাম কম্পিউটিং এর আবির্ভাবের ফলে এই এনক্রিপশন কী (KEY) গুলো ভেঙে ফেলা অনেক সহজ হয়ে গিয়েছে।
### Symmetric এবং asymmetric keys
এনক্রিপশন ২ভাবে হয় - symmetric এবং asymmetric.
**Symmetric** এনক্রিপশন ডেটা এনক্রিপ্ট এবং ডিক্রিপ্ট করতে একই কী ব্যবহার করে। প্রেরক এবং প্রাপক উভয়েরই একই কী (KEY)টি জানা দরকার। এটি সর্বনিম্ন সুরক্ষা স্তর, কারণ কী (KEY)টি শেয়ার করতে হবে। প্রেরকের দ্বারা প্রাপকের কাছে একটি এনক্রিপ্ট করা বার্তা প্রেরণের জন্য, প্রেরককে প্রথমে প্রাপককে কী (KEY) টি পাঠাতে হবে।
![Symmetric key encryption uses the same key to encrypt and decrypt a message](../../../../images/send-message-symmetric-key.png)
If the key gets stolen in transit, or the sender or recipient get hacked and the key is found, the encryption can be cracked.
যদি ট্রানজিটে কী (KEY) টি চুরি হয়ে যায়, বা প্রেরক বা প্রাপক হ্যাক হয়ে যায় এবং কী(KEY)টি পাওয়া যায়, তাহলে এনক্রিপশনটি ক্র্যাক হয়ে যাবে।
![Symmetric key encryption is only secure if a hacker doesn't get the key - if so they can intercept and decrypt the message](../../../../images/send-message-symmetric-key-hacker.png)
**Asymmetric** এনক্রিপশনটিতে 2টি KEY ব্যবহৃত হয় - একটি এনক্রিপশন কী এবং একটি ডিক্রিপশন কী, এটি পাবলিক/প্রাইভেট KEY Pair হিসাবে উল্লেখ করা হয়। বার্তাটি এনক্রিপ্ট করার জন্য সর্বজনীন (PUBLIC) কী ব্যবহৃত হয় তবে এটি ডিক্রিপ্ট করতে তা ব্যবহার করা যায় না। ব্যক্তিগত (PRIVATE) KEY এখানে বার্তাটি ডিক্রিপ্ট করার জন্য ব্যবহৃত হয় তবে এটি আবার এনক্রিপ্ট করার জন্য ব্যবহার করা যায় না।
![Asymmetric encryption uses a different key to encrypt and decrypt. The encryption key is sent to any message senders so they can encrypt a message before sending it to the recipient who owns the keys](../../../../images/send-message-asymmetric.png)
প্রাপক তার পাবলিক KEY শেয়ার করে এবং প্রেরক এটি ব্যবহার করে বার্তা এনক্রিপ্ট করে। ম্যাসেজ পাঠানোর পরে,প্রাপক তার প্রাইভেট কী দ্বারা তা ডিক্রিপ্ট করে। এসিমেট্রিক এনক্রিপশন তুলনামূলকভাবে বেশি নিরাপদ কারণ এখানে প্রাপকের প্রাইভেট কী কখনই শেয়ার করা হয়না। পাবলিক কী তে যে কেউই একসেস পেতে পারে, তবে এটি দিয়ে কেবলমাত্র এনক্রিপ্ট করা যাবে।
সিমেট্রিক পদ্ধতিটি এসিমেট্রিক এর তুলনায় দ্রুত কাজ করতে পারে, যদিও তা কম নিরাপদ। কিছু ক্ষেত্রে উভয় পদ্ধতিই ব্যবহৃত হয়। এসিমেট্রিক পদ্ধতিতে এনক্রিপ্ট করা, আবার সিমেট্রিক কী টি শেয়ার করে সকল ডেটা সিমেট্রিক কী দ্বারা এনক্রিপ্ট করা হয়। এতে করে প্রেরক ও প্রাপকের মধ্যে সিমেট্রিক কী শেয়ার করলেও সেটা অনেক বেশি নিরাপদ থাকে আবার দ্রুতও হয়।
## আইওটি ডিভাইস নিরাপত্তা
আইওটি ডিভাইসের নিরাপত্তার জন্য সিমেট্রিক এবং এসিমেট্রিক পদ্ধতি ব্যবহার করা যায়। সিমেট্রিকটি সহজ, তবে কম নিরাপদ।
### সিমেট্রিক KEYs
আইওটি হাবের সাথে আমাদের ডিভাইসের যোগাযোগ এর জন্য আমরা কানেকশন স্ট্রিং ব্যবহার করেছিলাম। উদাহরণস্বরূপ :
```output
HostName=soil-moisture-sensor.azure-devices.net;DeviceId=soil-moisture-sensor;SharedAccessKey=Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=
```
এই কানেকশন স্ট্রিং ৩ভাগে বিভক্ত যার প্রতিটি অংশ সেমিকোলন দ্বারা পৃথকীকৃত, যেখানে প্রতি অংশে Key এবং Value রয়েছে।
| কী | ভ্যালু | বর্ণনা |
| --- | ----- | ----------- |
| Hostname | `soil-moisture-sensor.azure-devices.net` | এটি আইওটি হাবের URL |
| DeviceID | `soil-moisture-sensor` | এটি ডিভাইসের ইউনিক আইডি |
| SharedAccessKey | `Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=` | ডিভাইস এবং আইওটি হাবের কাছে থাকা সিমেট্রিক KEY |
কানেকশন স্ট্রিং এর শেষাংশ `SharedAccessKey` মূলত একটি symmetric key যা ডিভাইস এবং আইওটি হাব উভয়ের কাছেই রয়েছে। এটি কখনই ডিভাইস থেকে ক্লাউডে বা ক্লাউড থেকে ডিভাইসে প্রেরণ করা হয়না। বরং এটি পরিবাহিত ডেটাকে এনক্রিপ্ট করে।
✅ একটি এক্সপেরিমেন্ট করা যাক। আমরা আইওটি ডিভাইসে সংযুক্ত হবার সময় কানেকশন স্ট্রিং এর `SharedAccessKey` পরিবর্তন করে দিলে কী হবে? চেষ্টা করে দেখো।
ডিভাইসটি প্রথমে আইওটি হাবের সাথে সংযোগ দেওয়ার চেষ্টা করলে, তখন URL সহ shared access signature (SAS) টোকেন , একটি টাইমস্ট্যাম্প যেটির সিগনেচার একসেস একটি নির্দিষ্ট সময় পর (সাধারণত বর্তমান সময় থেকে 1 দিন পর্যন্ত) শেষ হয়ে যায় এবং একটি সিগনেচার - এসব প্রেরণ করে। এই সিগনেচারে কানেকশন স্ট্রিং থেকে ইউআরএল, মেয়াদোত্তীর্ণের সময় এবং এনক্রিপটেড শেয়ারড একসেস কী থাকে।
আইওটি হাব শেয়ারড একসেস কী দিয়ে সিগনেচারটি ডিক্রিপ্ট করে এবং যদি ডিক্রিপ্ট করা মানটি ইউআরএল এবং মেয়াদোত্তীর্ণের সময়ের সাথে মিলে যায় তবে ডিভাইসটি সংযোগ করার অনুমতি পায়। এটিও যাচাই করা হয় যে বর্তমান সময়টি মেয়াদোত্তীর্ণের সময়ের আগে যাতে করে কোন দুষ্ট (malicious) ডিভাইস কোন আসল ডিভাইসের এসএএস টোকেন ক্যাপচার করে তা ব্যবহার করতে না পারে।
প্রেরকটি সঠিক ডিভাইস কিনা তা যাচাই করার জন্য এটি একটি উত্তম উপায়। ডিক্রিপ্ট এবং এনক্রিপ্ট করা কিছু জ্ঞাত ডেটা প্রেরণ করে সার্ভার ডিভাইসটিকে যাচাই করে দেখতে পারে যে এনক্রিপ্ট করা ডেটার ডিক্রিপ্ট ভার্সনটি, প্রেরণ করা ডিক্রিপ্ট করা সংস্করণের সাথে মেলে কিনা। যদি এটি মেলে, তবে প্রেরক এবং প্রাপক উভয়েরই একই Symmetric Encryption Key রয়েছে।
> 💁 যেহেতু এখানে মেয়াদোত্তীর্ণের একটি বিষয় রয়েছে, আমাদের আইওটি ডিভাইসের জন্য তাই বর্তমান সময়টি জানা জরুরী। সাধারণত [NTP](https://wikipedia.org/wiki/Network_Time_Protocol) সার্ভার থেকেই এটি সময় সংক্রান্ত ডেটা নেয়। সময় সঠিক না হলে, কানেকশন হবেনা।
সংযোগের পরে, ডিভাইস থেকে আইওটি হাবের কাছে বা আইওটি হাব থেকে ডিভাইসে প্রেরিত সমস্ত ডেটা shared access key দিয়ে এনক্রিপ্ট করা হবে।
✅ একাধিক ডিভাইস একই সংযোগের স্ট্রিং শেয়ার করলে কী ঘটবে বলে মনে হয়?
> 💁 কোডের মধ্যেই এই KEY সংরক্ষণ করা নিরাপত্তার প্রেক্ষিতে বেশ বাজে একটি চর্চা। কোন হ্যাকার যদি আমাদের সোর্স কোড পায় তবে তারা আমাদের KEY পেয়ে যেতে পারে। এছাড়াও কোড রিলিজ করার সময় এটি আরও কঠিন হয় কারণ আমাদের প্রতিটি ডিভাইসের জন্য একটি আপডেট কী দিয়ে পুনরায় ্তা পরিবর্তন করতে হবে। একটি হার্ডওয়্যার সুরক্ষা মডিউল থেকে এই KEY লোড করা ভাল উপায়। এই মডিউল হলো আইওটি ডিভাইসের একটি চিপ যা এনক্রিপ্ট করা মানগুলিকে স্টোর করে যা আমাদের কোড দ্বারা একসেস করা যাবে।
>
> আইওটি শেখার সময় KEY গুলো কোডে রাখলে কাজ করা সহজ হয়, যেমন আমরা পূর্ববর্তী পাঠে করেছিলাম। তবে আমাদের অবশ্যই নিশ্চিত করতে হবে যে এই KEY জনসাধারণের জন্য সোর্স কোডে উন্মুক্ত করা হয়নি।
Devices have 2 keys, and 2 corresponding connection strings. This allows we to rotate the keys - that is switch from one key to another if the first gets compromised, and re-generate the first key.
ডিভাইসগুলিতে 2 টি কী এবং 2 টি কানেকশন স্ট্রিং রয়েছে। এটি আমাদের কীগুলির মধ্যে পরিবর্তনের অনুমতি দেয় । যদি প্রথম কী টি সমস্যার মুখে পড়ে, তবে ২য়টি ব্যবহার করা এবং প্রথম কী পুনরায় তৈরী করে।
### X.509 সার্টিফিকেট
যখন আমরা কোনও পাবলিক/প্রাইভেট কী পেয়ার এর সাথে এসিমেট্রিক এনক্রিপশন ব্যবহার করি, আমাদেরকে কেউ ডেটা প্রেরণ করতে চাইলে তাকে আমাদের পাবলিক কী সরবরাহ করতে হবে। সমস্যাটি হল কীভাবে আমাদের প্রাপক নিশ্চিত করতে পারেন যে এটি আসলেই আমাদের পাবলিক কী এবং অন্য কেউ আমাদের রূপধারণ করে সংযোগের চেষ্টা করছে না? KEY সরবরাহ করার পরিবর্তে, আমরা বরং আমাদের পাবলিক কী এমন একটি সার্টিফিকেটের ভিতরে সরবরাহ করতে পারি যা একটি বিশ্বস্ত তৃতীয় পক্ষ দ্বারা যাচাই করা হয়েছে এবং এটিকে বলা হয় X.509 সার্টিফিকেট।
X.509 সার্টিফিকেট হলো ডিজিটাল ডকুমেন্ট যেগুলো পাবলিক/প্রাইভেট কী পেয়ার এর পাবলিক অংশটি ধারণ করে। এগুলি সাধারণত বেশ কয়েকটি বিশ্বস্ত সংস্থার দ্বারা ইস্যু করা হয় যেগুলোকে বলা হয় [Certification authorities](https://wikipedia.org/wiki/Certificate_authority) (CAs) এবং এসকল সংস্থা এই ডকুমেন্টগুলো সাইন করে দেয় যা বোঝায় যে key গুলো সঠিক এবং ঠিক ব্যবহারকারীর কাছ থেকেই আসছে। যেভাবে আমরা পাসপোর্ট বা ড্রাইভিং লাইসেন্সে বিশ্বাস করি (যেহেতু নির্দিষ্ট কর্তৃপক্ষ দ্বারা তা স্বীকৃত, সেভাবেই এখানেও আমরা সার্টিফিকেটগুলো বিশ্বাস করি। এগুলোর জন্য অর্থ ব্যয় হয়, তাই আমরা 'স্ব-স্বাক্ষর'ও করতে পারি, এটি পরীক্ষার উদ্দেশ্যে আমাদের স্বাক্ষরিত একটি সার্টিফিকেট তৈরি করে।
> 💁 আমাদের কোনও প্রোডাকশন রিলিজের জন্য স্ব-স্বাক্ষরিত সার্টিফিকেট ব্যবহার করা উচিত নয়।
These certificates have a number of fields in them, including who the public key is from, the details of the CA who issued it, how long it is valid for, and the public key itself. Before using a certificate, it is good practice to verify it by checking that is was signed by the original CA.
এই সার্টিফিকেট গুলির মধ্যে বেশ কয়েকটি ক্ষেত্র রয়েছে যেমন - কোথায় পাবলিক কী রয়েছে , যে CA এটির স্বীকৃতি দিয়েছে তার তথ্যাবলি, কতক্ষণের জন্য এটি বৈধ হবে তার বিবরণ এবং সেই পাবলিক কী। কোন সার্টিফিকেট ব্যবহার করার আগে, এটি যে সিএ স্বাক্ষর করেছে তা যাচাই করা ভাল একটি চর্চা।
✅ সার্টিফিকেট গুলির সকল ক্ষেত্র এর বর্ণনা [Microsoft Understanding X.509 Public Key Certificates tutorial](https://docs.microsoft.com/azure/iot-hub/tutorial-x509-certificates?WT.mc_id=academic-17441-jabenn#certificate-fields) এ রয়েছে।
X.509 সার্টিফিকেট ব্যবহার করার সময়, প্রেরক এবং প্রাপক উভয়েরই নিজস্ব public and private keys এবং সেইসাথে উভয়েরই X.509 শংসাপত্র থাকবে যাতে পাবলিক কী রয়েছে। এরপরে তারা X.509 সার্টিফিকেট কোনভাবে বিনিময় করেন, একে অপরকে তারা পাঠানো ডেটা এনক্রিপ্ট করার জন্য পাবলিক কী এবং তাদের প্রাপ্ত ডেটা ডিক্রিপ্ট করার জন্য তাদের নিজস্ব প্রাইভেট কী ব্যবহার করে।
![Instead of sharing a public key, we can share a certificate. The user of the certificate can verify that it comes from we by checking with the certificate authority who signed it.](../../../../images/send-message-certificate.png)
X.509 শংসাপত্রগুলি ব্যবহার করার একটি বড় সুবিধা হল এগুলি ডিভাইসের মধ্যে শেয়ার করা যায়। আমরা একটি শংসাপত্র তৈরি করতে পারি, এটি আইওটি হাবে আপলোড করতে পারি এবং আমাদের সমস্ত ডিভাইসের জন্য এটি ব্যবহার করতে পারি। আইওটি হাব থেকে প্রাপ্ত বার্তাগুলি ডিক্রিপ্ট করার জন্য প্রতিটি ডিভাইসটির তখন কেবল প্রাইভেট কী জানতে হবে।
আইওটি হাবের কাছে পাঠানো বার্তাগুলি এনক্রিপ্ট করার জন্য আমাদের ডিভাইস দ্বারা ব্যবহৃত শংসাপত্রটি মাইক্রোসফ্ট প্রকাশ করে। এটি একই শংসাপত্র যা প্রচুর অ্যাজুর সার্ভিস ব্যবহার করে এবং কখনও কখনও SDK-গুলিতে অন্তর্নির্মিত থাকে।
> 💁 মনে রাখতে হবে, একটি public key আসলেই 'public'. অ্যাজুরে পাবলিক কী কেবল এখানে প্রেরিত ডেটা এনক্রিপ্ট করতে ব্যবহার করা যেতে পারে, এটি ডিক্রিপ্ট করার জন্য নয়, সুতরাং এটি সোর্স কোড সহ সর্বত্র শেয়ার করা যায়। উদাহরণস্বরূপ, আমরা [Azure IoT C SDK source code](https://github.com/Azure/azure-iot-sdk-c/blob/master/certs/certs.c) এও তা দেখতে পাবো।
✅ X.509 certificates এ কিছু নির্দিষ্ট শব্দ বা ভাষা রয়েছে। অপিরিচিত কোন শোব্দের মুখোমুখি হলে আমরা [The laymans guide to X.509 certificate jargon](https://techcommunity.microsoft.com/t5/internet-of-things/the-layman-s-guide-to-x-509-certificate-jargon/ba-p/2203540?WT.mc_id=academic-17441-jabenn) পড়লে তা পাবো।
## X.509 certificate তৈরী ও ব্যবহার
একটি X.509 শংসাপত্র তৈরী করার পদক্ষেপগুলি হল:
1. একটি public/private key pair তৈরী করা। এটির জন্য বহুল ব্যবহৃত একটি এলগরিদম হলো [RivestShamirAdleman](https://wikipedia.org/wiki/RSA_(cryptosystem))(RSA) পদ্ধতি।
1. CA বা self-signing এর মাধ্যমে পাবলিক কী এবং প্রয়োজনীয় তথ্য সাবমিট করা।
আইওটি হাবটিতে একটি নতুন ডিভাইস আইডেনটিটি তৈরি করতে এবং স্বয়ংক্রিয়ভাবে পাবলিক / প্রাইভেট কী পেয়ার তৈরী করতে এবং স্ব-স্বাক্ষরিত শংসাপত্র তৈরি করার জন্য আজুর সিএলআইয়ের কমান্ড রয়েছে।
> 💁 কাজের ধাপগুলো বিস্তারিত দেখতে হলে আমরা বরং CLI এর পরিবর্তে [Using OpenSSL to create self-signed certificates tutorial in the Microsoft IoT Hub documentation](https://docs.microsoft.com/azure/iot-hub/tutorial-x509-self-sign?WT.mc_id=academic-17441-jabenn) দেখতে পারি।
### কাজ - X.509 certificate দ্বারা ডিভাইস আইডেনটিটি তৈরী
1. নতুন ডিভাইস আইডেনটিটি তৈরীর জন্য নিম্নের কমান্ড রান দিই, স্বয়ংক্রিয়ভাবে কী এবং শংসাপত্রগুলি তৈরি করা হচ্ছে:
```sh
az iot hub device-identity create --device-id soil-moisture-sensor-x509 \
--am x509_thumbprint \
--output-dir . \
--hub-name <hub_name>
```
এখানে `<hub_name>` এর জায়গায় আমাদের IoT Hub এ ব্যবহৃত নামটি দিতে হবে।
এটি `soil-moisture-sensor-x509` এর জন্য একটি আইডি সহ ডিভাইস তৈরী করে দিবে যা আগের লেসনে তৈরী করা ডিভাইস থেকে ভিন্ন । এছাড়াও ২টি ফাইল তৈরী হবে:
* `soil-moisture-sensor-x509-key.pem` - এই ফাইলে ডিভাইসের জন্য প্রাইভেট কী থাকে।
* `soil-moisture-sensor-x509-cert.pem` - এটিতে X.509 সার্টিফিকেট থাকে।
এই ফাইল গুলো সুরক্ষিত রাখতে হবে! পাবলিকে একসেস পাওয়ার মত করে রাখা যাবেনা।
### কাজ - ডিভাইস কোডে X.509 certificate ব্যবহার
নিম্নের প্রাসঙ্গিক কোন একটি গাইড অনুসরণ করতে হবে ঃ
* [Arduino - Wio Terminal](wio-terminal-x509.md)
* [Single-board computer - Raspberry Pi/Virtual IoT device](single-board-computer-x509.md)
---
## 🚀 চ্যালেঞ্জ
Azure সার্ভিস তৈরী, পরিচালনা এবং ডিলিট করার জন্য অনেকগুলো উপায় রয়েছে। এর মধ্যে একটি হলো [Azure Portal](https://portal.azure.com?WT.mc_id=academic-17441-jabenn) - একটি ওয়য়েব-ভিত্তিক ইন্টারফেস এক্সেখান থেকে আমরা সহজেই Azure services ব্যবহার করতে পারি।
আমরা [portal.azure.com](https://portal.azure.com?WT.mc_id=academic-17441-jabenn) এ গিয়ে পোর্টালটি দেখতে পারি। এখানে আইওটি হাব তৈরী করে, পরে ডিলিট করে দিই।
**Hint** - পোর্টালের মাধ্যমে পরিষেবাগুলি তৈরি করার সময়, আমাদের শুরুতেই কোনও রিসোর্স গ্রুপ তৈরি করার দরকার নেই, যখন পরিষেবাটি তৈরি করা হয় তখন একটি রিসোর্স গ্রুপ তৈরি করা যেতে পারে। সবশেষে নিশ্চিত হয়ে নিতে হবে যে আমরা কাজ শেষ হয়ে গেলে এটি মুছে ফেলেছি!
Azure Portal নিয়ে ডকুমেন্ট, টিউটোরিয়াল, গাইড [Azure portal documentation](https://docs.microsoft.com/azure/azure-portal/?WT.mc_id=academic-17441-jabenn) এ পাওয়া যাবে।
## লেকচার-পরবর্তী কুইজ
[লেকচার পরবর্তী কুইজ](https://brave-island-0b7c7f50f.azurestaticapps.net/quiz/20)
## রিভিউ এবং স্ব-অধ্যয়ন
* সংকেতলিপি এর ইতিহাস [History of Cryptography - Wikipedia](https://wikipedia.org/wiki/History_of_সংকেতলিপি (Cryptography))থেকে জেনে নিই।
* X.509 সার্টিফিকেট সম্পর্কে [X.509 page on Wikipedia](https://wikipedia.org/wiki/X.509) থেকে বিশদভাবে জ্ঞান অর্জন করি।
## এসাইনমেন্ট
[নতুন আইওটি ডিভাইস তৈরি](assignment.bn.md)

@ -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) द्वारा ️♥️ साथ लिखे गए थे

@ -15,8 +15,8 @@ framework = arduino
lib_deps =
bblanchon/ArduinoJson @ 6.17.3
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -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) द्वारा ️♥️ साथ लिखे गए थे

@ -14,8 +14,8 @@ board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -14,8 +14,8 @@ board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -14,8 +14,8 @@ board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -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) द्वारा ️♥️ साथ लिखे गए थे ।

@ -14,8 +14,8 @@ board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -14,8 +14,8 @@ board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
seeed-studio/Seeed Arduino RTC @ 2.0.0

@ -6,6 +6,8 @@ The latest iterations are now part of our smart devices. In kitchens in homes al
In these 4 lessons you'll learn how to build a smart timer, using AI to recognize your voice, understand what you are asking for, and reply with information about your timer. You'll also add support for multiple languages.
> ⚠️ Working with speech and microphone data uses a lot of memory, meaning it is easy to hit limits on microcontrollers. The project here works around these issues, but be aware the Wio Terminal labs are complex and may take more time that other labs in this curriculum.
> 💁 These lessons will use some cloud resources. If you don't complete all the lessons in this project, make sure you [clean up your project](../clean-up.md).
## Topics

@ -13,7 +13,7 @@ platform = atmelsam
board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
build_flags =
-DSFUD_USING_QSPI

@ -13,8 +13,8 @@ platform = atmelsam
board = seeed_wio_terminal
framework = arduino
lib_deps =
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
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

@ -67,6 +67,11 @@ public:
return text;
}
String AccessToken()
{
return _access_token;
}
private:
String getAccessToken()
{

@ -24,8 +24,8 @@ Once each buffer has been captured, it can be written to the flash memory. Flash
```ini
lib_deps =
seeed-studio/Seeed Arduino FS @ 2.0.3
seeed-studio/Seeed Arduino SFUD @ 2.0.1
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
```
1. Open the `main.cpp` file and add the following include directive for the flash memory library to the top of the file:

@ -18,6 +18,8 @@ To connect the ReSpeaker 2-Mics Pi Hat you will need 40 pin-to-pin (also referre
> 💁 If you are comfortable soldering, then you can use the [40 Pin Raspberry Pi Hat Adapter Board For Wio Terminal](https://www.seeedstudio.com/40-Pin-Raspberry-Pi-Hat-Adapter-Board-For-Wio-Terminal-p-4730.html) to connect the ReSpeaker.
You will also need an SD card to use to download and playback audio. The Wio Terminal only supports SD Cards up to 16GB in size, and these need to be formatted as FAT32 or exFAT.
### Task - connect the ReSpeaker Pi Hat
1. With the Wio Terminal powered off, connect the ReSpeaker 2-Mics Pi Hat to the Wio Terminal using the jumper leads and the GPIO sockets on the back of the Wio Terminal:
@ -59,3 +61,15 @@ To connect the ReSpeaker 2-Mics Pi Hat you will need 40 pin-to-pin (also referre
* If you are using a speaker with a 3.5mm jack, or headphones, insert them into the 3.5mm jack socket.
![A speaker connected to the ReSpeaker via the 3.5mm jack socket](../../../images/respeaker-35mm-speaker.png)
### Task - set up the SD card
1. Connect the SD Card to your computer, using na external reader if you don't have an SD Card slot.
1. Format the SD Card using the appropriate tool on your computer, making sure to use a FAT32 or exFAT file system
1. Insert the SD card into the SD Card slot on the left-hand side of the Wio Terminal, just below the power button. Make sure the card is all the way in and clicks in - you may need a thin tool or another SD Card to help push it all the way in.
![Inserting the SD card into the SD card slot below the power switch](../../../images/wio-sd-card.png)
> 💁 To eject the SD Card, you need to push it in slightly and it will eject. You will need a thin tool to do this such as a flat-head screwdriver or another SD Card.

@ -180,6 +180,15 @@ The audio can be sent to the speech service using the REST API. To use the speec
This code builds the URL for the token issuer API using the location of the speech resource. It then creates an `HTTPClient` to make the web request, setting it up to use the WiFi client configured with the token endpoints certificate. It sets the API key as a header for the call. It then makes a POST request to get the certificate, retrying if it gets any errors. Finally the access token is returned.
1. To the `public` section, add a method to get the access token. This will be needed in later lessons to convert text to speech.
```cpp
String AccessToken()
{
return _access_token;
}
```
1. To the `public` section, add an `init` method that sets up the token client:
```cpp
@ -497,7 +506,7 @@ The audio can be sent to the speech service using the REST API. To use the speec
Serial.println(text);
```
1. Build this code, upload it to your Wio Terminal and test it out through 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, then converted to text.
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, then converted to text.
```output
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time

@ -274,7 +274,7 @@ Rather than calling LUIS from the IoT device, you can use serverless code with a
func new --name text-to-timer --template "HTTP trigger"
```
This will crate an HTTP trigger called `text-to-timer`.
This will create an HTTP trigger called `text-to-timer`.
1. Test the HTTP trigger by running the functions app. When it runs you will see the endpoint listed in the output:
@ -493,9 +493,35 @@ Rather than calling LUIS from the IoT device, you can use serverless code with a
### Task - make your function available to your IoT device
1. For your IoT device to call your REST endpoint, it will need to know the URL. When you accessed it earlier, you used `localhost`, which is a shortcut to access REST endpoints on your local machine. To allow you IoT device to get access, you need to either:
1. For your IoT device to call your REST endpoint, it will need to know the URL. When you accessed it earlier, you used `localhost`, which is a shortcut to access REST endpoints on your local machine. To allow you IoT device to get access, you need to either publish to the cloud, or get your IP address to access it locally.
> ⚠️ If you are using a Wio Terminal, it is easier to run the function app locally, as there will be a dependency on libraries that mean you cannot deploy the function app in the same way as you have done before. Run the function app locally and access it via your computers IP address. If you do want to deploy to the cloud, information will be provided in a later lesson on the way to do this.
* Publish the Functions app - follow the instructions in earlier lessons to publish your functions app to the cloud. Once published, the URL will be `https://<APP_NAME>.azurewebsites.net/api/text-to-timer`, where `<APP_NAME>` will be the name of your functions app. Make sure to also publish your local settings.
When working with HTTP triggers, they are secured by default with a function app key. To get this key, run the following command:
```sh
az functionapp keys list --resource-group smart-timer \
--name <APP_NAME>
```
Copy the value of the `default` entry from the `functionKeys` section.
```output
{
"functionKeys": {
"default": "sQO1LQaeK9N1qYD6SXeb/TctCmwQEkToLJU6Dw8TthNeUH8VA45hlA=="
},
"masterKey": "RSKOAIlyvvQEQt9dfpabJT018scaLpQu9p1poHIMCxx5LYrIQZyQ/g==",
"systemKeys": {}
}
```
This key will need to be added as a query parameter to the URL, so the final URL will be `https://<APP_NAME>.azurewebsites.net/api/text-to-timer?code=<FUNCTION_KEY>`, where `<APP_NAME>` will be the name of your functions app, and `<FUNCTION_KEY>` will be your default function key.
> 💁 You can change the type of authorization of the HTTP trigger using `authlevel` setting in the `function.json` file. You can read more about this in the [configuration section of the Azure Functions HTTP trigger documentation on Microsoft docs](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=python&WT.mc_id=academic-17441-jabenn#configuration).
* Publish the Functions app - follow the instructions in earlier lessons to publish your functions app to the cloud. Once published, the URL will be `http://<APP_NAME>.azurewebsites.net/api/text-to-timer`, where `<APP_NAME>` will be the name of your functions app.
* Run the functions app locally, and access using the IP address - you can get the IP address of your computer on your local network, and use that to build the URL.
Find your IP address:
@ -506,9 +532,11 @@ Rather than calling LUIS from the IoT device, you can use serverless code with a
Once you have your IP address, you will able to access the function at `http://<IP_ADDRESS>:7071/api/text-to-timer`, where `<IP_ADDRESS>` will be your IP address, for example `http://192.168.1.10:7071/api/text-to-timer`.
> 💁 Not that this uses port 7071, so after the IP address you will need to have `:7071`.
> 💁 This will only work if your IoT device is on the same network as your computer.
1. Test the endpoint by accessing it using your browser.
1. Test the endpoint by accessing it using curl.
---

@ -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,6 +1,6 @@
# Set a timer - Virtual IoT Hardware and Raspberry Pi
In this part of the lesson, you will set a timer on your virtual IoT device or Raspberry Pi based off a command from the IoT Hub.
In this part of the lesson, you will call your serverless code to understand the speech, and set a timer n your virtual IoT device or Raspberry Pi based off the results.
## Set a timer

@ -1,3 +1,287 @@
# 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
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…
Cancel
Save