Neste post, vamos desenvolver um sistema completo de monitoramento da qualidade do ar que combina hardware e software. Utilizaremos o sensor MQ-7, capaz de detectar a concentração de monóxido de carbono no ambiente, conectado a um microcontrolador ESP32, que possui capacidades de conectividade WiFi e Bluetooth. Os dados coletados serão enviados para a internet através do protocolo MQTT (Message Queuing Telemetry Transport), um protocolo leve e eficiente para comunicação entre dispositivos IoT (Internet das Coisas).
Para visualizar esses dados, criaremos um aplicativo móvel utilizando o framework Flutter, desenvolvendo um APK para dispositivos Android. Esse aplicativo se conectará ao mesmo broker MQTT para receber e exibir os dados em tempo real.
Veja a estrutura do projeto a baixo:

Fonte: Autor
Para realizar este projeto, você precisará dos seguintes componentes:
O MQ-7 é um sensor analógico que pode ser facilmente conectado ao ESP32. Vamos utilizar as seguintes conexões:

Fonte: Autor
Observe que o MQ-7 funciona geralmente em 5 V por isso criamos um divisor de tensão com dois resistores para baixar a tensão de entrada, permitindo uma leitura correta pelo ESP 32 considerando que ele opera em 3.3 V.
O MQ-7 é um sensor eletroquímico que contém um elemento sensível composto de dióxido de estanho (SnO2), cuja resistência varia quando exposto ao monóxido de carbono (CO). O sensor possui um aquecedor interno que opera em dois ciclos de temperatura: um ciclo de alta temperatura (em torno de 450°C) para a limpeza do sensor e um ciclo de baixa temperatura (cerca de 50°C) para a detecção do CO.
Quando o monóxido de carbono entra em contato com o elemento sensível, ocorre uma reação química que altera sua resistência elétrica. Essa variação é convertida em um sinal analógico que pode ser lido pelo ESP32.
O MQ-7 precisa de um período de aquecimento (geralmente 24-48 horas para calibração completa, mas 20-30 minutos para uso básico) para estabilizar suas leituras. Durante o desenvolvimento, é recomendável deixar o sensor ligado por pelo menos 20 minutos antes de confiar nas medições.
Usaremos o MQTT (Message Queuing Telemetry Transport) que é um protocolo de comunicação projetado para dispositivos IoT, conhecido por sua leveza e eficiência no uso de rede. Para entender como ele funciona, é importante conhecer seus principais componentes:
O broker é o coração da comunicação MQTT. Ele atua como um intermediário entre os dispositivos, gerenciando o fluxo de mensagens.
Principais responsabilidades do broker:
Exemplos de brokers populares: EMQX, Mosquitto, HiveMQ, RabbitMQ.
Os clientes MQTT são os dispositivos conectados ao broker e podem atuar de duas formas:
Publicador (Publisher):
sensor/temperatura.Assinante (Subscriber):
sensor/temperatura para monitorar os valores enviados pelo sensor.Importante: Um mesmo cliente pode ser publicador e assinante ao mesmo tempo!
O MQTT organiza a comunicação em tópicos, que funcionam como “endereços” para as mensagens.
Exemplo de estrutura de tópicos:
casa/sala/temperatura
casa/quarto/umidade
fabrica/motor1/status
Os assinantes podem ouvir mensagens de um tópico específico ou de um grupo deles usando curingas.
O MQTT permite configurar o nível de garantia na entrega de mensagens:
QoS 0 (Entrega pelo melhor esforço – “At Most Once”)
QoS 1 (Entrega garantida – “At Least Once”)
QoS 2 (Entrega única – “Exactly Once”)
Retained Message:
Last Will and Testament (LWT):
alertas/sensores dizendo: “Sensor desconectado”.Para testar e desenvolver aplicações com MQTT, utilizaremos o broker.emqx.io, um servidor gratuito oferecido pela EMQX. Esse broker é amplamente utilizado por desenvolvedores para prototipagem e testes, pois:
Embora o broker.emqx.io seja excelente para testes, seu uso em produção pode ser limitado, pois é um serviço público e pode sofrer instabilidades devido ao grande volume de acessos. Para sistemas que exigem maior controle, escalabilidade e segurança, algumas alternativas são:
Agora que entendemos mais sobre o que usaremos para nos comunicar vamos programar o ESP32 para ler os dados do sensor e enviá-los para um broker MQTT. Utilizaremos o Arduino IDE para programar o ESP32, então certifique-se de ter instalado a placa ESP32 em sua IDE e duas bibliotecas inportantes: PubSubClient – biblioteca de comunicação para o protocolo MQTT MQUnifiedsensor – Biblioteca de interpretação dos sensores MQ
Para instalar as bibliotecas na IDE do Arduino pesquise pelo nome dela no menu de bibliotecas e faça sua instalação.

