Nosso mundo conectado: explicando as Máquinas de Estados Finitos
Diz-se que o homem e a mulher foram criados à imagem de Deus. De maneira semelhante, homens e mulheres agora criam máquinas à sua própria imagem. Um exemplo disso é a programação de , ou MEF (do inglês Finite States Machine). Engenheiros e desenvolvedores agora usam computadores para executar tarefas que antes eram realizadas manualmente. Por exemplo, você tem roupa suja espalhada por aí? Eu sei que tenho. Antes, tínhamos que lavar a roupa em uma banheira ou pia, colocar sabão, esfregar, enxaguar novamente e assim por diante para ter uma camiseta limpa para ir trabalhar ou sair à noite. Agora temos máquinas de lavar que fazem esse trabalho para nós. Chegamos a esse ponto graças aos engenheiros que projetaram milhares de produtos e dispositivos que executam programas baseados no pensamento ou na ação humana. Isso não é diferente do que acontece hoje com o aprendizado de máquina ou outros termos da moda em Inteligência Artificial. Milhões de dispositivos e aplicativos estão sendo desenvolvidos para aumentar a eficiência e facilitar a vida de homens e mulheres, e muitos desses processos de auxílio existem graças às MEFs.
As Máquinas de Estados Finitos (MEF) são simplesmente cálculos matemáticos de uma série de relações de causa e efeito. No exemplo da máquina de lavar, a MEF determina quando iniciar o ciclo de enxágue, quando centrifugar e quando parar completamente. Para melhor compreender uma Máquina de Estados Finitos (MEF), precisamos primeiro definir o conceito de "estado". Um estado é uma informação única dentro de um programa computacional maior. A computação da MEF muda ou transita de um estado para outro em resposta a entradas externas. Uma MEF é definida por uma lista ou ordem lógica de seus estados: seu estado inicial e as condições para cada transição, concluindo com um estado final. A MEF é uma série de pensamentos programados pelo computador para executar operações com base em entradas – da mesma forma que o ser humano pensa e age, também o fazem nossas máquinas e os computadores que as controlam.
Os estados são o DNA da Máquina de Estados Finitos (MEF), ditando o comportamento interno ou as interações com o ambiente, como aceitar entradas ou produzir saídas, o que pode ou não causar uma mudança ou transição de estado do sistema. O estado a ser executado depende especificamente das diferentes condições definidas na MEF. Esse conceito é muito importante para engenheiros de hardware e elétricos, pois muitos problemas práticos, como a programação de máquinas de lavar (quando adicionar água ou sabão, quando centrifugar ou pausar), são resolvidos facilmente usando MEFs em vez dos paradigmas clássicos de programação sequencial. Em outras palavras, uma MEF é uma solução mais "elétrica e eletrônica" para resolver um problema de hardware em comparação com a programação sequencial.
Abaixo estão dois exemplos de Máquinas de Estados Finitos (FSMs) que produzem tomada de decisão lógica com menos tempo e energia necessários para implantar um programa logicamente testado. A FSM é o seu primeiro passo para a computação Edge em nível de dispositivo único em aplicações industriais IoT .
Máquina de Mealy: No cálculo da Máquina de Mealy, as saídas de cada estado dependem do estado atual e de seus valores de entrada. Tipicamente, em cada cálculo de Mealy, a entrada de um estado resulta em uma única saída para uma transição ou para um estado final. A máquina de lavar está enchendo de água – quando o nível X for atingido – a água para de fluir.
Máquina de Moore: Na máquina de Moore, as saídas de cada estado dependem do estado atual e normalmente são baseadas em sistemas sequenciais sincronizados. A máquina de lavar está centrifugando após 4 minutos; pare a máquina.
DIAGRAMA DE ESTADOS
Qualquer Máquina de Estados Finitos (MEF) deve ser descrita antes de ser codificada por meio de um diagrama de estados – a maneira como diagramamos os pensamentos da máquina. O exemplo abaixo mostra o comportamento da MEF e suas transições, que são (tipicamente) representadas usando bolhas para descrever os estados e setas para as transições. Além disso, uma observação importante ao executar uma MEF corretamente é ter um estado presente único, onde o próximo estado (futuro) a ser executado possa ser facilmente identificado pelas credenciais de programação do estado.
No diagrama acima, ilustramos um processo completo de uma Máquina de Estados Finitos de Mealy. Suponhamos que a operação comece no Estado 1 e, em seguida, transite para o Estado 2 assim que as credenciais de programação forem atendidas. Após a transição para o Estado 2, a Máquina de Estados Finitos computa o estado atual até que um gatilho seja acionado para prosseguir para o Estado 3 ou o Estado 4. Observe que, neste diagrama, o Estado 3 e o Estado 4 são estados finais que resultam em dados computados para o resultado final do seu projeto.
Ubidots FSM
Agora, vamos começar a programar uma Máquina de Estados Finitos (MEF) para enviar dados ao Ubidots e proporcionar a você uma experiência prática com esse método de programação. Para nossa MEF, buscamos identificar e reagir à necessidade inicial. Construiremos uma Máquina de Moore simples: enviaremos dados de sensores do nosso microcontrolador (Espressif ESP8266) a cada minuto para Ubidots
Com base nesse requisito inicial, optamos por implementar dois estados usando um modelo de computação de Máquina de Moore com Máquina de Estados Finitos (FSM):
- ESPERE: Não faça nada até que um minuto tenha passado (permaneça em estado ocioso por aproximadamente 59 segundos).
- READ_SEND: Lê a entrada analógica do microcontrolador onde o sensor está conectado e envia o resultado para Ubidots usando MQTT aos 60 segundos.
O diagrama de estados abaixo ilustra a lógica de programação da nossa máquina de estados finitos (FSM):

