Blog

  • Olá, mundo!

    Boas-vindas ao WordPress. Esse é o seu primeiro post. Edite-o ou exclua-o, e então comece a escrever!

  • IoT + Cloud: Arquitetura em 3 camadas

    IoT + Cloud: Arquitetura em 3 camadas

    Existe um problema clássico em projetos de IoT que qualquer engenheiro de telecom conhece bem: milhares, às vezes milhões, de dispositivos gerando dados no campo, e nenhuma capacidade real de processar tudo isso na borda. Sensor de temperatura industrial, câmera de vigilância, medidor de energia smart, veículo conectado. Cada um com sua restrição de memória, energia e processamento.

    Cada um produzindo um fluxo constante de dados que simplesmente não cabe no hardware local.

    A resposta natural é integrar IoT com Cloud Computing como uma divisão de responsabilidades bem definida: a borda coleta e transmite, a nuvem processa e analisa. O que parece simples no papel revela uma série de desafios de engenharia que ainda estão sendo resolvidos em 2025 e é exatamente sobre isso que este post trata.

    A arquitetura em três camadas

    Iot Cloud Topology V5

    A topologia canônica de uma solução cloud-based IoT se organiza em três camadas distintas.

    Na base estão os dispositivos IoT, sensores, atuadores, câmeras, wearables, veículos conectados. Cada device tem um endereço IP único e capacidade de enviar e receber dados via rede, mas com restrições severas de processamento e energia.

    Eles não foram feitos para pensar; foram feitos para perceber e enviar dados.

    No meio fica a camada de coleta e agregação, o que o diagrama clássico chama de IoT Data Collection. É aqui que mora o gateway físico ou o middleware de software. Responsável por pré-processar, filtrar e comprimir os dados antes de enviá-los para a nuvem. Sem essa camada funcionando bem, você vai inundar o núcleo da rede com ruído desnecessário e estourar o budget de transferência de dados em semanas.

    No topo, a camada cloud provê toda a infraestrutura elástica: computação virtualizada, armazenamento distribuído, analytics em escala, modelos de ML, digital twins. 

    Os dispositivos podem cobrir as mais diversas verticais de atuação. A mesma arquitetura de três camadas sustenta todos esses cenários, com variações no protocolo de comunicação (MQTT, CoAP, HTTP) e no middleware escolhido.

    Desafios das arquiteturas de IoT-Cloud

    A integração IoT-Cloud não é um problema resolvido. Existe uma lista bem documentada de questões que continuam sendo objeto de pesquisa ativa:

    Segurança e privacidade encabeçam a lista. Dispositivos com hardware limitado são superfícies de ataque fáceis. A autorização adequada garantindo que só usuários autorizados acessem dados críticos continua sem solução padronizada.

    Comunicação desnecessária de dados: sem pré-processamento inteligente no gateway, o volume de dados que sobe para a cloud gera delays inaceitáveis e custo desnecessário. O gateway precisa decidir o que vale a pena transmitir.

    Deploy de IPv6: IPv4 não endereça todos os dispositivos de uma frota IoT relevante. A coexistência IPv4/IPv6 ainda traz riscos de segurança específicos

    Alocação e gerenciamento de recursos, interoperabilidade entre plataformas, service discovery para frotas móveis, scaling com segurança proporcional e eficiência energética completam a lista. Cada um desses é um post separado esperando para ser escrito por aqui, ou um produto aguardando ser construído 😉.

    Conclusão

    Dez anos atrás, players como a AWS IoT mostraram que a integração IoT-Cloud saiu de experimento de laboratório para infraestrutura de missão crítica em escala global. A arquitetura de três camadas se provou padrão a ser seguido. Os problemas abertos se tornaram mais específicos e mais difíceis, o que é sinal de maturidade, não de fracasso do mercado.

    Para quem projeta sistemas de IoT, a boa notícia é que as peças fundamentais estão disponíveis e bem documentadas hoje com AWS IoT, Azure Hub, ou on-prem tecnologias como EMQX, Kafka e afins.

    A má notícia é que o diabo continua morando nos detalhes, segurança, interoperabilidade e gerenciamento de escala ainda exigem decisões de engenharia que nenhuma plataforma toma por você ou te entrega pronto hoje em dia.

  • Stack IoT MQTT mTLS → Kafka → ksqlDB

    Stack IoT MQTT mTLS → Kafka → ksqlDB

    Por que on Premisse?

    A maioria dos tutoriais de IoT começa com AWS IoT Core, Azure IoT Hub ou algum broker SaaS. São boas opções, hoje vou mostrar um arquitetura para empresas, como Operadoras, ou Enterprises que preferem rodar suas próprias cloud privadas. Ou em qualquer cloud em containers.

    Esta stack foi construída com as seguintes premissas:

    • Controle total sobre os dados
    • mTLS obrigatório, nenhum dispositivo sem certificado válido consegue conectar
    • Pipeline de streaming
    • Custo operacional próximo de zero.
      • Para ser justo neste item, O custo aqui são seus próprios servidores e seus analistas de TI. Analistas estes  que precisaria ter do mesmo jeito para operar soluções em nuvens.

    Uma arquitetura que faria sentido numa planta industrial, numa rede privativa ou isolada da internet.

    Visão geral da arquitetura

     

    Documento@3x

     

    Cada camada tem responsabilidade única. 

    • O EMQX não conhece o Kafka diretamente, ele autentica e controla as chaves mTLS dos dispositivos, valida quem pode postar em qual tópico e dispara uma rule de transporte de dados. Aqui ele faz o papel de Midleware/segunda camada citado neste post aqui
    • O Kafka não conhece o dispositivo, ele só recebe mensagens com um tópico e um payload.

    Camada 1: O dispositivo – M5StickC Plus2 com MicroPython

    O cliente é um M5StickC Plus2 rodando MicroPython. Ele coleta dados de sensores e publica no tópico sensors/device-001 a cada intervalo configurável.

    A conexão usa mTLS: o dispositivo carrega três arquivos no filesystem interno, ca.pem, cert.pem e key.pem. Sem esses certificados, a conexão é recusada na camada TLS antes mesmo de chegar ao MQTT.

    MQTT_HOST  = "mqtt.mfs.eng.br"
    MQTT_PORT  = 8883
    MQTT_TOPIC = "sensors/device-001"
    
    DIR_CERTS = "certificate"
    FILE_CA   = "ca.pem"
    FILE_CERT = "cert.pem"
    FILE_KEY  = "key.pem"

    O payload publicado pelo dispositivo é um JSON com campos de sistema, rede, ambiente e identidade do hardware:

    {
      "clientid": "device-001",
      "host": "189.37.74.112:18983",
      "mqtt_topic": "sensors/device-001",
      "ts": 1774877350907,
      "pub_rec_date": 1774877350907,
      "payload": {
        "timestamp": "2026-03-30T13:29:10",
        "system": {
          "send_interval_seconds": 60,
          "send_count": 647,
          "is_charging": true,
          "client_id": "device-001",
          "battery_v": 4.246,
          "battery_pct": 100
        },
        "network": {
          "ssid": "MinhaRede",
          "rssi": -39,
          "mac": "00:4B:12:C4:B8:0C",
          "ip": "192.168.68.107"
        },
        "env": {
          "temperature": 26.2,
          "pressure": 911.22,
          "humidity": 58.18,
          "altitude_est": 886.4
        },
        "device": {
          "unique_id": "004B12C4B80C",
          "micropython_version": "1.25.0",
          "machine": "M5STACK StickC PLUS2 with ESP32(SPIRAM)",
          "cpu_freq_mhz": 240,
          "chip": "esp32"
        }
      }
    }

    Depois eu faço um novo post explicando sobre certificados, mTLS e como gerar eles do lado do dispositivo e do servidor.

    Camada 2: EMQX 5.0

    Por que EMQX?

    O EMQX 5 tem uma feature nativa que eu gosto muito: autenticação isolada por listener. Isso significa que você pode ter na mesma instância:

    • Porta 8883: aceita apenas certificado de cliente (mTLS), sem usuário/senha
    • Porta 1883: aceita apenas usuário/senha, sem TLS
    • Porta 8083: WebSocket com usuário/senha

    Cada listener tem sua própria cadeia de autenticação. Um dispositivo sem certificado não consegue conectar na 8883, mesmo que apresente credenciais válidas.

    Configuração dos listener mTLS

    # mTLS — dispositivos IoT
    listeners.ssl.default {
      bind = "0.0.0.0:8883"
      enable_authn = false
    
      ssl_options {
        cacertfile           = "etc/certs/rootCA.crt"
        certfile             = "etc/certs/server.crt"
        keyfile              = "etc/certs/server.key"
        verify               = verify_peer
        fail_if_no_peer_cert = true
      }
    }
    
    mqtt.peer_cert_as_clientid = cn
    mqtt.peer_cert_as_username = cn

    Usando o CN do certificado como identidade

    Uma decisão de design importante: o EMQX pode extrair o Common Name do certificado de cliente e usá-lo como clientid e username automaticamente.

    mqtt.peer_cert_as_clientid = cn
    mqtt.peer_cert_as_username = cn

    Isso significa que a identidade do dispositivo é o próprio certificado, não uma senha que pode vazar. O CN device-001 vira automaticamente o clientid na sessão MQTT.

    Camada 3: Kafka + ksqlDB

    Kafka em KRaft

    O Kafka roda sem Zookeeper usando KRaft, o modo nativo de consenso do Kafka desde a versão 3. Menos um processo para gerenciar, menos uma fonte de falha.

    A rule SQL no EMQX seleciona os campos do payload e encaminha para a action Kafka:

    SELECT
      timestamp as ts,
      clientid,
      peername as host,
      topic as mqtt_topic,
      publish_received_at as pub_rec_date,
      json_decode(payload) as p
    FROM
      "sensors/+"

    ksqlDB — Stream de entrada (TELEMETRY_RAW)

    O stream de entrada mapeia o tópico iot.telemetry.raw com o schema completo do payload. Um aprendizado importante na prática: dentro de STRUCT<> o ksqlDB não aceita backticks nem INTEGER, use INT. A palavra timestamp funciona normalmente dentro do STRUCT, ao contrário do que se esperaria.

    CREATE STREAM TELEMETRY_RAW (
        clientid     VARCHAR,
        host         VARCHAR,
        mqtt_topic   VARCHAR,
        ts           BIGINT,
        pub_rec_date BIGINT,
        payload      STRUCT<
            timestamp VARCHAR,
            system STRUCT<
                send_interval_seconds INT,
                send_count            INT,
                is_charging           BOOLEAN,
                client_id             VARCHAR,
                battery_v             DOUBLE,
                battery_pct           INT
            >,
            network STRUCT<
                ssid VARCHAR,
                rssi INT,
                mac  VARCHAR,
                ip   VARCHAR
            >,
            env STRUCT<
                temperature  DOUBLE,
                pressure     DOUBLE,
                humidity     DOUBLE,
                altitude_est DOUBLE
            >,
            device STRUCT<
                unique_id           VARCHAR,
                ram_free_b          BIGINT,
                ram_alloc_b         BIGINT,
                micropython_version VARCHAR,
                machine             VARCHAR,
                flash_size_kb       INT,
                firmware            VARCHAR,
                cpu_freq_mhz        INT,
                chip                VARCHAR
            >
        >
    ) WITH (
        KAFKA_TOPIC  = 'iot.telemetry.raw',
        VALUE_FORMAT = 'JSON',
        TIMESTAMP    = 'ts'
    );

    ksqlDB — Stream derivado flat (SENSOR_RAW)

    A partir do stream raw, criamos um stream flat com apenas as variáveis de ambiente, RSSI e identidade do dispositivo. O clientid vira device e tudo fica no mesmo nível, sem aninhamento.

    CREATE STREAM SENSOR_RAW
    WITH (
        KAFKA_TOPIC  = 'iot.telemetry.sensor',
        VALUE_FORMAT = 'JSON',
        PARTITIONS   = 6
    ) AS
    SELECT
        clientid                     AS device,
        ts,
        payload->network->rssi       AS rssi,
        payload->env->temperature    AS temperature,
        payload->env->pressure       AS pressure,
        payload->env->humidity       AS humidity,
        payload->env->altitude_est   AS altitude_est
    FROM TELEMETRY_RAW
    EMIT CHANGES;

    O resultado no tópico iot.telemetry.sensor é um JSON plano, fácil de consumir por qualquer sink:

    {
      "DEVICE":       "device-001",
      "TS":           1774877350907,
      "RSSI":         -39,
      "TEMPERATURE":  26.2,
      "PRESSURE":     911.22,
      "HUMIDITY":     58.18,
      "ALTITUDE_EST": 886.4
    }

    ksqlDB — Tabela de estatísticas diárias (SENSOR_DAILY_STATS)

    Uma tabela com janela tumbling de 1 dia agrega mínimo, máximo e média de todas as variáveis por dispositivo. A chave da tabela é composta por device + janela, então cada dispositivo tem exatamente um registro por dia.

    CREATE TABLE SENSOR_DAILY_STATS
    WITH (
        KAFKA_TOPIC  = 'iot.telemetry.sensor.daily',
        VALUE_FORMAT = 'JSON',
        PARTITIONS   = 6
    ) AS
    SELECT
        device,
        WINDOWSTART                AS window_start,
        WINDOWEND                  AS window_end,
        COUNT(*)                   AS total_records,
    
        MIN(temperature)           AS temperature_min,
        MAX(temperature)           AS temperature_max,
        ROUND(AVG(temperature), 2) AS temperature_avg,
    
        MIN(pressure)              AS pressure_min,
        MAX(pressure)              AS pressure_max,
        ROUND(AVG(pressure), 2)    AS pressure_avg,
    
        MIN(humidity)              AS humidity_min,
        MAX(humidity)              AS humidity_max,
        ROUND(AVG(humidity), 2)    AS humidity_avg,
    
        MIN(altitude_est)          AS altitude_min,
        MAX(altitude_est)          AS altitude_max,
        ROUND(AVG(altitude_est), 2) AS altitude_avg,
    
        MIN(rssi)                  AS rssi_min,
        MAX(rssi)                  AS rssi_max,
        ROUND(AVG(rssi), 2)        AS rssi_avg
    
    FROM SENSOR_RAW
    WINDOW TUMBLING (SIZE 1 DAY)
    GROUP BY device
    EMIT CHANGES;

    Para consultar as estatísticas do dia de um dispositivo específico:

    SELECT
        device,
        TIMESTAMPTOSTRING(window_start, 'yyyy-MM-dd') AS dia,
        temperature_min, temperature_max, temperature_avg,
        humidity_min, humidity_max, humidity_avg,
        pressure_min, pressure_max, pressure_avg,
        rssi_min, rssi_max, rssi_avg
    FROM SENSOR_DAILY_STATS
    WHERE device = 'device-001'
    EMIT CHANGES;

    O resultado é um pipeline reativo: quando o M5Stick publica, o dado atravessa EMQX → Kafka → ksqlDB em menos de um segundo, e as estatísticas diárias são atualizadas em tempo real.

    Decisões de design e trade-offs

    mTLS em vez de usuário/senha nos dispositivos: Certificados não podem ser adivinhados por força bruta. Um dispositivo comprometido pode ter seu certificado revogado na CA sem afetar os demais.

    Kafka em vez de gravar direto no banco: O banco é um sink, não a fonte da verdade. Com Kafka no meio, é possível adicionar novos consumidores sem alterar o produtor.

    ksqlDB em vez de consumidores customizados: Para agregações e transformações simples, SQL é suficiente e mais legível do que código Python ou Java.

    Streams em camadas: O TELEMETRY_RAW preserva o schema completo do dispositivo. O SENSOR_RAW é o stream de trabalho, flat e leve. A tabela SENSOR_DAILY_STATS entrega agregações prontas sem nenhuma consulta adicional.

    Conclusão

    Esta stack não é a mais simples de configurar. Mas é a mais honesta para se rodar on Premisse.

    O M5Stick poderia ser qualquer dispositivo IoT com suporte a mTLS.

    O EMQX escala para milhões de conexões simultâneas mantendo a mesma configuração.

    O Kafka absorve picos sem perda, desacopla a arquitetura entre escrita e leitura.

    O ksqlDB processa em tempo real sem infraestrutura adicional.

  • Visualização de Mapas do IBGE com Python e Jupyter

    Para aqueles interessados em visualizar mapas geográficos utilizando Python e manipular dados territoriais do Brasil, este tutorial oferece uma abordagem passo a passo. O objetivo é extrair e visualizar os dados geográficos disponibilizados pelo IBGE.

    Entendendo os pacotes utilizados

    
    from datetime import datetime
    import requests
    import pandas as pd
    import geopandas as gpd
    import matplotlib.pyplot as plt
    
    • requests: faz requisições web.
    • pandas: manipulação de dados.
    • geopandas: extensão do pandas para dados geográficos.
    • matplotlib: visualização gráfica.

    Extração dos dados

    Os arquivos geográficos do IBGE são baixados diretamente via HTTP:

    
    arquivos = {
        'brasil': 'https://geoftp.ibge.gov.br/.../BR_Pais_2021.zip',
        'rga':'https://geoftp.ibge.gov.br/.../BR_RG_Intermediarias_2021.zip',
        'rgi':'https://geoftp.ibge.gov.br/.../BR_RG_Imediatas_2021.zip',
        'rgme':'https://geoftp.ibge.gov.br/.../BR_Mesorregioes_2021.zip',
        'rgmi':'https://geoftp.ibge.gov.br/.../BR_Microrregioes_2021.zip',
        'uf': 'https://geoftp.ibge.gov.br/.../BR_UF_2021.zip',
        'mun': 'https://geoftp.ibge.gov.br/.../BR_Municipios_2021.zip'
    }
    

    Cada arquivo representa um nível distinto de divisão territorial:

    • brasil: contorno do país.
    • rga: Regiões Geográficas Intermediárias.
    • rgi: Regiões Geográficas Imediatas.
    • rgme: Mesorregiões.
    • rgmi: Microrregiões.
    • uf: Unidades Federativas (estados).
    • mun: Municípios (nível mais detalhado).
    
    for i in arquivos:
        arquivo = i + ".zip"
        print("Coletando:", arquivos[i])
        data = requests.get(arquivos[i])
        with open("./input/"+arquivo, "wb") as file:
            file.write(data.content)
    

    Visualização dos Estados Brasileiros

    
    df = gpd.read_file('zip://input/uf.zip')
    df.head()
    

    Filtrando somente a região Nordeste:

    
    df[df['NM_REGIAO']=='Nordeste'].plot()
    

    Visualização dos Municípios

    
    df = gpd.read_file('zip://input/mun.zip')
    mg = df[df['SIGLA'] == 'MG']
    udi = df[df['NM_MUN'] == 'Uberlândia']
    
    
    fig, (ax1, ax2) = plt.subplots(1,2, figsize=(15,10))
    mg.plot(ax=ax1, column="NM_MUN", cmap="YlGnBu")
    udi.plot(ax=ax2, edgecolor="k")
    ax1.set_title('Minas Gerais')
    ax2.set_title('Uberlândia')
    ax1.set_axis_off()
    ax2.set_axis_off()
    plt.tight_layout()
    plt.show()
    

    Exemplo mais complexo

    Obtendo dados populacionais via API do IBGE e mesclando com os dados geográficos:

    
    url = "http://servicodados.ibge.gov.br/api/v3/agregados/6579/periodos/2021/variaveis/9324?localidades=N6[N3[31]]"
    response = requests.get(url, verify=False)
    data = response.json()
    
    municipios_info = data[0]['resultados'][0]['series']
    municipios_list = []
    
    for info in municipios_info:
        id = info['localidade']['id']
        nome = info['localidade']['nome']
        municipio, estado = nome.split(" - ")
        populacao = int(info['serie']['2021'])
        municipios_list.append({
            'id': id,
            'municipio': municipio,
            'estado': estado,
            'população': populacao
        })
    
    df_pop = pd.DataFrame(municipios_list)
    merged = mg.set_index('CD_MUN').join(df_pop.set_index('id'))
    

    Plotando mapa temático:

    
    vmin, vmax = 0, 500000
    fig, ax = plt.subplots(figsize=(10,6))
    merged.plot(column='população', cmap='YlGnBu', linewidth=0.8,
                ax=ax, edgecolor='0.8', vmin=vmin, vmax=vmax)
    ax.set_title('População por município em Minas Gerais')
    plt.show()
    

    Conclusão

    Com Python, GeoPandas e dados públicos do IBGE, é possível construir visualizações geográficas poderosas, indo desde análises estaduais até mapas temáticos complexos.

  • Traefik: Gerenciando Aplicações Self-Hosted

    Hospedar suas próprias aplicações web é uma tarefa desafiadora. Este post explora como o Traefik pode simplificar esse processo, funcionando como um gateway eficiente e um balanceador de carga moderno.

    Configurando o DNS: O Primeiro Passo

    Antes de implementar o Traefik, é crucial ter uma configuração de DNS sólida. Aqui está um exemplo real usando mfs.eng.br. Essa configuração garante que as solicitações sejam corretamente encaminhadas para o servidor:

    Resource TTL Type Priority Data
    *.mfs.eng.br 86400 A 0 <IP_DO_SERVIDOR>
    mfs.eng.br 86400 A 0 <IP_DO_SERVIDOR>

    Por que o Traefik Importa?

    O Traefik atua como um gateway eficiente para suas aplicações. Ele gerencia o tráfego, distribui requisições e detecta automaticamente novos containers Docker, criando rotas para eles sem necessidade de configuração manual.

    SSL/TLS Automático: A integração com o Let’s Encrypt permite gerar e renovar certificados automaticamente, trazendo segurança sem fricção.

    Hospedando Aplicações Self-Hosted

    Se você hospeda suas próprias aplicações, o Traefik é seu melhor aliado. Ele automatiza tarefas críticas e simplifica a exposição das aplicações para a internet.

    Abaixo está a configuração de um servidor Traefik completamente funcional com SSL automático:

    
    version: "3.3"
    
    services:
      traefik:
        image: "traefik:v2.10"
        container_name: "traefik"
        command:
          - "--api.insecure=true"
          - "--log.level=DEBUG"
          - "--log.filePath=/logs/traefik.log"
          - "--accessLog.filePath=/logs/access.log"
          - "--accessLog.bufferingSize=100"
          - "--providers.docker=true"
          - "--providers.docker.exposedbydefault=true"
          - "--providers.docker.endpoint=unix:///var/run/docker.sock"
          - "--entrypoints.web.address=:80"
          - "--entrypoints.websecure.address=:443"
          - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
          - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
          - "--certificatesresolvers.myresolver.acme.email=<SEU_EMAIL>"
          - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
        ports:
          - "80:80"
          - "443:443"
          - "127.0.0.1:8080:8080"
        extra_hosts:
          - "host.docker.internal:host-gateway"
        volumes:
          - "./letsencrypt:/letsencrypt"
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./logs:/logs"
    
    networks:
      default:
        external:
          name: proxy_network
      

    E aqui está um exemplo de aplicação configurada para ser exposta com domínio próprio via Traefik:

    
    version: '3.8'
    
    services:
      web:
        image: nginx:alpine
        volumes:
          - ./html:/usr/share/nginx/html
        labels:
          - "traefik.enable=true"
          - "traefik.http.services.cltpj.loadbalancer.server.port=80"
          - "traefik.http.routers.cltpj.rule=Host(`clt-pj.mfs.eng.br`)"
          - "traefik.http.routers.cltpj.entrypoints=websecure"
          - "traefik.http.routers.cltpj.tls.certresolver=myresolver"
    
          - "traefik.http.routers.cltpj-http.rule=Host(`clt-pj.mfs.eng.br`)"
          - "traefik.http.routers.cltpj-http.entrypoints=web"
          - "traefik.http.routers.cltpj-http.middlewares=redirect-to-https@docker"
    
          - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
    
    networks:
      default:
        external:
          name: proxy_network
      

    Conclusão

    Adotar o Traefik para suas aplicações self-hosted significa economizar tempo, esforço e garantir segurança. É uma escolha inteligente para quem busca simplificar a gestão de aplicações web.

    Espero que este guia tenha ajudado!

  • Explorando a placa Nucleo L476RG com microcontrolador STM32

    A placa STM32 Nucleo L476RG oferece uma plataforma sólida para testar novas ideias e construir protótipos com microcontroladores STM32. Compatível com Arduino™ e com cabeçalhos ST Morpho, ela permite expansão fácil com uma variedade de shields.

    Características do Microcontrolador

    No centro da placa está o STM32L476RGT6 (LQFP64), equipado com:

    • CPU ARM® Cortex®-M4 32-bit operando a até 80 MHz
    • 1 MB de memória Flash
    • 128 KB de SRAM
    • ART Accelerator™ — execução direta da Flash sem atraso
    • Interfaces de conectividade: SPI, I2C, USART, USB OTG
    • Periféricos: timers, watchdogs, TRNG, interrupções externas via GPIO

    Recursos da Nucleo

    • Compatibilidade Arduino Uno R3 — suporte a shields padrão
    • Headers ST Morpho — acesso total a todos os pinos do STM32
    • ST-LINK/V2-1 integrado — não exige programador externo

    Pinagem e Expansão

    A pinagem detalhada da placa pode ser consultada em documentos do ARMmbed.

    • Headers compatíveis com Arduino
    • Headers ST Morpho para acesso completo aos I/Os

    Shields Suportados e Compatibilidade

    A placa suporta shields da ST e de terceiros, permitindo adicionar conectividade sem fio, sensores extras e diversas interfaces.

    Glossário de Termos Técnicos

    • Microcontrolador: computador compacto dedicado a tarefas específicas.
    • GPIO: pinos configuráveis para entrada/saída digital.
    • SPI: protocolo de comunicação rápida com periféricos.
    • I2C: protocolo para comunicação com sensores de baixa velocidade.
  • Monitoramento Ambiental com STM32 Nucleo L476RG e DHT22: Um Guia Prático

    A construção de projetos de monitoramento ambiental é fundamental para diversas aplicações, desde a agricultura até residências inteligentes. Neste artigo, exploramos a construção de um protótipo de sistema de visualização de temperatura e umidade usando o microcontrolador STM32 Nucleo L476RG, um sensor DHT22 e um display TFT MCUFRIEND_kbv, programados em C++ com o framework Arduino.

    Objetivo do Projeto

    O código proposto permite monitorar continuamente a temperatura e a umidade do ambiente, exibindo os resultados em tempo real em um display gráfico. Além disso, o sistema registra os valores máximos e mínimos das últimas 24h e apresenta um gráfico simples que ajuda a visualizar as tendências ao longo do tempo.

    Hardware Necessário

    • STM32 Nucleo L476RG – plataforma de prototipagem com bom equilíbrio entre desempenho e custo.
    • DHT22 – sensor de temperatura e umidade, conhecido por precisão e durabilidade.
    • Display TFT MCUFRIEND_kbv – compatível com diversos controladores gráficos, usado para visualização.

    Pinagem

    20240428 204647 1024x551.jpg

    Sensor DHT22

    • VCC: conectado ao pino 3.3V da placa STM32 Nucleo L476RG.
    • GND: conectado ao pino GND da placa STM32.
    • DHTPIN (PB7): pino de dados entre o DHT22 e a STM32.

    Conexão do Display MCUFRIEND_kbv

    • LCD_CS (A3): Chip Select, ativa/desativa o display no barramento.
    • LCD_CD (A2): Command/Data.
    • LCD_WR (A1): pino de escrita.
    • LCD_RD (A0): pino de leitura.
    • LCD_RESET (A4): pino de reset do display.

    Configuração do Projeto no VSCode com PlatformIO

    O arquivo platformio.ini define o ambiente e as bibliotecas necessárias:

    [env:nucleo_l476rg]
    platform = ststm32
    board = nucleo_l476rg
    framework = arduino
    lib_deps = 
        adafruit/DHT sensor library@^1.4.6
        adafruit/Adafruit GFX Library@^1.11.9
        prenticedavid/MCUFRIEND_kbv@^3.1.0-Beta
        adafruit/Adafruit ST7735 and ST7789 Library@^1.10.3
    

    Análise do Código-Fonte

    Armazenamento de Dados das Últimas 24 Horas

    Para monitorar as condições ambientais ao longo de um dia, o código utiliza arrays para armazenar as leituras de temperatura e umidade:

    const int NUM_READINGS = 1440; // Total de leituras para 24 horas com intervalos de um minuto
    float tempReadings[NUM_READINGS];      // Leituras de temperatura
    float humidityReadings[NUM_READINGS];  // Leituras de umidade
    int readingIndex = 0;                  // Índice atual para nova leitura
    int readingsCount = 0;                 // Qtde efetiva de leituras registradas
    

    Cada minuto uma nova leitura é inserida, e o índice é atualizado em esquema circular, mantendo sempre as últimas 24h de dados.

    Visualização de Dados com Barras de Progresso

    Para facilitar a visualização das leituras atuais em relação aos valores mínimos e máximos, o código utiliza barras de progresso dinâmicas:

    void drawProgressBar(int x, int y, int width, int height,
                         int value, int max, uint16_t barColor) {
      int filledWidth = (int)((width * value) / max);
      tft.fillRect(x, y, filledWidth, height, barColor);         // Área preenchida
      tft.fillRect(x + filledWidth, y, width - filledWidth, height, BLACK); // Área vazia
    }
    

    Mapeamento de Cores para Temperatura e Umidade

    As cores das barras mudam de acordo com os valores lidos, permitindo uma leitura visual rápida:

    uint16_t getTemperatureColor(int temp) {
      if (temp >= 30) return RED;
      if (temp < 10) return BLUE;
      int red = map(temp, 10, 30, 0, 255);
      int blue = map(temp, 10, 30, 255, 0);
      return tft.color565(red, 0, blue);
    }
    
    uint16_t getHumidityColor(int humidity) {
      if (humidity <= 30) return RED;
      if (humidity > 60) return BLUE;
      int blue = map(humidity, 20, 90, 0, 255);
      int red = map(humidity, 20, 90, 255, 0);
      return tft.color565(red, 0, blue);
    }
    

    Uso de um Indicador Composto

    A função drawIndicador agrupa elementos comuns para exibir o valor, mínimo, máximo e barra correspondente de forma compacta:

    void drawIndicador(int x, int y,
                       float value, float min, float max,
                       String indicador, String unidade,
                       uint16_t cor) {
      tft.fillRect(x, y, 320, 70, BLACK);
      tft.setTextSize(2);
      tft.setCursor(x, y + 5);
      tft.setTextColor(cor);
      tft.print(indicador + ": " + String(value) + " " + unidade);
    
      tft.setTextColor(WHITE);
      tft.setCursor(x, y + 30);
      tft.setTextSize(1);
      tft.print("Min: " + String(min) + " | Max: " + String(max));
    
      if (indicador == "Temperatura") {
        drawProgressBar(x, y + 45, 300, 20, value, 40, getTemperatureColor(value));
      } else if (indicador == "Umidade") {
        drawProgressBar(x, y + 45, 300, 20, value, 100, getHumidityColor(value));
      }
    }
    

    Visualização Gráfica das Últimas 24h

    A visualização gráfica é fundamental para entender tendências de temperatura e umidade nas últimas 24 horas:

    void drawTemperatureChart(int x, int y, int width, int height,
                              float vector[], int tamanho, int iterator,
                              uint16_t cor) {
      int y_ponto, x_ponto = 0;
      for (int i = 0; i < iterator; i++) {
        y_ponto = static_cast<int>(round((static_cast<float>(vector[i] - 15)
                  / (40 - 15)) * height));
        x_ponto = static_cast<int>(round((static_cast<float>(i)
                  / static_cast<float>(tamanho)) * static_cast<float>(width)));
        tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
      }
    }
    
    void drawHumidityChart(int x, int y, int width, int height,
                           float vector[], int tamanho, int iterator,
                           uint16_t cor) {
      int y_ponto, x_ponto = 0;
      for (int i = 0; i < iterator; i++) {
        y_ponto = static_cast<int>(round((static_cast<float>(vector[i])
                  / 100.0) * height));
        x_ponto = static_cast<int>(round((static_cast<float>(i)
                  / static_cast<float>(tamanho)) * static_cast<float>(width)));
        tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
      }
    }
    

    Essas funções mapeiam os vetores de leituras para coordenadas de pixels, permitindo um gráfico compacto dentro dos limites físicos do display.

    Código Completo

    Abaixo está o código completo de exemplo usado no projeto. Ele integra leitura do DHT22, renderização no display TFT e armazenamento de um histórico de 24 horas:

    #include <Arduino.h>
    #include <Adafruit_Sensor.h>
    #include <DHT.h>
    #include <DHT_U.h>
    #include <Adafruit_GFX.h>    // Biblioteca gráfica base
    #include <MCUFRIEND_kbv.h>
    
    #define DHTPIN PB7
    #define DHTTYPE DHT22
    
    // Pinos do display
    #define LCD_CS A3
    #define LCD_CD A2
    #define LCD_WR A1
    #define LCD_RD A0
    #define LCD_RESET A4
    
    // Definição de cores
    #define BLACK   0x0000
    #define BLUE    0x001F
    #define RED     0xF800
    #define GREEN   0x07E0
    #define CYAN    0x07FF
    #define MAGENTA 0xF81F
    #define YELLOW  0xFFE0
    #define WHITE   0xFFFF
    
    #define RGB(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))
    
    #define GREY      RGB(127, 127, 127)
    #define DARKGREY  RGB(64, 64, 64)
    #define TURQUOISE RGB(0, 128, 128)
    #define PINK      RGB(255, 128, 192)
    #define OLIVE     RGB(128, 128, 0)
    #define PURPLE    RGB(128, 0, 128)
    #define AZURE     RGB(0, 128, 255)
    #define ORANGE    RGB(255, 128, 64)
    
    // DHT_Unified para obter metadados do sensor
    DHT_Unified dht(DHTPIN, DHTTYPE);
    MCUFRIEND_kbv tft;
    
    uint32_t delayMS;
    
    const int NUM_READINGS = 1440;
    float tempReadings[NUM_READINGS];
    float humidityReadings[NUM_READINGS];
    int readingIndex = 0;
    int readingsCount = 0;
    
    float tempMin = 500;
    float tempMax = -100;
    float humidityMin = 100;
    float humidityMax = 0;
    
    // Função para obter a cor baseada na temperatura
    uint16_t getTemperatureColor(int temp) {
      if (temp >= 30) return RED;
      if (temp < 10) return BLUE;
      int red = map(temp, 10, 30, 0, 255);
      int blue = map(temp, 10, 30, 255, 0);
      return tft.color565(red, 0, blue);
    }
    
    // Função para obter a cor baseada na umidade
    uint16_t getHumidityColor(int humidity) {
      if (humidity <= 30) return RED;
      if (humidity > 60) return BLUE;
      int blue = map(humidity, 20, 90, 0, 255);
      int red = map(humidity, 20, 90, 255, 0);
      return tft.color565(red, 0, blue);
    }
    
    // Barra de progresso
    void drawProgressBar(int x, int y, int width, int height,
                         int value, int max, uint16_t barColor) {
      int filledWidth = (int)((width * value) / max);
      tft.fillRect(x, y, filledWidth, height, barColor);
      tft.fillRect(x + filledWidth, y, width - filledWidth, height, BLACK);
    }
    
    // Indicador completo (valor + min/max + barra)
    void drawIndicador(int x, int y, float value, float min, float max,
                       String indicador, String unidade, uint16_t cor) {
      tft.fillRect(x, y, 320, 70, BLACK);
      tft.setTextSize(2);
      tft.setCursor(x, y + 5);
      tft.setTextColor(cor);
      tft.print(indicador + ": " + String(value) + " " + unidade);
    
      tft.setTextColor(WHITE);
      tft.setCursor(x, y + 30);
      tft.setTextSize(1);
      tft.print("Min: " + String(min) + " | Max: " + String(max));
    
      if (indicador == "Temperatura") {
        drawProgressBar(x, y + 45, 300, 20, value, 40, getTemperatureColor(value));
      } else if (indicador == "Umidade") {
        drawProgressBar(x, y + 45, 300, 20, value, 100, getHumidityColor(value));
      }
    }
    
    // Gráfico de temperatura
    void drawTemperatureChart(int x, int y, int width, int height,
                              float vector[], int tamanho, int iterator,
                              uint16_t cor) {
      int y_ponto, x_ponto = 0;
      for (int i = 0; i < iterator; i++) {
        y_ponto = static_cast<int>(round((static_cast<float>(vector[i] - 15)
                  / (40 - 15)) * height));
        x_ponto = static_cast<int>(round((static_cast<float>(i)
                  / static_cast<float>(tamanho)) * static_cast<float>(width)));
        tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
      }
    }
    
    // Gráfico de umidade
    void drawHumidityChart(int x, int y, int width, int height,
                           float vector[], int tamanho, int iterator,
                           uint16_t cor) {
      int y_ponto, x_ponto = 0;
      for (int i = 0; i < iterator; i++) {
        y_ponto = static_cast<int>(round((static_cast<float>(vector[i])
                  / 100) * height));
        x_ponto = static_cast<int>(round((static_cast<float>(i)
                  / static_cast<float>(tamanho)) * static_cast<float>(width)));
        tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
      }
    }
    
    void setup() {
      tft.reset();
      uint16_t identifier = tft.readID();
      tft.begin(identifier);
      tft.setRotation(1);
      tft.fillScreen(BLACK);
      tft.setTextColor(WHITE);
      tft.setTextSize(2);
    
      Serial.begin(9600);
      while (!Serial);
      delay(1000);
    
      dht.begin();
      Serial.println(F("Iniciando DHT22"));
      sensor_t sensor;
    
      dht.temperature().getSensor(&sensor);
      Serial.println(F("------------------------------------"));
      Serial.println(F("Temperature Sensor"));
      Serial.print  (F("Sensor Type: ")); Serial.println(sensor.name);
      Serial.print  (F("Driver Ver:  ")); Serial.println(sensor.version);
      Serial.print  (F("Unique ID:   ")); Serial.println(sensor.sensor_id);
      Serial.print  (F("Max Value:   ")); Serial.print(sensor.max_value); Serial.println(F("°C"));
      Serial.print  (F("Min Value:   ")); Serial.print(sensor.min_value); Serial.println(F("°C"));
      Serial.print  (F("Resolution:  ")); Serial.print(sensor.resolution); Serial.println(F("°C"));
      Serial.println(F("------------------------------------"));
    
      dht.humidity().getSensor(&sensor);
      Serial.println(F("Humidity Sensor"));
      Serial.print  (F("Sensor Type: ")); Serial.println(sensor.name);
      Serial.print  (F("Driver Ver:  ")); Serial.println(sensor.version);
      Serial.print  (F("Unique ID:   ")); Serial.println(sensor.sensor_id);
      Serial.print  (F("Max Value:   ")); Serial.print(sensor.max_value); Serial.println(F("%"));
      Serial.print  (F("Min Value:   ")); Serial.print(sensor.min_value); Serial.println(F("%"));
      Serial.print  (F("Resolution:  ")); Serial.print(sensor.resolution); Serial.println(F("%"));
      Serial.println(F("------------------------------------"));
      Serial.print  (F("Min Delay:  ")); Serial.print(sensor.min_delay / 1000 / 1000); Serial.println(F("s"));
      Serial.println(F("------------------------------------"));
      Serial.print  (F("Display Height:  ")); Serial.println(tft.height());
      Serial.print  (F("Display Width:   ")); Serial.println(tft.width());
    
      delayMS = 60 * 1000;
    }
    
    void loop() {
      sensors_event_t event;
    
      // Temperatura
      dht.temperature().getEvent(&event);
      float tempValue = isnan(event.temperature) ? 0 : event.temperature;
    
      // Umidade
      dht.humidity().getEvent(&event);
      float humidityValue = isnan(event.relative_humidity) ? 0 : event.relative_humidity;
    
      // Atualiza buffers circulares
      tempReadings[readingIndex] = tempValue;
      humidityReadings[readingIndex] = humidityValue;
    
      readingIndex = (readingIndex + 1) % NUM_READINGS;
      readingsCount = min(readingsCount + 1, NUM_READINGS);
    
      // Recalcula min e max
      tempMin = tempMax = tempReadings[0];
      humidityMin = humidityMax = humidityReadings[0];
      for (int i = 0; i < readingsCount; i++) {
        tempMin = min(tempMin, tempReadings[i]);
        tempMax = max(tempMax, tempReadings[i]);
        humidityMin = min(humidityMin, humidityReadings[i]);
        humidityMax = max(humidityMax, humidityReadings[i]);
      }
    
      // Desenho no display
      if (!isnan(tempValue)) {
        drawIndicador(10, 5, tempValue, tempMin, tempMax, "Temperatura", "C", BLUE);
        drawTemperatureChart(10, 160, 300, 60, tempReadings, NUM_READINGS, readingsCount, BLUE);
      }
    
      if (!isnan(humidityValue)) {
        drawIndicador(10, 80, humidityValue, humidityMin, humidityMax, "Umidade", "%", GREEN);
        drawHumidityChart(10, 160, 300, 60, humidityReadings, NUM_READINGS, readingsCount, GREEN);
      }
    
      delay(delayMS);
    }
    

    Conclusão

    Este projeto mostra como combinar o STM32 Nucleo L476RG, o sensor DHT22 e um display TFT para criar um sistema completo de monitoramento ambiental com histórico de 24h e visualização gráfica. A mesma abordagem pode ser estendida para outros sensores e formas de comunicação, como Wi-Fi, MQTT e dashboards em nuvem.

  • Monitoramento Ambiental com DHT22, ESP32 e InfluxDB

    O objetivo aqui é construir um sistema que coleta dados de temperatura e umidade usando o sensor DHT22 e os envia para um servidor InfluxDB para armazenamento e análise, juntamente com um servidor Grafana. O ESP32 atua conectando o sensor ao Wi-Fi e gerenciando a comunicação com o InfluxDB.

    Hardware Necessário

    • ESP32 — Microcontrolador com Wi-Fi e Bluetooth integrados.
    • DHT22 — Sensor de temperatura e umidade de baixo custo.
    • Conexão Wi-Fi — Necessária para envio dos dados.
    20240505 190842 1 1024x768.jpg

    Pinagem

    Sensor DHT22:

    • VCC: 3.3V do ESP32
    • GND: GND do ESP32
    • DHTPIN: GPIO configurado no código (definido como 18)

    Configuração do Projeto no VSCode com PlatformIO

    [env:upesy_wroom]
    platform = espressif32
    board = upesy_wroom
    framework = arduino
    lib_deps = 
        adafruit/DHT sensor library@^1.4.6
        arduino-libraries/NTPClient@3.1.0
        tobiasschuerg/ESP8266 Influxdb@^3.13.1
    

    Análise do Código Fonte

    Declaração do DHT22

    #include <Adafruit_Sensor.h>
    #include <DHT.h>
    
    #define DHTPIN 18
    #define DHTTYPE DHT22
    
    DHT dht(DHTPIN, DHTTYPE);
    

    Conexão com o Wi-Fi

    #include <WiFi.h>
    #include <WiFiUdp.h>
    
    const char* ssid = "MINHA_WIFI";
    const char* password = "MINHA_SENHA";
    
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(2500);
      Serial.println("Conectando à rede WiFi...");
    }
    Serial.println("Conectado à rede WiFi");
    

    Declaração do InfluxDB e Inicialização

    #include <InfluxDbClient.h>
    
    #define INFLUXDB_URL "http://influx.mfs.eng.br:8086"
    #define INFLUXDB_DB_NAME "clima"
    #define INFLUXDB_USER "esp32"
    #define INFLUXDB_PASSWORD "esp32_password"
    
    InfluxDBClient client(INFLUXDB_URL, INFLUXDB_DB_NAME);
    Point sensor("clima");
    
    client.setConnectionParamsV1(INFLUXDB_URL, INFLUXDB_DB_NAME,
                                 INFLUXDB_USER, INFLUXDB_PASSWORD);
    

    Coletando Dados do DHT22

    float tempValue = dht.readTemperature();
    float humidityValue = dht.readHumidity();
    
    if (!isnan(tempValue) && !isnan(humidityValue)) {
      Serial.print("Temperatura: ");
      Serial.println(tempValue);
      Serial.print("Umidade: ");
      Serial.println(humidityValue);
    }
    

    Envio de Dados para o InfluxDB

    sensor.clearFields();
    sensor.addField("temperature", tempValue);
    sensor.addField("humidity", humidityValue);
    
    if (!client.writePoint(sensor)) {
      Serial.print("InfluxDB write failed: ");
      Serial.println(client.getLastErrorMessage());
    }
    

    Vantagens do InfluxDB

    • Banco de dados de séries temporais — ideal para dados contínuos.
    • Escalabilidade — lida bem com grandes volumes de dados.
    • Integração perfeita com Grafana.

    Visualização no Grafana

    Dashboard de exemplo:
    Link para o painel do Grafana

    Image 1 1024x600.png

    Código Completo

    Repositório do projeto:
    https://gitlab.com/mfs.eng.br/hardware-quirks/temperatura_esp32

    #include <Arduino.h>
    #include <Adafruit_Sensor.h>
    #include <DHT.h>
    #include <WiFi.h>
    #include <NTPClient.h>
    #include <WiFiUdp.h>
    #include <InfluxDbClient.h>
    #include "config.h"
    
    // Código completo omitido aqui — incluído no repositório
    

    Referências

    Repositório do Código

  • SBC vs. SBM: Diferenças Entre Single Board Computer e Single Board Microcontroller

    Este artigo apresenta, de forma direta e comparativa, as diferenças fundamentais entre dois tipos de dispositivos amplamente utilizados em computação embarcada: os SBCs (Single Board Computers) e os SBMs (Single Board Microcontrollers).


    SBC – Single Board Computer

    Um single-board computer (computador de placa única) é um computador completo construído em uma única placa de circuito, incluindo processador, memória, armazenamento e interfaces de entrada/saída. Diferente dos microcontroladores, os SBCs executam sistemas operacionais completos e podem funcionar como computadores de propósito geral.

    Características

    • Processador: Capaz de rodar sistemas operacionais completos.
    • Memória: RAM + armazenamento persistente (eMMC, SSD, microSD).
    • Interfaces de E/S: USB, HDMI, Ethernet, Wi-Fi, Bluetooth, GPIOs.
    • Sistema Operacional: Linux, Android ou versões leves de Windows.

    Exemplos de SBCs

    • Raspberry Pi
    • BeagleBone Black

    SBM – Single Board Microcontroller

    Um single-board microcontroller é um computador em uma única placa contendo um microcontrolador — um chip compacto que combina processador, memória e periféricos. São usados para tarefas específicas em sistemas embarcados, com foco em eficiência energética e controle determinístico.

    Características

    • Processador: Microcontrolador dedicado a tarefas específicas.
    • Memória: Flash (programas) + SRAM (dados temporários).
    • Periféricos: GPIOs, ADCs, PWMs, UART, SPI, I2C.
    • Baixo consumo: Totalmente otimizado para energia.
    • Aplicações específicas: Automação, IoT, controle e sensores.

    Exemplos de SBMs

    • Arduino
    • ESP8266 / ESP32
    • STM32 Nucleo L476RG

    Resumo Final

    SBCs funcionam como mini-computadores completos capazes de rodar sistemas operacionais. SBMs funcionam como microcontroladores especializados para controle de hardware e tarefas determinísticas. A escolha depende do tipo de projeto: “computação generalista” → SBC; “controle e IoT” → SBM.

  • Comandos Linux para gestão de Hardware

    Tentei colocar aqui os comandos que utilizo para verificar as informações de hardware nos sistemas Linux que administro. Incluí instruções sobre como obter dados de CPU, memória, discos, controladores USB, adaptadores de rede, entre outros componentes.

    lscpu

    O comando lscpu reporta informações sobre a CPU e unidades de processamento. Ele não possui opções adicionais ou funcionalidades.

    $ lscpu
    
    Architecture:           x86_64
    CPU op-mode(s):         32-bit, 64-bit
    Byte Order:             Little Endian
    CPU(s):                 4
    On-line CPU(s) list:    0-3
    Thread(s) per core:     1
    Core(s) per socket:     4
    Socket(s):              1
    NUMA node(s):           1
    Vendor ID:              GenuineIntel
    CPU family:             6
    Model:                  23
    Stepping:               10
    CPU MHz:                1998.000
    BogoMIPS:               5302.48
    Virtualization:         VT-x
    L1d cache:              32K
    L1i cache:              32K
    L2 cache:               2048K
    NUMA node0 CPU(s):      0-3
    

    lshw – Listar Hardware

    O lshw é uma ferramenta geral que reporta informações detalhadas sobre múltiplos componentes de hardware, como CPU, memória, discos, controladores USB, adaptadores de rede, etc. Ele extrai informações de diferentes arquivos no /proc.

    $ lshw -short
    H/W path           Device      Class       Description
    ===================================================
    system                          ()
    /0                              bus         DG35EC
    /0/0                            processor   Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz
    /0/0/1                          memory      2MiB L2 cache
    /0/0/3                          memory      32KiB L1 cache
    /0/2                            memory      32KiB L1 cache
    /0/4                            memory      64KiB BIOS
    /0/14                           memory      8GiB System Memory
    /0/14/0                         memory      2GiB DIMM DDR2 Synchronous 667 MHz (1.5 ns)
    /0/14/1                         memory      2GiB DIMM DDR2 Synchronous 667 MHz (1.5 ns)
    /0/14/2                         memory      2GiB DIMM DDR2 Synchronous 667 MHz (1.5 ns)
    /0/14/3                         memory      2GiB DIMM DDR2 Synchronous 667 MHz (1.5 ns)
    /0/100                          bridge      82G35 Express DRAM Controller
    /0/100/2                        display     82G35 Express Integrated Graphics Controller
    /0/100/2.1                      display     82G35 Express Integrated Graphics Controller
    /0/100/19           eth0        network     82566DC Gigabit Network Connection
    

    hwinfo – Informações de Hardware

    O hwinfo é uma ferramenta de detecção de hardware que pode relatar informações detalhadas sobre vários componentes de hardware.

    $ hwinfo --short
    cpu:
      Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz, 2000 MHz
      Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz, 2000 MHz
      Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz, 2666 MHz
      Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz, 2666 MHz
    keyboard:
      /dev/input/event2 AT Translated Set 2 keyboard
    mouse:
      /dev/input/mice Microsoft Basic Optical Mouse v2.0
    graphics card:
      Intel 965G-1 Intel 82G35 Express Integrated Graphics Controller
    sound:
      Intel 82801H (ICH8 Family) HD Audio Controller
    

    lspci – Listar PCI

    O comando lspci lista todos os barramentos PCI e detalhes sobre os dispositivos conectados a eles.

    $ lspci
    00:00.0 Host bridge: Intel Corporation 82G35 Express DRAM Controller (rev 03)
    00:02.0 VGA compatible controller: Intel Corporation 82G35 Express Integrated Graphics Controller (rev 03)
    00:02.1 Display controller: Intel Corporation 82G35 Express Integrated Graphics Controller (rev 03)
    00:19.0 Ethernet controller: Intel Corporation 82566DC Gigabit Network Connection (rev 02)
    

    lsscsi – Listar Dispositivos SCSI

    O lsscsi lista dispositivos SCSI/SATA como discos rígidos e unidades ópticas.

    $ lsscsi
    [3:0:0:0]    disk    ATA      ST3500418AS      CC38  /dev/sda
    [4:0:0:0]    cd/dvd  SONY     DVD RW DRU-190A  1.63  /dev/sr0
    

    lsusb – Listar Dispositivos USB

    O comando lsusb mostra os controladores USB e detalhes sobre os dispositivos conectados a eles. Use a opção -v (verbose) para informações detalhadas.

    $ lsusb
    Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
    Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
    Bus 005 Device 002: ID 045e:00cb Microsoft Corp. Basic Optical Mouse v2.0
    

    lsblk – Listar Dispositivos de Bloco

    lsblk lista informações sobre todos os dispositivos de bloco, como partições de discos rígidos e outros dispositivos de armazenamento.

    $ lsblk
    NAME    MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
    sda       8:0    0 465.8G  0 disk
    ├─sda1    8:1    0    70G  0 part
    ├─sda2    8:2    0     1K  0 part
    ├─sda5    8:5    0  97.7G  0 part /media/4668484A68483B47
    ├─sda6    8:6    0  97.7G  0 part /
    ├─sda7    8:7    0   1.9G  0 part [SWAP]
    └─sda8    8:8    0 198.5G  0 part /media/13f35f59-f023-4d98-b06f-9dfaebefd6c1
    sr0      11:0    1  1024M  0 rom
    

    Inxi

    O inxi é um script bash que coleta detalhes de hardware de várias fontes e comandos no sistema, gerando um relatório compreensível.

    $ inxi -Fx
    System:
      Host: fedora Kernel: 6.9.8-200.fc40.x86_64 arch: x86_64 bits: 64
        compiler: gcc v: 2.41-37.fc40
      Desktop: KDE Plasma v: 6.1.3 Distro: Fedora Linux 40 (KDE Plasma)
    Machine:
      Type: Laptop System: Dell product: Inspiron 7560 v: N/A
        serial: <superuser required>
      Mobo: Dell model: 09WC1G v: A01 serial: <superuser required> UEFI: Dell
        v: 1.15.0 date: 07/23/2021
    Battery:
      ID-1: BAT0 charge: 5.7 Wh (100.0%) condition: 5.7/42.0 Wh (13.7%)
        volts: 12.4 min: 11.4 model: SMP DELL Y3F7Y6B status: full
      Device-1: wacom_battery_0 model: Wacom Intuos S 2 charge: 100%
        status: full
    CPU:
      Info: dual core model: Intel Core i7-7500U bits: 64 type: MT MCP
        arch: Amber/Kaby Lake note: check rev: 9 cache: L1: 128 KiB L2: 512 KiB
        L3: 4 MiB
      Speed (MHz): avg: 800 high: 801 min/max: 400/3500 cores: 1: 801 2: 800
        3: 800 4: 800 bogomips: 23199
      Flags: avx avx2 ht lm nx pae sse sse2 sse3 sse4_1 sse4_2 ssse3 vmx
    Graphics:
      Device-1: Intel HD Graphics 620 vendor: Dell driver: i915 v: kernel
        arch: Gen-9.5 bus-ID: 00:02.0
      Device-2: NVIDIA GM108M [GeForce 940MX] vendor: Dell driver: nouveau
        v: kernel arch: Maxwell bus-ID: 01:00.0
      Device-3: Microdia Integrated_Webcam_HD driver: uvcvideo type: USB
        bus-ID: 1-5:4
      Display: wayland server: Xwayland v: 24.1.1 compositor: kwin_wayland
        driver: N/A resolution: 1920x1080
      API: EGL v: 1.5 drivers: iris,nouveau,swrast platforms:
        active: wayland,x11,surfaceless,device inactive: gbm
      API: OpenGL v: 4.6 compat-v: 4.3 vendor: intel mesa v: 24.1.4 glx-v: 1.4
        direct-render: yes renderer: Mesa Intel HD Graphics 620 (KBL GT2)
      API: Vulkan v: 1.3.283 drivers: N/A surfaces: xcb,xlib,wayland devices: 2
    Audio:
      Device-1: Intel Sunrise Point-LP HD Audio vendor: Dell driver: snd_hda_intel
        v: kernel bus-ID: 00:1f.3
      API: ALSA v: k6.9.8-200.fc40.x86_64 status: kernel-api
      Server-1: PipeWire v: 1.0.7 status: active
    Network:
      Device-1: Qualcomm Atheros QCA6174 802.11ac Wireless Network Adapter
        vendor: Dell driver: ath10k_pci v: kernel bus-ID: 02:00.0 temp: 36.0 C
      IF: wlp2s0 state: up mac: 12:49:3c:d5:b5:cf
      Device-2: Realtek RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet
        vendor: Dell driver: r8169 v: kernel port: d000 bus-ID: 03:00.0
      IF: enp3s0 state: down mac: 84:7b:eb:f9:87:69
    Bluetooth:
      Device-1: Qualcomm Atheros driver: btusb v: 0.8 type: USB bus-ID: 1-8:6
      Report: btmgmt ID: hci0 rfk-id: 0 state: up address: A8:6B:AD:DE:14:AA
        bt-v: 4.2 lmp-v: 8
    Drives:
      Local Storage: total: 231.03 GiB used: 27.75 GiB (12.0%)
      ID-1: /dev/sda vendor: Gigabyte model: GP-GSTFS31120GNTD size: 111.79 GiB
      ID-2: /dev/sdb vendor: LITE-ON model: L8H-128V2G-11 M.2 2280 128GB
        size: 119.24 GiB
    Partition:
      ID-1: / size: 37.25 GiB used: 27.25 GiB (73.2%) fs: btrfs dev: /dev/sda4
      ID-2: /boot size: 965.9 MiB used: 466.6 MiB (48.3%) fs: ext4
        dev: /dev/sda3
      ID-3: /boot/efi size: 598.8 MiB used: 44.9 MiB (7.5%) fs: vfat
        dev: /dev/sda2
    Swap:
      ID-1: swap-1 type: zram size: 8 GiB used: 33.2 MiB (0.4%) dev: /dev/zram0
    Sensors:
      System Temperatures: cpu: 56.0 C pch: 47.5 C mobo: 45.0 C sodimm: SODIMM C
      Fan Speeds (rpm): cpu: 2539
    Info:
      Memory: total: 16 GiB available: 15.51 GiB used: 8.06 GiB (52.0%)
      Processes: 274 Uptime: 22h 5m Init: systemd target: graphical (5)
      Packages: 15 Compilers: gcc: 14.1.1 Shell: Bash v: 5.2.26 inxi: 3.3.34
    muller@fedora:~$
    

    df – Espaço em Disco

    O comando df relata várias partições, seus pontos de montagem e o espaço usado e disponível em cada uma.

    $ df -H
    Filesystem      Size  Used  Avail Use% Mounted on
    /dev/sda6       104G   26G    73G  26% /
    none            4.1k     0   4.1k   0% /sys/fs/cgroup
    udev            4.2G   4.1k  4.2G   1% /dev
    tmpfs           837M   1.6M  835M   1% /run
    

    Pydf – df em Python

    O pydf é uma versão aprimorada do df escrita em Python, que exibe saída colorida.

    Para instalar:

    $ pip install pydf
    

    Para utilizar:

    $ pydf
    Filesystem      Size  Used  Avail Use% Mounted on
    /dev/sda6       96G   23G    68G  24.4 [#.....] /
    /dev/sda8      195G  138G    47G  70.6 [####..] /media/13f35f59-f023-4d98-b06f-9dfaebefd6c
    

    fdisk

    O fdisk é uma ferramenta para modificar partições em discos rígidos e pode ser usada para listar informações de partições.

    $ fdisk -l
    Disk /dev/sda: 500.1 GB, 500107862016 bytes
    255 heads, 63 sectors/track, 60801 cylinders, total 976773168 sectors
    Units = sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disk identifier: 0x30093008
    
       Device Boot      Start         End      Blocks   Id  System
    /dev/sda1   *          63   146801969    73400953+   7  HPFS/NTFS/exFAT
    /dev/sda2       146802031   976771071   414984520+   f  W95 Ext'd (LBA)
    /dev/sda5       146802033   351614654   102406311    7  HPFS/NTFS/exFAT
    /dev/sda6       351614718   556427339   102406311   83  Linux
    /dev/sda7       556429312   560427007     1998848   82  Linux swap / Solaris
    /dev/sda8       560429056   976771071   208171008   83  Linux
    

    mount

    O comando mount é usado para montar/desmontar e visualizar sistemas de arquivos montados.

    $ mount | column -t
    /dev/sda6 on /         type ext4     (rw,errors=remount-ro)
    proc      on /proc     type proc     (rw,noexec,nosuid,nodev)
    sysfs     on /sys      type sysfs    (rw,noexec,nosuid,nodev)
    udev      on /dev      type devtmpfs (rw,mode=0755)
    devpts    on /dev/pts  type devpts   (rw,noexec,nosuid,gid=5,mode=0620)
    tmpfs     on /run      type tmpfs    (rw,noexec,nosuid,size=10%,mode=0755)
    

    free – Verificar Memória RAM

    Verifique a quantidade de RAM usada, livre e total no sistema com o comando free.

    $ free -m
                 total       used       free     shared    buffers     cached
    Mem:          7975       5865       2110          0         24        622
    -/+ buffers/cache:       5218       2757
    Swap:         1951        921       1030
    

    dmidecode

    O comando dmidecode extrai informações de hardware lendo dados das estruturas SMBOIS (também chamadas de tabelas DMI).

    # Informações sobre o processador/CPU
    $ dmidecode -t processor
    
    # Informações sobre a memória/RAM
    $ dmidecode -t memory
    
    # Detalhes do BIOS
    $ dmidecode -t bios
    

    Arquivos /proc

    Muitos arquivos virtuais no diretório /proc contêm informações sobre hardware e configurações.

    # Informações sobre a CPU
    $ cat /proc/cpuinfo
    
    # Informações sobre a memória
    $ cat /proc/meminfo
    
    # Informações sobre o Linux/kernel
    $ cat /proc/version
    
    # Dispositivos SCSI/SATA
    $ cat /proc/scsi/scsi
    
    # Partições
    $ cat /proc/partitions
    

    hdparm

    O comando hdparm obtém informações sobre dispositivos SATA como discos rígidos.

    $ hdparm -i /dev/sda
    /dev/sda:
     Model=ST3500418AS, FwRev=CC38, SerialNo=
     Config={ HardSect NotMFM HdSw>15uSec Fixed DTR>10Mbs RotSpdTol>.5% }
     RawCHS=16383/16/63, TrkSize=0, SectSize=0, ECCbytes=4
     BuffType=unknown, BuffSize=16384kB, MaxMultSect=16, MultSect=16
     CurCHS=16383/16/63, CurSects=16514064, LBA=yes, LBAsects=976773168
     IORDY=on/off, tPIO={min:120,w/IORDY:120}, tDMA={min:120,rec:120}
     PIO modes: pio0 pio1 pio2 pio3 pio4
     DMA modes: mdma0 mdma1 mdma2
     UDMA modes: udma0 udma1 udma2 udma3 udma4 udma5 *udma6
     AdvancedPM=no WriteCache=enabled
     Drive conforms to: unknown: ATA/ATAPI-4,5,6,7
    

    Disclaimer

    A lógica adotada para este manual foi baseada nas necessidades ao longo da minha carreira para fazer troubleshooting de problemas de aplicações que culminaram em causa raiz relacionada ao hardware do sistema ou problemas que tive ao montar e desmontar notebooks e computadores.