Pedro's dev blog

Monitoramento c++

Cppp.png
Published on
/21 mins read/---

Sistema de Integração em Tempo Real para Monitoramento Industrial

industrial-monitoring-system/
├── CMakeLists.txt       # Arquivo CMake de build de nível superior
├── README.md            # Descrição do projeto e instruções
├── LICENSE              # Licença do projeto (ex: MIT, Apache 2.0)
├── .gitignore           # Arquivos e diretórios para ignorar no Git
├── src/                 # Código-fonte para o backend C++
│   ├── database/        # Código relacionado ao banco de dados
│   │   ├── DatabaseManager.h
│   │   ├── DatabaseManager.cpp
│   │   └── ... (outros arquivos de banco de dados, se necessário)
│   ├── sensors/       # Geração e manipulação de dados de sensores
│   │   ├── RealTimeDataGenerator.h
│   │   ├── RealTimeDataGenerator.cpp
│   │   └── ...
│   ├── monitoring/     # Componentes de monitoramento de console
│   │   ├── DataMonitor.h
│   │   ├── DataMonitor.cpp
│   │   └── ...
│   ├── simulation/   # Componentes de simulação de consulta
│   │   ├── QuerySimulator.h
│   │   ├── QuerySimulator.cpp
│   │   └── ...
│   ├── system/       # Componentes principais do sistema (LiveDatabase)
│   │   ├── LiveDatabase.h
│   │   ├── LiveDatabase.cpp
│   │   └── ...
│   ├── main.cpp        # Ponto de entrada principal da aplicação
│   └── CMakeLists.txt   # Arquivo CMake para o diretório 'src'
│
├── include/           # Headers públicos (se você tiver algum que queira acessível globalmente)
│   └── ...
│
├── web/               # Frontend (HTML, CSS, JavaScript)
│   ├── index.html
│   ├── style.css  (opcional, se você separar o CSS)
│   ├── script.js  (opcional, se você separar o JS)
│   └── ... (outros recursos da web: imagens, fontes, etc.)
│
├── test/                # Testes unitários (altamente recomendado!)
│   ├── CMakeLists.txt
│   ├── database_tests.cpp
│   ├── sensor_tests.cpp
│   └── ...
│
├── scripts/             # Scripts utilitários (opcional)
│   ├── build.sh        # Script de build (para conveniência)
│   ├── run.sh          # Script de execução
│   └── ...
│
├── build/              # Artefatos de build (criados pelo CMake) - *Adicione isso ao .gitignore*
│   └── ...
│
└── docs/               # Documentação do projeto (opcional, mas boa prática)
    ├── architecture.md  # Descrição detalhada da arquitetura
    └── ...

Explicação e Considerações Chave:

  • CMakeLists.txt (Nível Superior): Este é o arquivo CMake principal que orquestra todo o processo de build. Ele definirá o projeto, encontrará dependências (como SQLite3) e incluirá subdiretórios (src, test).
  • README.md: Crucial para explicar o que é seu projeto, como construí-lo, como executá-lo e quaisquer dependências.
  • LICENSE: Especifica como outros podem usar seu código. Escolha uma licença apropriada para seu projeto (MIT, Apache 2.0, GPL, etc.).
  • .gitignore: Impede que artefatos de build, arquivos temporários e arquivos específicos do editor sejam rastreados pelo Git. Crucialmente, adicione build/ ao seu .gitignore.
  • src/: É aqui que reside o código C++ principal.
    • Subdiretórios por Componente: Os diretórios database/, sensors/, monitoring/, simulation/ e system/ organizam o código logicamente por sua função. Isso torna o projeto muito mais fácil de navegar e manter. Cada diretório contém os arquivos .h (cabeçalho) e .cpp (implementação) para as classes correspondentes.
    • main.cpp: O ponto de entrada da sua aplicação.
    • CMakeLists.txt (dentro de src/): Este arquivo CMake é específico para construir o backend C++. Ele definirá o(s) executável(eis) e vinculará as bibliotecas necessárias.
  • include/: Se você tiver arquivos de cabeçalho que precisam ser acessíveis de várias partes do seu projeto (e potencialmente por projetos externos), coloque-os aqui. Neste projeto específico, pode não ser estritamente necessário, pois os cabeçalhos estão localizados com suas implementações.
  • web/: O código do frontend (HTML, CSS, JavaScript) é bem separado. Isso mantém as preocupações do backend e do frontend distintas. Você poderia até ter um processo de build separado para o frontend (por exemplo, usando um empacotador JavaScript como Webpack ou Parcel) se ele se tornar mais complexo.
  • test/: Essencial para qualquer projeto sério. Este diretório contém testes unitários. Usar um framework de teste como Google Test (gtest) ou Catch2 é altamente recomendado. O CMakeLists.txt dentro de test/ lida com a construção e execução dos testes.
  • scripts/: Um lugar para scripts úteis para automatizar tarefas comuns (construção, execução, limpeza, etc.).
  • build/: É aqui que o CMake colocará os binários compilados, arquivos de objeto e outros artefatos de build. Não adicione este diretório ao controle de versão.
  • docs/: Para documentação mais extensa, como descrições arquiteturais detalhadas, documentação de API ou guias de usuário.