A partir deste diagrama, fica claro que a transição de WAIT para READ_SEND depende exclusivamente de o valor do tempo independente ser maior ou menor que 60 segundos. A partir do próximo estado WAIT, o programa continuará em execução nesse estado até que o tempo independente atinja ou ultrapasse 60 segundos. Assim que a marca de 60 segundos for atingida, a máquina de estados finitos (FSM) fará a transição de WAIT para READ_SEND. Após o envio do valor, a FSM retornará ao estado WAIT por uma contagem adicional de aproximadamente 59 segundos antes de calcular a transição novamente.
Codificação
Para tornar este exemplo um pouco mais fácil de entender, vamos analisar um código prático de Máquina de Estados Finitos (MEF) dividido em quatro partes separadas para detalhar cada um dos estados e condicionais de transição. O código completo pode ser encontrado aqui.
Parte 1 – Definir restrições
// Incluir bibliotecas #include "UbidotsESPMQTT.h" // Definir constantes #define TOKEN "...." // Seu TOKEN Ubidots #define WIFINAME "...." // Seu SSID #define WIFIPASS "...." // Sua senha do Wi-Fi #define WAIT 0 #define READ_SEND 1 uint8_t fsm_state = WAIT; // Estado inicial int msCount = 0; // contador de tempo float value; // espaço de memória para o valor a ser lido Ubidots client(TOKEN);
Esta primeira parte do código não é muito interessante, pois simplesmente importamos a biblioteca MQTT para enviar dados ao Ubidots e completamos algumas definições necessárias. É importante notar que aqui definimos os dois estados, WAIT e READ_SEND, como constantes em todo o código e definimos o estado atual usando a variável fsm_state. A próxima parte do código reserva espaço de memória para o temporizador independente, o valor a ser lido e o cliente MQTT a ser inicializado.
É importante que você não se esqueça de configurar os valores corretos para o seu TOKEN, o nome da sua rede Wi-Fi e a senha. Caso não saiba onde encontrar o seu token, consulte a Central de Ajuda Ubidots para obter mais dicas e truques.
Parte 2 – Retorno de chamada
// Funções auxiliares void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Mensagem recebida ["); Serial.print(topic); Serial.print("] "); for (int i=0;i < length;i++) { Serial.print((char)payload[i]); } Serial.println(); }Nesta parte do código, provisionamos uma função de retorno de chamada que lida com os dados do servidor quando/se necessário. Para nossa Máquina de Estados Finitos (FSM), esta etapa é necessária para compilar o código corretamente. Conforme descrito em nosso artigo sobre MQTT , a função de retorno de chamada lida com as alterações das suas variáveis no Ubidots e é necessário compilar o código e tê-la definida.
Parte 3 – Funções principais – Configuração()
// Funções principais void setup() { // inicializa o pino digital LED_BUILTIN como saída. pinMode(A0, INPUT); client.wifiConnection(WIFINAME, WIFIPASS); client.begin(callback); }Vamos começar com as funções principais. Na função `setup()`, definiremos o pino analógico zero como entrada (você deve editar o número do pino dependendo da conexão física do seu sensor) para poder usar o ADC, que permite ao sensor ler os dados do ambiente e representar um número de ponto flutuante como valor. Além disso, inicializamos o cliente Wi-Fi e passamos a função de retorno de chamada com base nos parâmetros de programação definidos anteriormente.
Parte 4 – Funções principais – Loop()
void loop() { switch(fsm_state) { case WAIT: if (msCount >= 60000){ msCount = 0; fsm_state = READ_SEND; } break; case READ_SEND: value = analogRead(A0); if(!client.connected()){ client.reconnect(); } /* Rotina para envio de dados */ client.add("stuff", value);ubidots("source1"); client.loop(); fsm_state = WAIT; break; default: break; } // Incrementa o contador msCount += 1; delay(1); }
Uma maneira popular de implementar máquinas de estados finitos (FSM) em microcontroladores é usando a switch-case . Para o nosso exemplo, os casos serão os estados e as chaves serão a fsm_state . Vejamos em mais detalhes como cada estado é projetado:
Uma maneira popular de implementar uma Máquina de Estados Finitos (MEF) em microcontroladores é usando a switch-case . Para o nosso exemplo, os switch-cases serão nossos estados e a programação que causa uma transição será representada pela variável fsm_state. Aqui, determinaremos READ_SEND versus WAIT, onde os valores 1 ou 0 serão enviados respectivamente para identificar cada estágio da MEF e a transição entre as operações com base em um temporizador independente de 60 segundos.
Vejamos em mais detalhes como cada estado é projetado:
- ESPERA: A partir do código deste estado, podemos inferir que ele não fará nada se o resultado do temporizador independente armazenado em
msCountfor menor que 60000 milissegundos; assim que essa condição for atingida, o valor defsm_statemuda e passamos para o próximo estado, o estado READ_SEND. - READ_SEND: Aqui lemos o valor do nosso sensor e simplesmente o adicionamos a uma variável chamada "stuff" e publicamos seus dados em um dispositivo chamado "source1". Ao executar este programa, sempre retornaremos ao estado WAIT antes de emitir uma segunda saída.
Finalmente, a partir da nossa estrutura switch-case, incrementamos o valor do nosso temporizador e temos um pequeno atraso de 1 milissegundo para que o tempo seja consistente com o nosso contador.
Neste ponto, você pode estar se perguntando por que programar tudo isso se podemos usar a programação sequencial usual? Imagine que você tenha três tarefas adicionais para executar dentro da sua rotina:
- Controle um servomotor usando PWM
- Exibir valores em uma tela de LCD
- Abrir ou fechar um portão
Ao executar múltiplas tarefas, a Máquina de Estados Finitos (FSM) permite o armazenamento mínimo de dados na memória local do dispositivo, além de as funções de estado poderem executar tarefas imediatas com base nos valores de entrada, e não apenas em uma única saída. Usando a FSM, você pode tomar decisões mais lógicas com menos tempo e energia necessários para implantar um programa logicamente testado. O valor da FSM reside em sua capacidade de computar processos no nível de um único dispositivo – o primeiro passo na computação Edge .
Testando
Nossos scripts funcionam como esperado; um novo dispositivo chamado "source1" é criado com uma variável chamada "stuff" que recebe e salva o valor do sensor a cada 60 segundos no Ubidots.


Melhorias e Ideias
Uma Máquina de Estados Finitos (MEF) pode ser implementada de diversas maneiras, e às vezes a estrutura `switch-case` pode ser tediosa de manter se você precisar gerenciar um grande número de estados. Uma melhoria adicional ao código explicado aqui na Parte 1 seria evitar a espera de 1 milissegundo entre cada caso analisado. Isso pode ser feito usando a função `millis()`.
Conclusão
O ser humano projetou nossos computadores para operarem à nossa imagem e semelhança, e as Máquinas de Estados Finitos (MEFs) são a programação que permite que as máquinas executem tarefas e forneçam aos humanos assistência e segurança valiosas em nosso dia a dia. À medida que a tecnologia e o conhecimento edge implementar MEFs se tornam cada vez mais baratos e acessíveis, continuaremos a ver computadores de placa única (SBCs) permeando fábricas e linhas de produção industriais. O controle de processos simples com programação de MEFs em SBCs continua a impulsionar soluções conectadas que complementam gateway e sistemas de controle lógico programável (CLPs) de marcas como Dell, Siemens, etc., ao mesmo tempo que oferece soluções locais que economizam $ em custos de hardware e operacionais.