Fonte: Autor

Fonte: Autor
#include <WiFi.h>
#include <PubSubClient.h>
#include <MQUnifiedsensor.h>
// Configurações de WiFi e MQTT
const char* WIFI_SSID = "REDE"; //Coloque o nome de sua rede wifi aqui
const char* WIFI_PASSWORD = "SENHA"; //Coloque sua senha wifi aqui
const char* MQTT_TOPIC = "esp32/air_quality"; //Coloque aqui o tópico
const char* MQTT_CLIENT_ID = "esp32_air_monitor"; //Coloque aqui seu ID
const char* MQTT_BROKER = "broker.emqx.io";
const int MQTT_PORT = 1883;
// Configurações do Sensor MQ-7
#define SENSOR_PLACA "ESP-32"
#define SENSOR_VOLTAGE_RESOLUTION 3.3
#define SENSOR_ANALOG_PIN 32
#define SENSOR_TYPE "MQ-7"
#define SENSOR_ADC_RESOLUTION 12
#define SENSOR_CLEAN_AIR_RATIO 27.5
#define SENSOR_PWM_PIN 5
// Intervalos de tempo para ciclos de aquecimento e leitura
const unsigned long HEATING_HIGH_DURATION = 60 * 1000; // 60 segundos em 5V
const unsigned long HEATING_LOW_DURATION = 90 * 1000; // 90 segundos em 1.4V
const unsigned long PUBLISH_INTERVAL = 5000; // 5 segundos entre publicações
// Objetos globais
MQUnifiedsensor MQ7(SENSOR_PLACA, SENSOR_VOLTAGE_RESOLUTION, SENSOR_ADC_RESOLUTION, SENSOR_ANALOG_PIN, SENSOR_TYPE);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
unsigned long lastPublishTime = 0;
unsigned long heatingStartTime = 0;
bool isHighVoltageHeating = true;
void setupWiFi() {
Serial.println("\nConectando ao WiFi...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi conectado!");
Serial.print("Endereço IP: ");
Serial.println(WiFi.localIP());
}
void reconnectMQTT() {
while (!mqttClient.connected()) {
Serial.print("Tentando conectar ao broker MQTT... ");
if (mqttClient.connect(MQTT_CLIENT_ID)) {
Serial.println("Conectado!");
} else {
Serial.print("Falha. Código: ");
Serial.println(mqttClient.state());
delay(5000);
}
}
}
void calibrateSensor() {
Serial.print("Calibrando sensor MQ-7... ");
float calcR0 = 0;
for(int i = 1; i <= 10; i++) {
MQ7.update();
calcR0 += MQ7.calibrate(SENSOR_CLEAN_AIR_RATIO);
Serial.print(".");
}
MQ7.setR0(calcR0 / 10);
Serial.println(" Concluído!");
// Verificações de erro na calibração
if (isinf(calcR0)) {
Serial.println("Erro: Circuito aberto! Verifique o cabeamento.");
while(1);
}
if (calcR0 == 0) {
Serial.println("Erro: Curto-circuito no pino analógico!");
while(1);
}
}
void setupSensor() {
// Configuração do modelo matemático de PPM
MQ7.setRegressionMethod(1); // _PPM = a * ratio^b
MQ7.setA(99.042);
MQ7.setB(-1.518);
// Inicialização do sensor
MQ7.init();
pinMode(SENSOR_PWM_PIN, OUTPUT);
// Calibração
calibrateSensor();
}
void setup() {
Serial.begin(115200);
// Configuração WiFi e MQTT
setupWiFi();
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
// Configuração do sensor
setupSensor();
// Marca o início do aquecimento
heatingStartTime = millis();
}
void loop() {
// Garante conexão MQTT
if (!mqttClient.connected()) {
reconnectMQTT();
}
mqttClient.loop();
unsigned long currentTime = millis();
// Ciclo de aquecimento e leitura do sensor MQ-7
if (isHighVoltageHeating) {
analogWrite(SENSOR_PWM_PIN, 255); // 5V
if (currentTime - heatingStartTime >= HEATING_HIGH_DURATION) {
isHighVoltageHeating = false;
heatingStartTime = currentTime;
}
} else {
analogWrite(SENSOR_PWM_PIN, 20); // 1.4V
if (currentTime - heatingStartTime >= HEATING_LOW_DURATION) {
isHighVoltageHeating = true;
heatingStartTime = currentTime;
}
}
// Leitura e publicação a cada 5 segundos
if (currentTime - lastPublishTime >= PUBLISH_INTERVAL) {
lastPublishTime = currentTime;
MQ7.update();
float ppm = MQ7.readSensor();
// Criar JSON com dados
String jsonPayload = "{\"sensor\":\"MQ-7\",\"ppm\":" + String(ppm) +
",\"heating\":" + String(isHighVoltageHeating ? 5 : 1.4) + "}";
Serial.print("Publicando: ");
Serial.println(jsonPayload);
// Publicar no tópico MQTT
mqttClient.publish(MQTT_TOPIC, jsonPayload.c_str());
}
}
Nosso código passa pelos seguintes processos:
Se atente para inserir no código a sua rede wifi, senha, nome desejado para o tópico no broker e ID escolhido.
Ao fazer o upload do código no ESP32 e abrir o monitor serial será exibido as informações de conexão com a internet, com o broker e a publicação dos dados no tópico, use essas informações para saber se o sistema esta rodando como planejado.
Aqui já usamos algumas outras tecnologias como o protocolo de rede Wifi para fazer a conexão via internet dos serviços, o arquivo JSON que é um formato comum de representação de dados para comunicação de programas e agora vamos continuar expandindo as tecnologias para a parte de aplicativos moveis.
O ambiente em que será desenvolvido o app é o Android Studio, existem outros com diversas configurações diferentes, mas para introduzir o desenvolvimento de apps esse é um bom ambiente.
.exeO Flutter é um framework de desenvolvimento multiplataforma criado pela Google, utilizado para criar aplicativos móveis, web e desktop a partir de uma única base de código para usar ele seguiremos os passos:
C:\\flutter)C:\\flutter\\binNo terminal, execute: flutter doctor
Este comando verificará sua instalação e mostrará quaisquer dependências faltantes.
Geralmente nesta etapa as dependências necessárias para este projeto já estão instaladas.
Na pasta onde você desenvolverá o projeto rode no terminal:
flutter create nome_do_seu_projeto
cd nome_do_seu_projeto
Agora temos um projeto flutter criado, vamos abrir ele no Android Studio.