Princípios Chave:

  • Separação de Responsabilidades: A estrutura de diretórios separa claramente diferentes partes da aplicação (banco de dados, sensores, monitoramento, frontend).
  • Modularidade: Cada componente é relativamente autocontido, tornando-o mais fácil de entender, modificar e testar.
  • Sistema de Build (CMake): O CMake fornece uma maneira consistente e independente de plataforma para construir o projeto.
  • Teste: O diretório test/ incentiva a escrita de testes unitários para garantir a correção do código e evitar regressões.
  • Limpeza: O arquivo .gitignore mantém o repositório limpo de arquivos desnecessários.

Como Criar Essa Estrutura (usando a linha de comando):

  1. Crie o diretório raiz:

    mkdir industrial-monitoring-system
    cd industrial-monitoring-system
  2. Crie os arquivos básicos:

    touch CMakeLists.txt README.md LICENSE .gitignore
  3. Crie os subdiretórios:

    mkdir src include web test scripts build docs
  4. Crie os arquivos dentro de src/:

    mkdir src/database src/sensors src/monitoring src/simulation src/system
    touch src/main.cpp src/CMakeLists.txt
    touch src/database/DatabaseManager.h src/database/DatabaseManager.cpp
    touch src/sensors/RealTimeDataGenerator.h src/sensors/RealTimeDataGenerator.cpp
    touch src/monitoring/DataMonitor.h src/monitoring/DataMonitor.cpp
    touch src/simulation/QuerySimulator.h src/simulation/QuerySimulator.cpp
    touch src/system/LiveDatabase.h src/system/LiveDatabase.cpp
  5. Crie os arquivos dentro de web:

    touch web/index.html web/style.css web/script.js
  6. Crie os arquivos do diretório de teste

    touch test/CMakeLists.txt test/database_tests.cpp test/sensor_tests.cpp
  7. Preencha CMakeLists.txt (Nível Superior): Comece com uma configuração básica do CMake:

    cmake_minimum_required(VERSION 3.10)  # Ou superior, se necessário
    project(IndustrialMonitoringSystem)
     
    # Encontrar SQLite3
    find_package(SQLite3 REQUIRED)
     
    # Adicionar subdiretórios
    add_subdirectory(src)
    # add_subdirectory(test)  # Descomente quando tiver testes
     
    # Você pode adicionar opções para construir o frontend aqui,
    # ou ter um processo de build separado para ele.
  8. Preencha src/CMakeLists.txt:

    add_executable(IndustrialMonitoringSystem main.cpp
        database/DatabaseManager.cpp
        sensors/RealTimeDataGenerator.cpp
        monitoring/DataMonitor.cpp
        simulation/QuerySimulator.cpp
        system/LiveDatabase.cpp
    )
     
    target_include_directories(IndustrialMonitoringSystem PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
    target_link_libraries(IndustrialMonitoringSystem SQLite::SQLite3 pthread) # Link SQLite3 and pthreads
     
    # Se você estiver usando C++17 ou posterior:
    set_property(TARGET IndustrialMonitoringSystem PROPERTY CXX_STANDARD 17)
    set_property(TARGET IndustrialMonitoringSystem PROPERTY CXX_STANDARD_REQUIRED ON)
  9. Preencha .gitignore:

    build/
    *.o
    *.exe
    *.db  # Você pode querer manter arquivos .db de exemplo, mas não os gerados
    # Adicione outros arquivos e pastas específicos do editor/IDE aqui
    
  10. Preencha test/CMakeLists.txt:

    # Encontre a biblioteca GoogleTest
    find_package(GTest REQUIRED)
 
    # Habilite os testes
    enable_testing()
 
    # Crie um executável de teste
    add_executable(DatabaseTests database_tests.cpp)
 
    # Vincule o executável de teste ao GoogleTest
    target_link_libraries(DatabaseTests GTest::GTestMain)
 
    # Adicione o teste ao conjunto de testes
    add_test(NAME DatabaseTests COMMAND DatabaseTests)
 
    # Crie um executável de teste
    add_executable(SensorTests sensor_tests.cpp)
 
    # Vincule o executável de teste ao GoogleTest
    target_link_libraries(SensorTests GTest::GTestMain)
 
    # Adicione o teste ao conjunto de testes
    add_test(NAME SensorTests COMMAND SensorTests)
 

Esta configuração detalhada fornece uma base sólida para o seu projeto. Lembre-se de instalar as bibliotecas de desenvolvimento SQLite3 (libsqlite3-dev no Debian/Ubuntu ou similar em outros sistemas). Você também precisará de um compilador C++ (GCC, Clang, MSVC) e CMake. Este é um muito bom ponto de partida para um projeto C++ de qualidade profissional.

Visão Geral

Este sistema demonstra uma solução robusta para integração de diversos subsistemas industriais em tempo real usando C++ moderno. A arquitetura implementa um sistema distribuído que coleta dados de sensores, processa-os e permite controle remoto de atuadores em uma planta industrial.

Arquitetura do Sistema

+------------------------+     +------------------------+     +-------------------+
| Sensores Distribuídos  |     | Sistema de Controle    |     | Interface HMI     |
| (Temperatura/Pressão/  +---->+ Central                +---->+ (Web/Mobile/      |
|  Vibração/Nível)       |     | (Processamento/Análise)|     |  Desktop)         |
+------------------------+     +------------------------+     +-------------------+
                                         ^                             |
                                         |                             |
                                         v                             v
+------------------------+     +------------------------+     +-------------------+
| Sistema de             |     | Banco de Dados         |     | Sistema de        |
| Alarmes e Notificações |<----+ em Tempo Real          |<----+ Geração de       |
|                        |     | (Série temporal)       |     | Relatórios        |
+------------------------+     +------------------------+     +-------------------+

Implementação do Sistema

1. Código do Sistema de Aquisição de Dados

##include <iostream>
#include <sqlite3.h>
#include <thread>
#include <mutex>
#include <chrono>
#include <random>
#include <vector>
#include <atomic>
#include <string>
#include <sstream>
#include <iomanip>
#include <ctime>
 
// Classe para gerenciar conexão com banco de dados SQLite
class DatabaseManager {
private:
    sqlite3* db;
    std::mutex dbMutex;
    std::string dbName;
 
public:
    DatabaseManager(const std::string& dbName) : dbName(dbName), db(nullptr) {}
 
    bool open() {
        std::lock_guard<std::mutex> lock(dbMutex);
        int rc = sqlite3_open(dbName.c_str(), &db);
        if (rc != SQLITE_OK) {
            std::cerr << "Erro ao abrir banco de dados: " << sqlite3_errmsg(db) << std::endl;
            return false;
        }
        return true;
    }
 
    bool executeQuery(const std::string& query) {
        std::lock_guard<std::mutex> lock(dbMutex);
        char* errMsg = nullptr;
        int rc = sqlite3_exec(db, query.c_str(), nullptr, nullptr, &errMsg);
        
        if (rc != SQLITE_OK) {
            std::cerr << "Erro SQL: " << errMsg << std::endl;
            sqlite3_free(errMsg);
            return false;
        }
        return true;
    }
 
    // Callback para processar resultados de consulta
    static int queryCallback(void* data, int argc, char** argv, char** azColName) {
        auto* results = static_cast<std::vector<std::vector<std::string>>*>(data);
        
        // Para a primeira linha, adiciona os cabeçalhos
        if (results->empty()) {
            std::vector<std::string> headers;
            for (int i = 0; i < argc; i++) {
                headers.push_back(azColName[i]);
            }
            results->push_back(headers);
        }
        
        // Adiciona os dados da linha
        std::vector<std::string> row;
        for (int i = 0; i < argc; i++) {
            row.push_back(argv[i] ? argv[i] : "NULL");
        }
        results->push_back(row);
        
        return 0;
    }
 
    std::vector<std::vector<std::string>> executeSelect(const std::string& query) {
        std::lock_guard<std::mutex> lock(dbMutex);
        std::vector<std::vector<std::string>> results;
        
        char* errMsg = nullptr;
        int rc = sqlite3_exec(
            db, 
            query.c_str(), 
            queryCallback, 
            &results, 
            &errMsg
        );
        
        if (rc != SQLITE_OK) {
            std::cerr << "Erro na consulta: " << errMsg << std::endl;
            sqlite3_free(errMsg);
        }
        
        return results;
    }
 
    void close() {
        std::lock_guard<std::mutex> lock(dbMutex);
        if (db) {
            sqlite3_close(db);
            db = nullptr;
        }
    }
 
    ~DatabaseManager() {
        close();
    }
};
 
// Classe para gerar dados em tempo real
class RealTimeDataGenerator {
private:
    DatabaseManager& dbManager;
    std::atomic<bool> running;
    std::thread generatorThread;
    std::random_device rd;
    std::mt19937 gen;
    
    // Distribuições para diferentes tipos de dados
    std::uniform_int_distribution<> sensorIdDist;
    std::normal_distribution<> temperatureDist;
    std::normal_distribution<> humidityDist;
    std::normal_distribution<> pressureDist;
    std::uniform_real_distribution<> batteryDist;
    
    std::string getTimestamp() {
        auto now = std::chrono::system_clock::now();
        auto time_t = std::chrono::system_clock::to_time_t(now);
        
        std::stringstream ss;
        ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
        return ss.str();
    }
    
    // Função que executa em uma thread separada
    void generatorFunction(int intervalMs) {
        while (running) {
            // Gerar dados aleatórios para cada sensor
            for (int i = 0; i < 5; i++) {
                int sensorId = sensorIdDist(gen);
                double temperature = temperatureDist(gen);
                double humidity = humidityDist(gen);
                double pressure = pressureDist(gen);
                double battery = batteryDist(gen);
                std::string timestamp = getTimestamp();
                
                // Inserir no banco de dados
                std::stringstream ss;
                ss << "INSERT INTO sensor_data (sensor_id, temperature, humidity, pressure, battery_level, timestamp) "
                   << "VALUES (" 
                   << sensorId << ", " 
                   << std::fixed << std::setprecision(2) << temperature << ", " 
                   << std::fixed << std::setprecision(2) << humidity << ", " 
                   << std::fixed << std::setprecision(2) << pressure << ", " 
                   << std::fixed << std::setprecision(2) << battery << ", " 
                   << "'" << timestamp << "')";
                
                dbManager.executeQuery(ss.str());
                
                // Criar um evento aleatório (10% de chance)
                if (std::uniform_real_distribution<>(0, 1)(gen) < 0.1) {
                    std::string eventType;
                    std::string eventDescription;
                    int severity;
                    
                    // Decidir o tipo de evento
                    double eventRand = std::uniform_real_distribution<>(0, 1)(gen);
                    if (eventRand < 0.4) {
                        eventType = "TEMPERATURE_ALERT";
                        eventDescription = "Temperatura fora dos limites normais";
                        severity = 2;
                    } else if (eventRand < 0.7) {
                        eventType = "BATTERY_LOW";
                        eventDescription = "Nível de bateria baixo";
                        severity = 1;
                    } else if (eventRand < 0.9) {
                        eventType = "PRESSURE_WARNING";
                        eventDescription = "Pressão anormal detectada";
                        severity = 3;
                    } else {
                        eventType = "SYSTEM_ERROR";
                        eventDescription = "Erro de comunicação com sensor";
                        severity = 4;
                    }
                    
                    std::stringstream eventSs;
                    eventSs << "INSERT INTO events (sensor_id, event_type, description, severity, timestamp) "
                            << "VALUES (" 
                            << sensorId << ", " 
                            << "'" << eventType << "', " 
                            << "'" << eventDescription << "', " 
                            << severity << ", "
                            << "'" << timestamp << "')";
                    
                    dbManager.executeQuery(eventSs.str());
                }
            }
            
            // Limpar dados antigos (manter apenas os últimos 1000 registros)
            dbManager.executeQuery("DELETE FROM sensor_data WHERE id NOT IN (SELECT id FROM sensor_data ORDER BY timestamp DESC LIMIT 1000)");
            dbManager.executeQuery("DELETE FROM events WHERE id NOT IN (SELECT id FROM events ORDER BY timestamp DESC LIMIT 500)");
            
            // Aguardar o intervalo configurado
            std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
        }
    }
 
public:
    RealTimeDataGenerator(DatabaseManager& db) : 
        dbManager(db), 
        running(false),
        gen(rd()),
        sensorIdDist(1, 10),
        temperatureDist(22.0, 5.0),       // Média 22°C, desvio padrão 5°C
        humidityDist(60.0, 15.0),         // Média 60%, desvio padrão 15%
        pressureDist(1013.0, 10.0),       // Média 1013 hPa, desvio padrão 10 hPa
        batteryDist(0.5, 1.0)             // Entre 50% e 100%
    {}
    
    bool initialize() {
        // Criar tabelas se não existirem
        if (!dbManager.executeQuery(
            "CREATE TABLE IF NOT EXISTS sensor_data ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
            "sensor_id INTEGER NOT NULL,"
            "temperature REAL,"
            "humidity REAL,"
            "pressure REAL,"
            "battery_level REAL,"
            "timestamp TEXT NOT NULL"
            ")"
        )) {
            return false;
        }
        
        if (!dbManager.executeQuery(
            "CREATE TABLE IF NOT EXISTS events ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
            "sensor_id INTEGER NOT NULL,"
            "event_type TEXT NOT NULL,"
            "description TEXT,"
            "severity INTEGER,"
            "timestamp TEXT NOT NULL"
            ")"
        )) {
            return false;
        }
        
        // Criar índices para melhorar performance
        dbManager.executeQuery("CREATE INDEX IF NOT EXISTS idx_sensor_data_timestamp ON sensor_data(timestamp)");
        dbManager.executeQuery("CREATE INDEX IF NOT EXISTS idx_sensor_data_sensor_id ON sensor_data(sensor_id)");
        dbManager.executeQuery("CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp)");
        dbManager.executeQuery("CREATE INDEX IF NOT EXISTS idx_events_sensor_id ON events(sensor_id)");
        
        return true;
    }
    
    void start(int intervalMs = 1000) {
        if (!running) {
            running = true;
            generatorThread = std::thread(&RealTimeDataGenerator::generatorFunction, this, intervalMs);
            std::cout << "Gerador de dados iniciado com intervalo de " << intervalMs << "ms" << std::endl;
        }
    }
    
    void stop() {
        if (running) {
            running = false;
            if (generatorThread.joinable()) {
                generatorThread.join();
            }
            std::cout << "Gerador de dados parado" << std::endl;
        }
    }
    
    ~RealTimeDataGenerator() {
        stop();
    }
};
 
// Classe para exibir dados em tempo real no console
class DataMonitor {
private:
    DatabaseManager& dbManager;
    std::atomic<bool> running;
    std::thread monitorThread;
    
    void monitorFunction(int intervalMs) {
        while (running) {
            // Exibir últimas leituras
            std::cout << "\n=========== ÚLTIMAS LEITURAS DE SENSORES ===========" << std::endl;
            auto results = dbManager.executeSelect(
                "SELECT sensor_id, temperature, humidity, pressure, battery_level, timestamp "
                "FROM sensor_data ORDER BY timestamp DESC LIMIT 5"
            );
            
            displayResults(results);
            
            // Exibir eventos recentes
            std::cout << "\n=========== EVENTOS RECENTES ===========" << std::endl;
            auto events = dbManager.executeSelect(
                "SELECT sensor_id, event_type, description, severity, timestamp "
                "FROM events ORDER BY timestamp DESC LIMIT 3"
            );
            
            displayResults(events);
            
            // Estatísticas
            std::cout << "\n=========== ESTATÍSTICAS ===========" << std::endl;
            auto stats = dbManager.executeSelect(
                "SELECT sensor_id, "
                "AVG(temperature) as avg_temp, "
                "MIN(temperature) as min_temp, "
                "MAX(temperature) as max_temp, "
                "AVG(battery_level) as avg_battery "
                "FROM sensor_data "
                "GROUP BY sensor_id "
                "ORDER BY sensor_id "
                "LIMIT 10"
            );
            
            displayResults(stats);
            
            std::cout << "\n" << std::string(50, '-') << std::endl;
            
            // Aguardar o intervalo configurado
            std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
        }
    }
    
    void displayResults(const std::vector<std::vector<std::string>>& results) {
        if (results.size() <= 1) {
            std::cout << "Sem dados disponíveis." << std::endl;
            return;
        }
        
        // Determinar largura de cada coluna
        std::vector<size_t> colWidths(results[0].size(), 0);
        for (const auto& row : results) {
            for (size_t i = 0; i < row.size(); i++) {
                colWidths[i] = std::max(colWidths[i], row[i].length() + 2);
            }
        }
        
        // Exibir cabeçalhos
        for (size_t i = 0; i < results[0].size(); i++) {
            std::cout << std::left << std::setw(colWidths[i]) << results[0][i] << " | ";
        }
        std::cout << std::endl;
        
        // Linha separadora
        for (size_t i = 0; i < results[0].size(); i++) {
            std::cout << std::string(colWidths[i], '-') << "-+-";
        }
        std::cout << std::endl;
        
        // Exibir dados
        for (size_t row = 1; row < results.size(); row++) {
            for (size_t i = 0; i < results[row].size(); i++) {
                std::cout << std::left << std::setw(colWidths[i]) << results[row][i] << " | ";
            }
            std::cout << std::endl;
        }
    }
 
public:
    DataMonitor(DatabaseManager& db) : dbManager(db), running(false) {}
    
    void start(int intervalMs = 5000) {
        if (!running) {
            running = true;
            monitorThread = std::thread(&DataMonitor::monitorFunction, this, intervalMs);
            std::cout << "Monitor de dados iniciado com intervalo de " << intervalMs << "ms" << std::endl;
        }
    }
    
    void stop() {
        if (running) {
            running = false;
            if (monitorThread.joinable()) {
                monitorThread.join();
            }
            std::cout << "Monitor de dados parado" << std::endl;
        }
    }
    
    ~DataMonitor() {
        stop();
    }
};
 
// Classe para simular consultas de usuários
class QuerySimulator {
private:
    DatabaseManager& dbManager;
    std::atomic<bool> running;
    std::thread simulatorThread;
    std::mt19937 gen;
    std::random_device rd;
    
    // Vetor de possíveis consultas
    std::vector<std::string> queries = {
        "SELECT * FROM sensor_data WHERE temperature > 25 ORDER BY timestamp DESC LIMIT 5",
        "SELECT * FROM sensor_data WHERE battery_level < 0.7 ORDER BY timestamp DESC LIMIT 5",
        "SELECT COUNT(*) as total_events, event_type FROM events GROUP BY event_type",
        "SELECT sensor_id, AVG(temperature) as avg_temp FROM sensor_data GROUP BY sensor_id",
        "SELECT * FROM events WHERE severity > 2 ORDER BY timestamp DESC LIMIT 3",
        "SELECT strftime('%H', timestamp) as hour, COUNT(*) as count FROM sensor_data GROUP BY hour",
        "SELECT * FROM sensor_data WHERE sensor_id = 3 ORDER BY timestamp DESC LIMIT 5",
        "SELECT * FROM events WHERE event_type = 'TEMPERATURE_ALERT' ORDER BY timestamp DESC LIMIT 3"
    };
    
    void simulatorFunction(int minIntervalMs, int maxIntervalMs) {
        std::uniform_int_distribution<> queryDist(0, queries.size() - 1);
        std::uniform_int_distribution<> intervalDist(minIntervalMs, maxIntervalMs);
        
        while (running) {
            // Selecionar uma consulta aleatória
            std::string query = queries[queryDist(gen)];
            
            // Executar a consulta
            std::cout << "\n=========== CONSULTA AD HOC ===========" << std::endl;
            std::cout << "Executando: " << query << std::endl;
            auto results = dbManager.executeSelect(query);
            
            // Exibir resultados
            if (results.size() <= 1) {
                std::cout << "Sem resultados." << std::endl;
            } else {
                std::cout << "Encontrados " << results.size() - 1 << " resultados." << std::endl;
            }
            
            // Aguardar um intervalo aleatório
            int interval = intervalDist(gen);
            std::this_thread::sleep_for(std::chrono::milliseconds(interval));
        }
    }
 
public:
    QuerySimulator(DatabaseManager& db) : dbManager(db), running(false), gen(rd()) {}
    
    void start(int minIntervalMs = 8000, int maxIntervalMs = 15000) {
        if (!running) {
            running = true;
            simulatorThread = std::thread(&QuerySimulator::simulatorFunction, this, minIntervalMs, maxIntervalMs);
            std::cout << "Simulador de consultas iniciado com intervalo entre " 
                   << minIntervalMs << "ms e " << maxIntervalMs << "ms" << std::endl;
        }
    }
    
    void stop() {
        if (running) {
            running = false;
            if (simulatorThread.joinable()) {
                simulatorThread.join();
            }
            std::cout << "Simulador de consultas parado" << std::endl;
        }
    }
    
    ~QuerySimulator() {
        stop();
    }
};
 
// Classe principal do sistema
class LiveDatabase {
private:
    DatabaseManager dbManager;
    RealTimeDataGenerator dataGenerator;
    DataMonitor dataMonitor;
    QuerySimulator querySimulator;
 
public:
    LiveDatabase(const std::string& dbName) : 
        dbManager(dbName),
        dataGenerator(dbManager),
        dataMonitor(dbManager),
        querySimulator(dbManager) {}
    
    bool initialize() {
        if (!dbManager.open()) {
            std::cerr << "Falha ao abrir banco de dados." << std::endl;
            return false;
        }
        
        if (!dataGenerator.initialize()) {
            std::cerr << "Falha ao inicializar gerador de dados." << std::endl;
            return false;
        }
        
        return true;
    }
    
    void start() {
        std::cout << "Iniciando sistema de banco de dados vivo..." << std::endl;
        dataGenerator.start(2000);  // Gerar dados a cada 2 segundos
        std::this_thread::sleep_for(std::chrono::seconds(2));  // Esperar dados iniciais
        dataMonitor.start(7000);    // Atualizar monitor a cada 7 segundos
        querySimulator.start(8000, 15000);  // Simular consultas a cada 8-15 segundos
    }
    
    void stop() {
        std::cout << "Parando sistema..." << std::endl;
        querySimulator.stop();
        dataMonitor.stop();
        dataGenerator.stop();
    }
};
 
int main() {
    LiveDatabase liveDb("sensors_live.db");
    
    if (!liveDb.initialize()) {
        std::cerr << "Falha ao inicializar o sistema." << std::endl;
        return 1;
    }
    
    liveDb.start();
    
    std::cout << "Sistema em execução. Pressione Enter para encerrar..." << std::endl;
    std::cin.get();
    
    liveDb.stop();
    
    return 0;
}

2. Interface Web para Monitoramento

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sistema de Monitoramento Industrial</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background-color: white;
            padding: 20px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            border-radius: 5px;
        }
        
        h1 {
            color: #333;
            border-bottom: 2px solid #007bff;
            padding-bottom: 10px;
        }
        
        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
            margin-top: 20px;
        }
        
        .sensor-card {
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 15px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.05);
            transition: transform 0.3s ease;
        }
        
        .sensor-card:hover {
            transform: translateY(-5px);
        }
        
        .sensor-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 1px solid #eee;
            padding-bottom: 10px;
            margin-bottom: 10px;
        }
        
        .sensor-name {
            font-weight: bold;
            font-size: 1.1em;
            color: #333;
        }
        
        .sensor-id {
            color: #777;
            font-size: 0.9em;
        }
        
        .sensor-value {
            font-size: 2em;
            font-weight: bold;
            text-align: center;
            margin: 10px 0;
        }
        
        .sensor-time {
            font-size: 0.8em;
            color: #888;
            text-align: right;
        }
        
        .temperature { color: #dc3545; }
        .pressure { color: #007bff; }
        .vibration { color: #fd7e14; }
        .level { color: #28a745; }
        .flow { color: #6f42c1; }
        
        .alarm {
            background-color: #fff3cd;
            border-color: #ffeeba;
            animation: pulse 2s infinite;
        }
        
        @keyframes pulse {
            0% { box-shadow: 0 0 0 0px rgba(255, 193, 7, 0.4); }
            70% { box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
            100% { box-shadow: 0 0 0 0px rgba(255, 193, 7, 0); }
        }
        
        .status-bar {
            background-color: #343a40;
            color: white;
            padding: 10px;
            margin-top: 20px;
            border-radius: 5px;
            display: flex;
            justify-content: space-between;
        }
        
        .notification-area {
            margin-top: 20px;
            padding: 10px;
            max-height: 200px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        
        .notification {
            padding: 10px;
            margin-bottom: 5px;
            border-radius: 3px;
            background-color: #f8d7da;
            border: 1px solid #f5c6cb;
            color: #721c24;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Sistema de Monitoramento Industrial</h1>
        
        <div class="status-bar">
            <div class="connection-status">Status: <span id="connection-status">Conectando...</span></div>
            <div class="sensor-count">Sensores ativos: <span id="sensor-count">0</span></div>
            <div class="last-update">Última atualização: <span id="last-update">--</span></div>
        </div>
        
        <div class="dashboard" id="sensor-dashboard">
            <!-- Os sensores serão adicionados dinamicamente aqui -->
        </div>
        
        <h2>Notificações e Alarmes</h2>
        <div class="notification-area" id="notification-area">
            <!-- As notificações serão adicionadas aqui -->
        </div>
    </div>
 
    <script>
        // Configurações
        const wsUrl = 'ws://localhost:9002';
        const sensorTypes = {
            0: { name: 'Temperatura', unit: '°C', class: 'temperature', min: 10, max: 40 },
            1: { name: 'Pressão', unit: 'kPa', class: 'pressure', min: 90, max: 160 },
            2: { name: 'Vibração', unit: 'mm/s', class: 'vibration', min: 0, max: 8 },
            3: { name: 'Nível', unit: '%', class: 'level', min: 10, max: 90 },
            4: { name: 'Fluxo', unit: 'L/min', class: 'flow', min: 5, max: 100 }
        };
        
        // Estado da aplicação
        const sensors = new Map();
        let ws = null;
        let reconnectAttempts = 0;
        
        // Conectar ao WebSocket
        function connectWebSocket() {
            updateConnectionStatus('Conectando...');
            
            ws = new WebSocket(wsUrl);
            
            ws.onopen = () => {
                updateConnectionStatus('Conectado');
                reconnectAttempts = 0;
            };
            
            ws.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    updateSensor(data);
                } catch (error) {
                    console.error('Erro ao processar mensagem:', error);
                }
            };
            
            ws.onclose = () => {
                updateConnectionStatus('Desconectado');
                
                // Tentativa de reconexão
                if (reconnectAttempts < 5) {
                    reconnectAttempts++;
                    const delay = Math.min(1000 * reconnectAttempts, 5000);
                    setTimeout(connectWebSocket, delay);
                } else {
                    updateConnectionStatus('Falha na conexão');
                    addNotification('Não foi possível estabelecer conexão com o servidor após múltiplas tentativas.');
                }
            };
            
            ws.onerror = (error) => {
                console.error('Erro WebSocket:', error);
                updateConnectionStatus('Erro');
            };
        }
        
        // Atualizar status de conexão
        function updateConnectionStatus(status) {
            document.getElementById('connection-status').textContent = status;
            document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
        }
        
        // Atualizar ou criar sensor no dashboard
        function updateSensor(data) {
            const sensorKey = `${data.type}-${data.id}`;
            const typeInfo = sensorTypes[data.type] || { name: 'Desconhecido', unit: '', class: '' };
            
            // Verificar se o sensor já existe
            if (!sensors.has(sensorKey)) {
                // Criar novo card de sensor
                const sensorElement = document.createElement('div');
                sensorElement.className = `sensor-card ${typeInfo.class}`;
                sensorElement.id = sensorKey;
                
                sensorElement.innerHTML = `
                    <div class="sensor-header">
                        <div class="sensor-name">${typeInfo.name}</div>
                        <div class="sensor-id">ID: ${data.id}</div>
                    </div>
                    <div class="sensor-value">${data.value.toFixed(1)} ${typeInfo.unit}</div>
                    <div class="sensor-time">${data.timestamp}</div>
                `;
                
                document.getElementById('sensor-dashboard').appendChild(sensorElement);
                sensors.set(sensorKey, data);
            } else {
                // Atualizar sensor existente
                const sensorElement = document.getElementById(sensorKey);
                sensorElement.querySelector('.sensor-value').textContent = `${data.value.toFixed(1)} ${typeInfo.unit}`;
                sensorElement.querySelector('.sensor-time').textContent = data.timestamp;
                
                // Verificar alarmes
                const isAlarm = data.value < typeInfo.min || data.value > typeInfo.max;
                sensorElement.classList.toggle('alarm', isAlarm);
                
                if (isAlarm) {
                    const message = `ALARME: ${typeInfo.name} #${data.id} fora dos limites! Valor: ${data.value.toFixed(1)} ${typeInfo.unit} (Limites: ${typeInfo.min} - ${typeInfo.max})`;
                    addNotification(message);
                }
                
                sensors.set(sensorKey, data);
            }
            
            // Atualizar contagem de sensores
            document.getElementById('sensor-count').textContent = sensors.size;
            document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
        }
        
        // Adicionar notificação
        function addNotification(message) {
            const notificationElement = document.createElement('div');
            notificationElement.className = 'notification';
            notificationElement.textContent = message;
            
            const notificationArea = document.getElementById('notification-area');
            notificationArea.insertBefore(notificationElement, notificationArea.firstChild);
            
            // Limitar número de notificações
            if (notificationArea.children.length > 10) {
                notificationArea.removeChild(notificationArea.lastChild);
            }
        }
        
        // Inicializar aplicação
        window.addEventListener('load', () => {
            connectWebSocket();
        });
    </script>
</body>
</html>

Principais Recursos e Tecnologias

1. Sistema de Aquisição de Dados

  • Coleta de dados em tempo real de múltiplos sensores
  • Thread-safe com controle de concorrência
  • Polling configurável
  • Estruturas de dados otimizadas para tempo real

2. Sistema de Processamento

  • Análise de valores para detecção de anomalias
  • Sistema de alarmes baseado em limiares configuráveis
  • Processamento paralelo para melhor desempenho

3. Comunicação em Tempo Real

  • WebSockets para comunicação bidirecional de baixa latência
  • Serialização JSON para interoperabilidade
  • Reconexão automática em caso de falhas

4. Interface em Tempo Real

  • Dashboard responsivo com atualizações em tempo real
  • Indicadores visuais para alarmes
  • Design adaptável a diferentes dispositivos

Benefícios da Arquitetura

  1. Baixa Latência: O sistema é capaz de processar e reagir a eventos em milissegundos.
  2. Alta Disponibilidade: Mecanismos de recuperação automática garantem funcionamento contínuo.
  3. Escalabilidade: Arquitetura modular permite adicionar novos sensores e subsistemas.
  4. Interoperabilidade: Padrões abertos (JSON, WebSockets) facilitam integração com outros sistemas.
  5. Robustez: Tratamento de erros em múltiplas camadas evita falhas em cascata.

Aplicações Industriais

Este sistema pode ser adaptado para monitoramento e controle em diversos setores:

  1. Indústria Química: Monitoramento de pressão, temperatura e nível em reatores
  2. Manufatura: Controle de processos de produção e detecção de falhas em equipamentos
  3. Energia: Monitoramento de usinas e transmissão de energia
  4. Petróleo e Gás: Supervisão de oleodutos e plataformas
  5. Mineração: Monitoramento de operações de extração e processamento

Conclusão

Esta implementação demonstra como C++ moderno pode ser usado para criar sistemas de integração em tempo real robustos e de alto desempenho. O sistema combina múltiplas tecnologias (threads, mutexes, websockets, interface web) para fornecer uma solução completa para monitoramento industrial.