Este projeto tem como objetivo implementar uma biblioteca para interpretar expressões aritméticas no ESP32/ESP8266 visando o uso na tomada de decisão em cenários de automação em geral. O projeto também demonstra a utilização da biblioteca.
À primeira vista, o desenvolvimento de um interpretador de expressões aritméticas para microcontroladores pode parecer um exercício acadêmico ou de pouca utilidade. No entanto, à medida que aplicações embarcadas se tornam mais complexas e conectadas, cresce a necessidade por configurações dinâmicas, ajustes em tempo de execução e flexibilidade na lógica de controle, especialmente em soluções de automação residencial, industrial e agrícola.
Ao permitir a avaliação de expressões matemáticas diretamente no dispositivo, abre-se a possibilidade de criar sistemas altamente parametrizáveis. Por exemplo, um usuário pode ajustar condições de ativação de relés, alarmes ou outros atuadores a partir de expressões envolvendo múltiplos sensores — tudo isso sem necessidade de regravação do firmware.
Mais ainda, ao integrar esse interpretador a uma interface web, é possível fornecer ao usuário final um painel para editar regras ou fórmulas diretamente do navegador. Essas regras podem ser armazenadas no próprio dispositivo (via SPIFFS ou LittleFS ou Banco de Dados) e verificadas periodicamente para acionar lógicas de decisão, criando uma base de regras configurável, similar a um mini motor de inferência, mas leve e prático.
A motivação principal, portanto, é oferecer uma ferramenta flexível e em forma de biblioteca, compatível com plataforma ESP32/ESP8266 uma vez que existem restrições críticas de memória no Arduino Uno/Nano. O interpretador proporciona um grau de liberdade na reconfiguração de comportamentos em sistemas embarcados, tornando-os mais flexíveis, modulares e adaptáveis a longo prazo.
Aplicações Práticas
A utilidade de um interpretador de expressões em microcontroladores vai muito além de calcular operações matemáticas simplesmente. Ele pode ser o núcleo de sistemas embarcados que tomam decisões com base em expressões configuráveis, permitindo que o comportamento do sistema seja ajustado facilmente, sem necessidade de recompilar o firmware.
Veja alguns cenários práticos:
Imagine um sistema de irrigação com sensores de umidade, temperatura e hora atual. Em vez de codificar regras fixas, o usuário pode inserir expressões como:
(UMIDADE < 40 && HORA >= 6 && HORA <= 8) || (TEMPERATURA > 30 && UMIDADE < 50)
Essa expressão, salva em SPIFFS e avaliada periodicamente, define se a irrigação deve ser ativada, oferecendo flexibilidade total na lógica de controle.
Um sistema com sensores de corrente e tensão pode avaliar:
POTENCIA > 800 && DIA_DA_SEMANA != 0
e desligar cargas não prioritárias quando o consumo ultrapassar determinado valor. O interpretador atua como motor de decisão baseado em expressões simples, fáceis de editar via interface web.
Em ambientes monitorados por sensores (como temperatura, gás, fumaça, portas), as condições para disparo de alarme podem ser definidas com expressões do tipo:
GAS > 200 || (TEMP > 60 && PORTA_ABERTA == 1)
Essas regras podem ser modificadas sem reprogramar o ESP32/ESP8266, apenas editando via uma interface web.
Em uma aplicação com painel web (AsyncWebServer), o usuário pode testar expressões diretamente na interface, como uma calculadora técnica:
cos(PI/3)^2 + sin(PI/3)^2
Esse uso combina a força do interpretador com visualização imediata de resultados e serve tanto para testes quanto para aprendizado ou depuração em campo.
Com a combinação de variáveis, funções matemáticas e a possibilidade de expansão com funções externas registradas por call-back, é possível montar um mini motor de inferência baseado em regras, útil para sistemas de automação com requisitos leves e alta flexibilidade.
A partir de uma dada expressão aritméticas em notação infixada (padrão standard da matemática usado em calculadoras científicas), a biblioteca faz a análise sintática da expressão dividindo em tokens (partes que compõem a expressão) e, em seguida, transformando na notação pós fixada (padrão utilizado nas calculadoras HP) para avaliar o resultado. Se na expressão existirem variáveis ou funções, a biblioteca procura numa lista interna, existindo, o valor é recuperado para entrar na avaliação do resultado. As variáveis podem ser registradas (via função pública da classe) ou serem definidas através de atribuição na própria expressão (ex: A=10;A/2), sendo que múltiplas expressões podem ser definidas na expressão usando-se o separador ‘;’. Quando uma variável é alterada para um novo valor, um call-back poder ser definido para tratar qual variável sofreu alteração e tomar alguma decisão. Caso a função não seja encontrada e, se um call-back estiver definido, o nome da função e parâmetros são passados para o call-back retornar o valor a ser considerado no cálculo da expressão. Desta forma, a aplicação que utiliza a biblioteca participará do cálculo da expressão recebendo do call-back os valores a serem considerados para compor o resultado da expressão (como se fosse macro expansão), para isso consultando sensores, bases de dados ou fazendo outros cálculos. Uma planilha eletrônica, como EXCEL por exemplo, nada mais é do que um interpretador de expressões ou fórmulas que são organizadas em linhas e colunas.
Exemplo de expressão Infixada: (2*3 + 5*4)
Exemplo da conversão pós fixada: 2 3 * 5 4 * +
A implementação foi para a família ESP32/ESP8266 pois a análise sintática consome um pouco mais de memória devido ao uso de estruturas mais complexas e, na família AVR (NANO/UNO), tais estruturas não existem, restando o uso de estruturas de alocação estática que consome mais recursos e só há 2K de SRAM. Com isso inviabilizou-se a portabilidade para esta plataforma.
Visão geral da arquitetura
A biblioteca é composta por três etapas principais:
Principais estruturas (conforme o cabeçalho):
Tipo numérico: a API pública retorna float em evaluate(…) por eficiência no ESP32; internamente podem existir cálculos em double. O valor NAN é usado para sinalizar resultados inválidos/indefinidos.
Tokenização
A tokenização percorre a expressão e emite tokens:
Observações de ambiguidade
Conversão infixa → pós-fixa (Shunting-Yard)
A função infixToPostfix(…) usa uma pilha de operadores:
Precedência e associatividade
Da maior para a menor:
Avaliador (RPN)
A evaluatePostfix(…) percorre os tokens pós-fixos:
Erros de execução (divisão por zero, função não encontrada, aridade incorreta etc.) chamam reportError(…) e o resultado final tende a ser NAN.
Variáveis e funções
Variáveis
Funções
Múltiplas expressões com ;
O método público evaluate(const String& expression):
Operadores suportados e applyOperator()
A implementação provê applyOperator(const String& op, float a, float b) para binários e tratamento específico para unários no parser.
Operadores previstos no projeto:
Divisão por zero resulta em NAN e emite reportError(F(“Division by zero”), token).
Potência com expoente fracionário e base negativa pode virar NAN (sem partes complexas).
Erros e depuração
Política típica de erro:
Convenções de nomenclatura
Complexidade e consumo
Contrato público (resumo)
Operadores Aritméticos
| Símbolo | Operação | Exemplo |
| + | Soma | 2+3 |
| – | Subtração | 5-1 |
| * | Multiplicação | 3*4 |
| / | Divisão | 6/2 |
| % | Módulo | 7%4 |
| # | Potência | 2#3 = 8 |
Operadores Relacionais
| Símbolo | Operação | Exemplo |
| == | Igualdade | a == b |
| != | Diferente | a != b |
| > | Maior | a > b |
| < | Menor | a < b |
| >= | Maior ou igual | a >= b |
| <= | Menor ou igual | a <= b |
Operadores Lógicos
| Símbolo | Função Lógica | Exemplo |
| & | E lógico (AND) | a > 0 & b < 10 |
| | | | | OU lógico (OR) |
| ! | NÃO lógico (NOT) | !a == 1 |
| ^ | OU Exclusivo (XOR) | a ^ b |
Operadores Auxiliares
| Símbolo | Função | Exemplo |
| = | Atribuição de valor | a = 2 + 3 |
| ( | Abre parêntese | (a + b) * c |
| ) | Fecha parêntese | (a + b) * c |
| ; | Separador de expressões | a=2; b=3; a+b |
Números:
Você pode definir números com dígitos de 0 a 9. Adicionalmente, você deve usar o ponto para separar a parte decimal.
Ex: 2.1 + 10.333
Variáveis:
Você pode definir variáveis usando letras de A a Z e números de 0 a 9 começando com letra não superando 15 caracteres. Não faz diferença se as letras sejam maiúsculas ou minúsculas.
Ex: Temp1, Temp2, Umidade, Luminosidade
Funções:
Você pode definir funções usando letras de A a Z até 15 caracteres. Não faz diferença se as letras sejam maiúsculas ou minúsculas. As funções podem conter até 10 parâmetros numéricos.
Ex: MMC(2,5), MDC(2,5). MAX(2,4,5,-1)
Funções Pré-Definidas:
| Função (assinatura) | Descrição | Exemplo | Domínio / Notas |
|---|---|---|---|
SIN(x) | Seno de um ângulo | SIN(PI/6) = 0.5 | – |
COS(x) | Cosseno de um ângulo | COS(PI/3) = 0.5 | – |
TAN(x) | Tangente de um ângulo | TAN(PI/4) = 1 | x ≠ PI/2 + k·PI |
ASN(x) | Arco-seno (inverso do seno) | ASN(0.5) = PI/6 | x ∈ [-1, 1] |
ACS(x) | Arco-cosseno (inverso do cosseno) | ACS(0.5) = PI/3 | x ∈ [-1, 1] |
ATN(x) | Arco-tangente (inverso da tangente) | ATN(1) = PI/4 | x ∈ ℝ |
INT(x) | Parte inteira (trunca em direção a 0) | INT(-2.9) = -2 | – |
ABS(x) | Valor absoluto | ABS(-3) = 3 | – |
FRAC(x) | Parte fracionária | FRAC(3.75) = 0.75 | Para negativos, definir política (ex.: x - floor(x)) |
LOG(x) | Logaritmo na base 10 | LOG(100) = 2 | x > 0 |
LN(x) | Logaritmo natural (neperiano) | LN(E) = 1 | x > 0 |
EXP(x) | Exponencial e^x | EXP(1) = E | – |
SQR(x) | Raiz quadrada | SQR(16) = 4 | x ≥ 0 (sem complexos) |
CUR(x) | Raiz cúbica | CUR(27) = 3 | x ∈ ℝ |
SGN(x) | Sinal do número | SGN(-5) = -1 | Retorna 1 (positivo), 0 (zero) ou -1 (negativo) |
FACT(n) | Fatorial | FACT(5) = 120 | n inteiro, n ≥ 0 |
HYPSIN(x) | Seno hiperbólico (sinh) | HYPSIN(0) = 0 | – |
HYPCOS(x) | Cosseno hiperbólico (cosh) | HYPCOS(0) = 1 | – |
HYPTAN(x) | Tangente hiperbólica (tanh) | HYPTAN(0) = 0 | – |
HYPASN(x) | Arco-seno hiperbólico (asinh) | HYPASN(1) ≈ 0.8814 | x ∈ ℝ |
HYPACS(x) | Arco-cosseno hiperbólico (acosh) | HYPACS(1) = 0 | x ≥ 1 |
HYPATN(x) | Arco-tangente hiperbólica (atanh) | HYPATN(0.5) ≈ 0.5493 | ` |
PRIME(n) | Verifica se n é primo (1=verdadeiro, 0=falso) | PRIME(23) = 1, PRIME(22) = 0 | n inteiro |
Variáveis Pré-Definidas:
| Variável | Descrição |
|---|---|
PI | Número PI |
E | Número neperiano |
Last | Último valor calculado |
Exemplos de Expressões:
| Expressão | Resultado | Comentário |
|---|---|---|
A=cos(PI/3);B=sin(PI/3);A#2+B#2 | A=0.50 B=0.87 1 | Define A e B; soma dos quadrados → identidade trigonométrica cos²+sin²=1. |
2*3+2*2 | 10 | Precedência: multiplicações antes da soma (6 + 4). |
CUR(27) | 3 | Raiz cúbica. |
LOG(10) | 1 | Log base 10. |
ln(E) | 1 | Log natural; funções são case-insensitive. |
PRIME(23) | 1 | 23 é primo → verdadeiro (1). |
PRIME(22) | 0 | 22 é composto → falso (0). |
FACT(3) | 6 | Fatorial de 3. |
SQR(16) | 4 | Raiz quadrada. |
(-1)+(2-3) | -2 | Parênteses e números negativos. |
-10-20 | -30 | Menos unário seguido de subtração. |
Observação importante: existe uma diretiva de compilação no ExpressionParser.h que pode ser “descomentada” durante o desenvolvimento da aplicação que utiliza a biblioteca. Assim mensagens adicionais poderão ser geradas e mostradas na console.
//#define DEBUG // Comente esta linha para desativar DEBUG
A biblioteca foi projetada para ser uma ferramenta para o desenvolvedor produzir aplicações mais ricas e foi pensada para “regras-como-dados”. Em vez de codificar if/else fixos no firmware, você expõe indicadores do negócio objeto da aplicação a ser desenvolvida (sensores, estados, configurações) como VARIÁVEIS e FUNÇÕES resolvidas/implementadas por call-backs. Assim, as variáveis/funções possibilitam a criação de regras que viram expressões editáveis (em arquivo, NVS/Preferences, OTA, web UI) — sem recompilar e flexibilizando a usabilidade da aplicação pelo usuário final.
Como pensar “indicadores do negócio” → VARIÁVEIS e FUNÇÕES
Identifique medidores: tudo que o usuário final usa para decidir algo. Ex.: TEMP, HUM, LUX, PRESSAO, FLOW, VAZAO, EFICIENCIA, PRODUTIVIDADE, etc.
Mapeie estados e modos: LED, PUMP, ALARM, MODE, SETPOINT, HYST.
Extraia utilidades de negócio como funções: MAP(x,inMin,inMax,outMin,outMax), HYST(x,low,high), MEDIANA(a,b,c), IN_RANGE(x,a,b), DEBOUNCE(x,ms), etc.
Persistência e ajustes: exponha parâmetros que o usuário salva por expressão, ex.: THRESH=30, PERIODO=900, MODO=1.
Regra prática: variáveis retornam um valor atual; funções calculam/transformam a partir de argumentos. Use nomes case-insensitive e retorne
NANquando algo não se aplicar (a LIB trata como erro/ausência).
Tabela de Call-backs
| Call-back | Assinatura | Papel na avaliação | Retorno/Efeito | Uso típico |
|---|---|---|---|---|
VariableCallback | float(const String& nameUpper) | Resolver variáveis lidas na expressão. | Retorne o valor; NAN se desconhecida/indisponível. | Sensores (TEMP, HUM), estados (LED), relógio (EPOCH, HOUR), constantes (PI). |
FunctionCallback | float(const String& nameUpper, const std::vector<String>& args) | Implementar funções de domínio chamadas na expressão. | Retorne resultado; NAN para erro/assinatura inválida. | MAP(...), HYST(...), MEDIANA(...), utilidades de processo. |
SettingVarCallback | void(const String& nameUpper, const float value) | Tomar decisões quando atribuições são feitas na expressão. | Efeito colateral (p.ex. salvar em NVS, alterar setpoint). | THRESH=30, MODO=1, KP=2.1. |
ErrorCallback | void(const String& message) | Receber mensagens de erro da avaliação. | Log/telemetria/alerta. | Print em Serial, buffer circular, envio via WS. |
CallbackType | float(const String&, const std::vector<String>&) | Alias geral compatível com funções. | Igual ao FunctionCallback. | Compatibilidade/legado. |
Notas:
Converta
nameUpperpara maiúsculas (ou já trate como maiúsculo) para facilitar case-insensitive.Em
FunctionCallback, os argumentos vêm comoString; converta parafloat(ex.:strtof(args[i].c_str(), nullptr)ouargs[i].toFloat()).
Boas práticas (essenciais)
Allowlist: reconheça apenas nomes esperados. Tudo o que não corresponder → NAN (falha segura).
Sem efeitos nas funções: mantenha FunctionCallback puro (sem side-effects). Use SettingVarCallback para mudar estado/salvar.
Perfomance: se a mesma expressão roda com alta frequência, considere usar CACHE ou temporização (ex.: suponha DOLAR como variável. Só recupere a cotação do DOLAR externamente uma vez no dia ou estabeleça intervalos menores para a atualização).
Persistência: para parâmetros de usuário, escreva em Preferences com debounce (p.ex. 3–10 s) para reduzir desgaste de flash.
Observabilidade: logue via ErrorCallback para acompanhamento na interface.
Com esses call-backs, você organiza o firmware como plataforma de execução, e deixa a “regra” virar dados (expressões). Resultado: parametrização real, mais reuso entre projetos e ajustes de produção sem recompilar.
Foram desenvolvidas duas aplicações para a demonstração do uso da biblioteca: uma BasicDemo que implementa a interação via Console Serial e uma mais avançada, WebDemo, utilizando uma interface HTML demonstrando a aplicabilidade em Automação.
Materiais Necessários (WebDemo)
1x Módulo WiFi ESP32 Bluetooth 30 pinos
1x Protoboard 400 Pontos
1x Sensor de Umidade e Temperatura DHT11
1x Sensor Fotoresistor LDR de 5mm
1x Led Difuso 5mm Vermelho
1x Resistor 10K 1/4W (10 Unidades) (LDR)
1x Resistor 4K7 1/4W (10 Unidades) (DHT)
1x Resistor 220R 1/4W (10 Unidades)(LED)
Opcional (BasicDemo)
1x Módulo WiFi ESP8266 NodeMcu v3 – Lolin
1x Protoboard 400 Pontos
WebDemo
A aplicação WebDemo utiliza um HTML com 3 abas:
Outras características da aplicação WebDemo:
A seguir estão as telas do HTML:

Figura 1 – Aba Expressões do HTML

Figura 2 – Aba Automação do HTML

Figura 3 – Aba Informações do HTML

Figura 4 – Diagrama do Circuito WebDemo
BasiDemo
A aplicação BasicDemo foi projetada para interagir com a Console Serial recebendo expressões digitadas na Console e esperando um NEWLINE. Adicionalmente, nenhum componente é utilizado, mas apenas o microcontrolador.
Características:
A seguir a tela do IDE do Arduino mostrando a interação com a console:

Figura 5- Tela da Console do IDE

Figura 6- Diagrama do Circuito BasicDemo
A biblioteca é distribuída seguindo o padrão do Arduino IDE no formato ZIP. A instalação é feita através do IDE conforme figura a seguir. Dentro do ZIP estão também as duas aplicações de demonstração BasicDemo e WebDemo.

Figura 7 – Instalação da Biblioteca no IDE do Arduino
A ExpressionParser transforma lógica de controle em dados configuráveis. Em vez de “recompilar” regras no firmware, você descreve expressões que o dispositivo interpreta em tempo real — com variáveis (sensores, relógio, estado), funções (pré-definidas e/ou estendidas) e atribuições. Esse modelo eleva substancialmente a parametrização de aplicações de automação, com ganhos claros:
Em síntese: a biblioteca promove um “regras-como-dados” para automação embarcada. Possibilita flexibilidade, manutenibilidade e velocidade de iteração. Ao combinar expressões parametrizáveis com um conjunto enxuto de regras, o projeto passa a escalar com menos risco e mais previsibilidade — do protótipo ao produto.
|
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!