Fonte: Autor
Acesse a pasta do projeto flutter e busque o arquivo
pubspec.yaml neste arquivo temos as seguintes dependências que são importantes para rodar o projeto, copie esse código e substitua.
name: air_quality_monitor
description: "A new Flutter Eletrogate app."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.7.0
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.20.5
# Ícones no estilo iOS
cupertino_icons: ^1.0.8
# Cliente MQTT para comunicação com dispositivos IoT
mqtt_client: ^9.8.1
# Gerenciamento de estado simplificado
provider: ^6.1.2
# Biblioteca para lidar com conversão de JSON
json_serializable: ^6.7.1
# Gerenciamento de logs na aplicação
logger: ^2.2.0
# Para requisições HTTP e comunicação com APIs
http: ^1.2.0
dev_dependencies:
flutter_test:
sdk: flutter
# Lints recomendadas para boas práticas de código
flutter_lints: ^5.0.0
# Ferramentas para geração de código a partir de anotações
build_runner: ^2.4.7
flutter:
uses-material-design: true
Agora vamos instalar as dependências necessárias, abra o terminal clicando no ícone inferior:

Fonte: Autor
Instale as dependências rodando o seguinte comando no terminal:
flutter pub get
Dentro da pasta do app tem uma pasta chamada lib, dentro desta pasta tem um arquivo chamado main.dart acesse ele, apague o código existente e cole este código.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'dart:convert';
import 'package:provider/provider.dart';
import 'package:logger/logger.dart';
// Inicialização do logger para registro de eventos no aplicativo
final logger = Logger(
printer: PrettyPrinter(methodCount: 0, lineLength: 80),
);
void main() {
// Ponto de entrada do aplicativo Flutter
runApp(
// Utilizando ChangeNotifierProvider para gerenciar o estado global do app
ChangeNotifierProvider(
create: (_) => AirQualityModel(),
child: const MyApp(),
),
);
}
// Modelo para gerenciar os dados do sensor de qualidade do ar
class AirQualityModel extends ChangeNotifier {
Map<String, dynamic> _sensorData = {"ppm": 0.0};
bool _isConnected = false;
String _connectionStatus = "Desconectado";
Map<String, dynamic> get sensorData => _sensorData;
bool get isConnected => _isConnected;
String get connectionStatus => _connectionStatus;
void updateSensorData(Map<String, dynamic> data) {
_sensorData = data;
notifyListeners();
}
void setConnectionStatus(bool connected, String status) {
_isConnected = connected;
_connectionStatus = status;
notifyListeners();
}
// Método para interpretar a qualidade do ar com base no valor de PPM
String getAirQualityStatus() {
final ppm = _sensorData["ppm"] as double;
if (ppm < 9) return "Excelente";
if (ppm < 50) return "Boa";
if (ppm < 100) return "Moderada";
if (ppm < 300) return "Ruim";
return "Perigosa";
}
// Método para determinar a cor que representa a qualidade do ar
Color getStatusColor() {
final ppm = _sensorData["ppm"] as double;
if (ppm < 9) return Colors.green;
if (ppm < 50) return Colors.lightGreen;
if (ppm < 100) return Colors.yellow;
if (ppm < 300) return Colors.orange;
return Colors.red;
}
}
// Widget principal do aplicativo
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // Remove o banner de debug
title: 'Monitor de Qualidade do Ar',
theme: ThemeData(
// Definição do tema do aplicativo
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
useMaterial3: true, // Usando Material Design 3
fontFamily: 'Roboto',
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
foregroundColor: Colors.blue,
),
cardTheme: CardTheme(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
),
darkTheme: ThemeData(
// Tema escuro do aplicativo
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
useMaterial3: true,
fontFamily: 'Roboto',
cardTheme: CardTheme(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
),
themeMode: ThemeMode.system, // Segue o tema do sistema
home: const SensorScreen(),
);
}
}
// Tela principal do sensor utilizando Hooks para gerenciamento de estado local
class SensorScreen extends HookWidget {
const SensorScreen({super.key});
// Configurações do broker MQTT
final String broker = "broker.emqx.io"; // Servidor MQTT público para teste
final int port = 1883; // Porta padrão para MQTT sem TLS
final String clientId = "flutter_air_monitor"; // Identificador único do cliente
final String topic = "esp32/air_quality"; // Tópico para inscrição
@override
Widget build(BuildContext context) {
// Usando hooks para memorizar o cliente MQTT e evitar recriação
final client = useMemoized(() => MqttServerClient(broker, clientId), []);
// Efeito que executa na montagem do widget
useEffect(() {
// Conecta ao broker MQTT quando o widget é inicializado
_connectMQTT(client, context);
// Função de limpeza executada na desmontagem do widget
return () {
logger.i("Desconectando do broker MQTT");
client.disconnect();
};
}, []);
// Obtém o modelo de dados do Provider
final model = Provider.of<AirQualityModel>(context);
return Scaffold(
// AppBar com design moderno e transparente
appBar: AppBar(
title: const Text(
"Qualidade do Ar",
style: TextStyle(fontWeight: FontWeight.bold),
),
actions: [
// Indicador de status de conexão
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Icon(
model.isConnected ? Icons.wifi : Icons.wifi_off,
color: model.isConnected ? Colors.green : Colors.red,
),
const SizedBox(width: 8),
Text(model.connectionStatus,
style: TextStyle(
color: model.isConnected ? Colors.green : Colors.red,
fontSize: 12,
)),
],
),
),
],
),
// Corpo principal com layout responsivo
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Card principal com informações do sensor
_buildMainSensorCard(context, model),
const SizedBox(height: 24),
// Informações adicionais sobre o dispositivo
_buildDeviceInfoCard(context),
const SizedBox(height: 24),
// Card com informações sobre qualidade do ar
_buildAirQualityInfoCard(context),
],
),
),
),
),
);
}
// Card principal com a leitura do sensor
Widget _buildMainSensorCard(BuildContext context, AirQualityModel model) {
final ppm = model.sensorData["ppm"] as double;
final quality = model.getAirQualityStatus();
final statusColor = model.getStatusColor();
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Ícone animado que muda de cor conforme a qualidade do ar
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: ppm),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Column(
children: [
Icon(
CupertinoIcons.wind,
size: 80,
color: statusColor,
),
const SizedBox(height: 16),
Text(
"Monóxido de Carbono (CO)",
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 24),
Text(
"${value.toStringAsFixed(1)} ppm",
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
color: statusColor,
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(
"Qualidade: $quality",
style: TextStyle(
fontWeight: FontWeight.bold,
color: statusColor,
),
),
),
],
);
},
),
],
),
),
);
}
// Card com informações sobre o dispositivo
Widget _buildDeviceInfoCard(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Informações do Dispositivo",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildInfoRow(context, "Sensor", "MQ-7 (Monóxido de Carbono)"),
_buildInfoRow(context, "Dispositivo", "ESP32"),
_buildInfoRow(context, "Localização", "Sala Principal"),
_buildInfoRow(context, "Última Atualização", "Agora"),
],
),
),
);
}
// Linha de informação para os cards
Widget _buildInfoRow(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.w500,
),
),
Text(
value,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
);
}
// Card com informações sobre qualidade do ar
Widget _buildAirQualityInfoCard(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Níveis de CO e Significado",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildQualityRow(context, "0 - 9 ppm", "Excelente", Colors.green),
_buildQualityRow(context, "9 - 50 ppm", "Boa", Colors.lightGreen),
_buildQualityRow(context, "50 - 100 ppm", "Moderada", Colors.yellow),
_buildQualityRow(context, "100 - 300 ppm", "Ruim", Colors.orange),
_buildQualityRow(context, "> 300 ppm", "Perigosa", Colors.red),
],
),
),
);
}
// Linha de informação para o card de qualidade do ar
Widget _buildQualityRow(BuildContext context, String range, String quality, Color color) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 12),
Text(
range,
style: const TextStyle(fontWeight: FontWeight.w500),
),
const Spacer(),
Text(
quality,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
// Método para conectar ao broker MQTT
Future<void> _connectMQTT(
MqttServerClient client, BuildContext context) async {
final model = Provider.of<AirQualityModel>(context, listen: false);
// Configuração do cliente MQTT
client.logging(on: true); // Ativa logs para debug
client.port = port;
client.keepAlivePeriod = 60; // Período para manter conexão ativa
// Callbacks para eventos de conexão
client.onConnected = () {
logger.i("MQTT Conectado ao broker $broker");
model.setConnectionStatus(true, "Conectado");
};
client.onDisconnected = () {
logger.w("MQTT Desconectado");
model.setConnectionStatus(false, "Desconectado");
};
client.onSubscribed = (String topic) {
logger.i("Inscrito no tópico: $topic");
};
// Mensagem de conexão MQTT
final connMessage = MqttConnectMessage()
.withClientIdentifier(clientId) // ID único do cliente
.startClean() // Inicia uma sessão limpa
.withWillQos(MqttQos.atMostOnce); // Qualidade de serviço
client.connectionMessage = connMessage;
try {
// Tentativa de conexão ao broker MQTT
logger.i("Conectando ao broker MQTT: $broker:$port");
await client.connect();
} catch (e) {
logger.e("Erro na conexão MQTT: $e");
model.setConnectionStatus(false, "Erro: $e");
client.disconnect();
return;
}
// Verifica se o cliente está conectado
if (client.connectionStatus!.state == MqttConnectionState.connected) {
logger.i("Cliente conectado");
// Inscreve-se no tópico para receber dados
client.subscribe(topic, MqttQos.atMostOnce);
// Configura listener para mensagens recebidas
client.updates!.listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
if (messages == null) return;
final MqttPublishMessage recMessage = messages[0].payload as MqttPublishMessage;
// Converte bytes da mensagem para string
final String payload = MqttPublishPayload.bytesToStringAsString(
recMessage.payload.message
);
logger.d("Mensagem recebida: $payload");
try {
// Decodifica o JSON recebido
final data = jsonDecode(payload);
model.updateSensorData(data);
} catch (e) {
logger.e("Erro ao decodificar JSON: $e");
}
});
} else {
logger.e("Cliente desconectado");
model.setConnectionStatus(false, "Falha na conexão");
client.disconnect();
}
}
}
1. Importações:
flutter/cupertino.dart: Widgets do estilo Cupertino (iOS).flutter/material.dart: Widgets do Material Design (Android).mqtt_client/mqtt_client.dart e mqtt_client/mqtt_server_client.dart: Biblioteca para comunicação MQTT.flutter_hooks/flutter_hooks.dart: Biblioteca para utilizar hooks no Flutter (simplifica o gerenciamento de estado).dart:convert: Funções para codificar e decodificar dados JSON.provider/provider.dart: Biblioteca para gerenciamento de estado.logger/logger.dart: Biblioteca para registro de logs na aplicação.2. Funções main() e MyApp:
main() inicia o aplicativo e configura o ChangeNotifierProvider para gerenciar o estado global.MyApp é um widget StatelessWidget que configura o aplicativo:
SensorScreen como a tela inicial.3. AirQualityModel (Modelo de dados):
ChangeNotifier para gerenciar os dados do sensor.4. SensorScreen (HookWidget):
SensorScreen é um widget HookWidget, permitindo o uso de hooks.broker, port, clientId, topic: Configurações para a conexão MQTT.client: Instância do MqttServerClient (conexão com o broker MQTT).useMemoized Hook:
MqttServerClient usando as configurações do broker e cliente.useEffect Hook:
_connectMQTT para estabelecer a conexão MQTT quando o widget é construído.Scaffold com um AppBar exibindo o título “Qualidade do Ar” e indicador de status de conexão.5. Widgets auxiliares:
_buildMainSensorCard: Cria o card principal com animação e indicador visual de qualidade._buildDeviceInfoCard: Exibe informações sobre o dispositivo sensor._buildAirQualityInfoCard: Mostra uma tabela com os diferentes níveis de CO e seus significados._buildInfoRow e _buildQualityRow: Componentes reutilizáveis para as linhas nos cards.6. _connectMQTT (Função de Conexão MQTT):
MqttServerClient e o BuildContext como parâmetros.esp32/air_quality com QoS atMostOnce.7. Protocolo MQTT:
esp32/air_quality para receber dados do sensor.atMostOnce é usado para entrega eficiente das mensagens.Agora vamos emular um dispositivo móvel para ver como o app ficará, você pode alterar seu layout, cores, funções e etc usando esse emulador como guia do que esta sendo feito.
No Android Studio criaremos um emulador seguindo os passos e baixando os recursos necessários.

Fonte: Autor

Fonte: Autor

Fonte: Autor

Fonte: Autor
Agora já escolhemos um aparelho que iremos emular e a sua versão de android.
No Andriod Studio:

Fonte: Autor
Aguardamos até o dispositivo ser emulado.

Fonte: Autor
Não se preocupe a primeira vez que é emulado o dispositivo demora bastante pois deve baixar algumas configurações para funcionar bem.
Depois que o dispositivo aparecer na tela rode no terminal, na pasta do app, no nosso caso air_quality_monitor: flutter run
Se tudo der certo terá a visão da tela que construímos.

Fonte: Autor
Agora vamos fazer um apk dessa aplicação para rodar em celulares android
Rode no terminal do Android Studio na pasta do projeto:flutter build apk --release
Depois que o processo terminar o apk estará em: build/app/outputs/flutter-apk/app-release.apk
Com o arquivo do apk basta passar ele para o dispositivo android e instalar ele, talvez seja necessário permitir a instalação de apks em seu dispositivo, mas é um processo simples.
Este é o app rodando em tempo real:
Este post marcou o início da integração entre programação Android e IoT, mas há diversas melhorias e evoluções possíveis:
HANWEI ELECTRONICS. MQ-7 Semiconductor Sensor for Carbon Monoxide. Zhengzhou, China, 2014. Disponível em: https://www.sparkfun.com/datasheets/Sensors/Biometric/MQ-7.pdf. Acesso em: 7 mar. 2025.
MQTT. MQTT: The Standard for IoT Messaging. 2024. Disponível em: https://mqtt.org/. Acesso em: 7 mar. 2025.
HIVEMQ. MQTT Essentials – A Gentle Introduction to the MQTT Protocol. 2024. Disponível em: https://www.hivemq.com/mqtt-essentials/. Acesso em: 7 mar. 2025.
EMQX. EMQX – The World’s Most Scalable MQTT Broker. 2024. Disponível em: https://www.emqx.io/. Acesso em: 7 mar. 2025.
|
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.
Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!