Ventilador Névoa com Temporizador caseiro RaspberryPI

Linux Utilidades

RUNBOOK OPERACIONAL: Sistema de Controle de Névoa e Monitoramento Climático

1. Visão Geral do Sistema

Este sistema automatiza o acionamento de uma válvula solenóide para gerar névoa de água em um ventilador de quintal, com temporizador e ciclos de névoa, visando o conforto térmico. O controle é feito remotamente via interface Web (HTTP), que também exibe o monitoramento em tempo real e o histórico de temperatura e umidade.

Este projeto pode ser considerado um complemento do artigo “Monitoramento de Temperatura e Umidade com Raspberry Pi e Grafana”, pois usa boa parte da mesma estrutura com Raspberry PI 3b+ e o sensor de temperatura e umidade. Porém, se você não acompanhou o artigo anterior, não tem problemas, pois vou abordar aqui tudo do absoluto zero.

  • Plataforma Base: Raspberry Pi 3 Model B (Debian / OS baseado em Linux).
  • Controle Lógico: Scripts Python (gpiozero, Adafruit_DHT) orquestrados via Shell Script e PHP.
  • Apresentação Web: Servidor Apache2 (página única index.php com interface responsiva, gráficos Chart.js e acionamento via AJAX).
  • Armazenamento de Dados: Arquivo CSV local (/etc/grafana/csv/temperatura.csv).

2. Arquitetura de Hardware (Bill of Materials – BOM)

  • Controladora: Raspberry Pi 3 Model B (Quad-core, 1GB RAM, Wi-Fi 2.4GHz integrado).
  • Sensor Climático: Módulo DHT11 (3 pinos, alimentação 3.3V a 5V).
  • Atuador Lógico: Módulo de Relé 1 Canal (HW-803) com Optoacoplador.
  • Atuador Físico: Válvula Solenóide de Água DC12V (NF – Normalmente Fechada), tipo de pressão.
  • Alimentação Externa (Carga): Fonte Chaveada AC/DC (Entrada 100-240V / Saída 12V DC, 2A).
  • Refrigeração: Micro Ventilador (Mini Fan Brushless) DC 5V.
  • Cabeamento: Jumpers Dupont (Fêmea-Fêmea) e Fio Paralelo Flexível de Cobre (0.50mm² ou 0.75mm²).

3. Diagrama Físico e Pinagem (Pinout)

O sistema utiliza o header GPIO de 40 pinos do Raspberry Pi. O mapeamento abaixo considera a placa vista de cima (lado do MicroSD na parte inferior esquerda).

(Lado do Cartão MicroSD)
[3.3V]    01 [X]    [X] 02 [5V]
[GPIO 02] 03 [ ]    [X] 04 [5V]
[GPIO 03] 05 [ ]    [X] 06 [GND]
[GPIO 04] 07 [ ]    [X] 08 [GPIO 14]
[GND]     09 [X]    [ ] 10 [GPIO 15]
[GPIO 17] 11 [ ]    [X] 12 [GPIO 18]
[GPIO 27] 13 [ ]    [X] 14 [GND]
... ...

Mapeamento das Conexões:

  • Módulo Sensor DHT11: VCC (Fio Roxo) -> Pino 01 (3.3V). GND (Fio Verde) -> Pino 06 (GND). DATA (Fio Azul) -> Pino 08 (GPIO 14).
  • Módulo Relé HW-803 (Jumper obrigatoriamente em “H”): DC+ -> Pino 02 (5V). DC- -> Pino 14 (GND). IN -> Pino 12 (GPIO 18).
  • Micro Ventilador (Fan): Cabo Vermelho (+) -> Pino 04 (5V). Cabo Preto (-) -> Pino 09 (GND).
  • Circuito Hidráulico (12V): Fonte 12V Positivo (Vermelho) -> Borne COM do Relé. Borne NO do Relé -> Terminal 1 da Válvula Solenóide. Fonte 12V Negativo (Preto) -> Terminal 2 da Válvula Solenóide.

4. Provisionamento Automatizado (Disaster Recovery Lógico)

Em caso de falha catastrófica de SO ou novo hardware, utilize o script monolítico abaixo para provisionar um Raspberry Pi recém-formatado (Debian/Raspberry OS base). Ele instala pacotes, cria diretórios, gera códigos Python/PHP, ajusta o sudoers e cria tarefas cron.

Execução:

  1. Crie o arquivo: sudo nano install_clima.sh.
  2. Cole o código abaixo e salve.
install_clima.sh
📥 Baixar
#!/bin/bash
# ==============================================================================
# SCRIPT DE INSTALAÇÃO AUTOMATIZADA - PROJETO NÉVOA & CLIMA
# Executar como ROOT (sudo bash install_clima.sh)
# ==============================================================================

echo "Iniciando a automação do ambiente..."

# 1. Instalação de Pacotes Base
echo "[1/7] Instalando dependências do sistema (Apache, PHP, Python)..."
apt-get update
apt-get install -y apache2 php libapache2-mod-php python3 python3-venv python3-dev python3-pip gcc libgpiod2

# 2. Criação de Diretórios
echo "[2/7] Criando estrutura de diretórios..."
mkdir -p /scripts
mkdir -p /etc/grafana/csv
mkdir -p /tmp/dht_saneamento

# 3. Configuração do Python Virtual Environment
echo "[3/7] Criando Virtual Environment e instalando bibliotecas GPIO/DHT..."
python3 -m venv /root/dht_venv
/root/dht_venv/bin/pip install Adafruit_DHT gpiozero

# 4. Criação dos Scripts Backend
echo "[4/7] Escrevendo scripts Shell e Python..."

# -> sensor.py
cat << 'EOF' > /scripts/sensor.py
#!/usr/bin/env python3
import sys, Adafruit_DHT as dht, Adafruit_DHT.Raspberry_Pi as rpi
try:
    h, t = dht.read_retry(dht.DHT11, 14, platform=rpi)
    if h is not None and t is not None:
        print(f"Temperatura: {round(t, 1)}°C\nUmidade: {round(h, 1)}%")
    else: sys.exit(1)
except: sys.exit(1)
EOF

# -> leitura_dht.py
cat << 'EOF' > /scripts/leitura_dht.py
#!/usr/bin/env python3
import sys, Adafruit_DHT as dht, Adafruit_DHT.Raspberry_Pi as rpi, json
try:
    h, t = dht.read_retry(dht.DHT11, 14, platform=rpi)
    if h is not None and t is not None:
        print(json.dumps({"success": True, "temperature": round(t, 1), "humidity": round(h, 1)}))
    else: print(json.dumps({"success": False, "error": "Falha na leitura."}))
except Exception as e:
    print(json.dumps({"success": False, "error": str(e)}))
    sys.exit(1)
EOF

# -> rele_loop.py
cat << 'EOF' > /scripts/rele_loop.py
#!/usr/bin/env python3
import sys, time
from gpiozero import OutputDevice
try:
    FLUXO = float(sys.argv[1])
    with OutputDevice(18, active_high=True) as rele:
        rele.on()
        time.sleep(FLUXO)
        rele.off()
except: sys.exit(1)
EOF

# -> LigaReleGPIO4.sh
cat << 'EOF' > /scripts/LigaReleGPIO4.sh
#!/bin/bash
Intervalo=$1
Fluxo=$2
while true; do
    /usr/bin/python3 /scripts/rele_loop.py $Fluxo
    sleep $Intervalo
done
EOF

# -> coleta_dht.sh
cat << 'EOF' > /scripts/coleta_dht.sh
#!/bin/bash
PYTHON_ENV="/root/dht_venv/bin/python"
SENSOR_SCRIPT="/scripts/sensor.py"
CSV_FILE="/etc/grafana/csv/temperatura.csv"
DATA=$(TZ='America/Sao_Paulo' date +%Y-%m-%d\ %H:%M:%S)
TEMPERATURA=$("$PYTHON_ENV" "$SENSOR_SCRIPT" 2>&1 | egrep "Temperatura|Umidade" | sed 's/Temperatura: //g; s/Umidade: //g' | sed 's/°C//g; s/%//g' | tr '\n' ';' | sed 's/;$//')
if [ -n "$TEMPERATURA" ]; then echo "$DATA;$TEMPERATURA" >> "$CSV_FILE"; fi
EOF

# -> saneamento_csv.sh
cat << 'EOF' > /scripts/saneamento_csv.sh
#!/bin/bash
CSV="/etc/grafana/csv/temperatura.csv"
TMP_CSV="/tmp/temperatura.tmp"
LOG_DIR="/tmp/dht_saneamento/"
mkdir -p "$LOG_DIR"
LOGTMP="$LOG_DIR/maior90.tmp"
rm -f "$LOGTMP"
HEADER=$(head -n 1 "$CSV")
DATA_LIMITE=$(date -d '90 days ago' +%Y-%m-%d\ %H:%M:%S)
tail -n +2 "$CSV" | awk -F';' -v data_limite="$DATA_LIMITE" '{ if ($1 < data_limite) print $0 }' > "$LOGTMP"
if [ -s "$LOGTMP" ]; then
    echo "$HEADER" > "$TMP_CSV"
    grep -v -F -f "$LOGTMP" <(tail -n +2 "$CSV") >> "$TMP_CSV"
    mv "$TMP_CSV" "$CSV"
fi
EOF

# Ajusta permissões de execução para os scripts
chmod +x /scripts/*

# 5. Inicialização do CSV
echo "[5/7] Configurando CSV base..."
if [ ! -f /etc/grafana/csv/temperatura.csv ]; then
    echo "data;temperatura;umidade" > /etc/grafana/csv/temperatura.csv
fi
chmod 666 /etc/grafana/csv/temperatura.csv

# 6. Publicação da Interface Web Unificada (index.php)
echo "[6/7] Publicando página Web..."

cat << 'EOF' > /var/www/html/index.php
<?php
$csv_file = '/etc/grafana/csv/temperatura.csv';
$python_env = '/root/dht_venv/bin/python';
$script_dht = '/scripts/leitura_dht.py';
$script_rele = '/scripts/LigaReleGPIO4.sh';
$pid_file = '/tmp/rele_script.pid';
$log_file = '/tmp/rele_script.log';
$params_file = '/tmp/rele_params.json';
$default_file = '/tmp/rele_defaults.json';

function read_dht_data() {
    global $python_env, $script_dht;
    $output = shell_exec("sudo $python_env $script_dht 2>&1");
    $data = json_decode($output, true);
    if ($data && isset($data['success']) && $data['success']) return $data;
    return ['success' => false, 'temperature' => 'N/A', 'humidity' => 'N/A', 'error' => $data['error'] ?? 'Falha na leitura.'];
}
function is_script_running() {
    global $pid_file, $params_file;
    if (file_exists($pid_file)) {
        $pid = trim(file_get_contents($pid_file));
        exec("ps -p $pid -o pid=", $output);
        if (!empty($output)) return true;
        @unlink($pid_file); @unlink($params_file);
    }
    return false;
}
function get_current_params() {
    global $params_file;
    if (file_exists($params_file)) return json_decode(file_get_contents($params_file), true) ?: ['intervalo' => 15, 'fluxo' => 3];
    return ['intervalo' => 15, 'fluxo' => 3];
}
function get_default_params() {
    global $default_file;
    if (file_exists($default_file)) return json_decode(file_get_contents($default_file), true) ?: ['intervalo' => 15, 'fluxo' => 3];
    return ['intervalo' => 15, 'fluxo' => 3];
}
function set_default_params($intervalo, $fluxo) {
    global $default_file;
    return file_put_contents($default_file, json_encode(['intervalo' => (int)$intervalo, 'fluxo' => (int)$fluxo])) !== false;
}
function start_script($intervalo, $fluxo) {
    global $pid_file, $log_file, $params_file, $script_rele;
    file_put_contents($params_file, json_encode(['intervalo' => (int)$intervalo, 'fluxo' => (int)$fluxo]));
    exec("nohup sudo $script_rele $intervalo $fluxo > $log_file 2>&1 & echo $! > $pid_file");
    return file_exists($pid_file) ? trim(file_get_contents($pid_file)) : null;
}
function stop_script() {
    global $pid_file, $params_file;
    if (file_exists($pid_file)) {
        exec("kill " . trim(file_get_contents($pid_file)));
        @unlink($pid_file); @unlink($params_file);
        return true;
    }
    return false;
}
function get_csv_history($hours = 24) {
    global $csv_file;
    $data = [];
    if (!file_exists($csv_file)) return $data;
    $cutoff = ($hours > 0) ? (time() - ($hours * 3600)) : 0;
    if (($handle = fopen($csv_file, "r")) !== FALSE) {
        fgetcsv($handle, 1000, ";");
        while (($row = fgetcsv($handle, 1000, ";")) !== FALSE) {
            if (count($row) >= 3) {
                if ($hours == 0 || strtotime(trim($row[0])) >= $cutoff) {
                    $data[] = ['time' => trim($row[0]), 'temp' => floatval(trim($row[1])), 'hum' => floatval(trim($row[2]))];
                }
            }
        }
        fclose($handle);
    }
    return $data;
}
if (isset($_REQUEST['action'])) {
    header('Content-Type: application/json');
    $a = $_REQUEST['action'];
    if ($a === 'dht_live') { echo json_encode(read_dht_data()); exit; }
    if ($a === 'get_csv') { echo json_encode(['success' => true, 'data' => get_csv_history(intval($_GET['hours'] ?? 24))]); exit; }
    if ($a === 'shutdown') { exec("sudo shutdown -h now"); echo json_encode(['success' => true]); exit; }
    
    $i = intval($_POST['intervalo'] ?? 0); $f = intval($_POST['fluxo'] ?? 0);
    if ($a === 'start') {
        if ($i > 0 && $f > 0 && $f < $i) echo json_encode(['success' => (bool)start_script($i, $f)]);
        else echo json_encode(['success' => false, 'message' => 'Fluxo deve ser menor que Intervalo.']);
        exit;
    }
    if ($a === 'stop') { echo json_encode(['success' => stop_script()]); exit; }
    if ($a === 'set_default') {
        if ($i > 0 && $f > 0 && $f < $i) echo json_encode(['success' => set_default_params($i, $f)]);
        else echo json_encode(['success' => false, 'message' => 'Valores inválidos.']);
        exit;
    }
}
$is_running = is_script_running();
$params = $is_running ? get_current_params() : get_default_params();
$dht_data = read_dht_data();
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Controle de Névoa</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: sans-serif; background: #000; color: #fff; padding: 20px; display: flex; flex-direction: column; align-items: center; }
        .container { max-width: 650px; width: 100%; display: flex; flex-direction: column; gap: 20px; }
        .card { background: #1e1e1e; border: 1px solid #333; border-radius: 12px; padding: 20px; text-align: center; }
        select { width: 100%; padding: 10px; margin-top: 5px; border-radius: 6px; font-weight: bold; }
        .btn { width: 100%; padding: 14px; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: bold; cursor: pointer; color: #fff; margin-top: 15px; }
        .btn-default { background: #007bff; font-size: 0.9rem; padding: 10px; }
        .btn-on { background: #28a745; } .btn-off { background: #dc3545; }
        .btn-shutdown { background: #b71c1c; }
        .status-box { padding: 12px; border-radius: 6px; font-weight: bold; margin-top: 15px; }
        .s-run { background: #155724; color: #d4edda; } .s-stop { background: #721c24; color: #f8d7da; }
        .chart-container { position: relative; height: 220px; width: 100%; margin-bottom: 20px; }
        .tf-btn { background: #333; color: #fff; border: 1px solid #555; padding: 6px; border-radius: 4px; cursor: pointer; margin-right: 5px; }
        .tf-btn.active { background: #007bff; }
    </style>
</head>
<body>
<div class="container">
    <h2>Controle de Névoa & Clima</h2>
    <div class="card" style="background: #3e2723; border-color: #ffcc80; color: #ffab40;">
        <span style="font-size:1.2rem; font-weight:bold;">
            🌡️ <span id="t-val"><?php echo $dht_data['temperature']; ?></span> °C | 💧 <span id="h-val"><?php echo $dht_data['humidity']; ?></span> %
        </span>
    </div>
    <div class="card" style="text-align: left;">
        <div style="display:flex; gap:15px;">
            <div style="flex:1;"><label>Intervalo:</label>
                <select id="i-val">
                    <?php foreach([5,10,15,30,60,120,180,240,300,600] as $v) echo "<option value='$v'".($v==$params['intervalo']?' selected':'' ).">$v s</option>"; ?>
                </select>
            </div>
            <div style="flex:1;"><label>Fluxo:</label>
                <select id="f-val">
                    <?php foreach([2,3,5,10,15,30,60] as $v) echo "<option value='$v'".($v==$params['fluxo']?' selected':'' ).">$v s</option>"; ?>
                </select>
            </div>
        </div>
        <button id="btnDef" class="btn btn-default">Definir como Padrão</button>
        <button id="btnTg" class="btn <?php echo $is_running ? 'btn-off' : 'btn-on'; ?>" data-a="<?php echo $is_running ? 'stop' : 'start'; ?>">
            <?php echo $is_running ? 'Desligar' : 'Ligar'; ?>
        </button>
        <div class="status-box <?php echo $is_running ? 's-run' : 's-stop'; ?>">
            Status: <strong><?php echo $is_running ? 'LIGADO' : 'DESLIGADO'; ?></strong>
        </div>
    </div>
    <div class="card">
        <div style="margin-bottom: 15px;">
            <button class="tf-btn" onclick="ld(1,this)">1h</button><button class="tf-btn" onclick="ld(6,this)">6h</button>
            <button class="tf-btn active" onclick="ld(24,this)">24h</button><button class="tf-btn" onclick="ld(168,this)">7d</button>
        </div>
        <div class="chart-container"><canvas id="cT"></canvas></div>
        <div class="chart-container"><canvas id="cH"></canvas></div>
    </div>
    <button class="btn btn-shutdown" onclick="if(confirm('Desligar RPi?')) fetch('index.php?action=shutdown')">⚠️ Desligar RPi</button>
</div>
<script>
    let tC = null, hC = null;
    const cT = v => v >= 40 ? '#f44336' : (v >= 35 ? '#ffeb3b' : '#3399ff');
    const cH = v => v < 30 ? '#f44336' : (v < 40 ? '#ffeb3b' : (v < 60 ? '#3399ff' : (v < 70 ? '#ffeb3b' : '#f44336')));
    
    setInterval(() => {
        fetch('index.php?action=dht_live').then(r=>r.json()).then(d=>{
            if(d.success){ document.getElementById('t-val').innerText=d.temperature; document.getElementById('h-val').innerText=d.humidity; }
        });
    }, 10000);

    function ld(h, b) {
        if(b) { document.querySelectorAll('.tf-btn').forEach(x=>x.classList.remove('active')); b.classList.add('active'); }
        fetch('index.php?action=get_csv&hours='+h).then(r=>r.json()).then(r => {
            if(!r.success) return;
            const lbs = r.data.map(d=>d.time.split(' ')[1].substring(0,5)), ts=r.data.map(d=>d.temp), hs=r.data.map(d=>d.hum);
            if(tC) tC.destroy(); if(hC) hC.destroy();
            const opts = { responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}}, scales:{x:{ticks:{color:'#888'}}, y:{ticks:{color:'#888'}}} };
            tC = new Chart(document.getElementById('cT'), { type:'line', data:{ labels:lbs, datasets:[{ data:ts, borderWidth:2, pointRadius:2, tension:0.2, segment:{borderColor:c=>cT(c.p1.raw)}, fill:false }] }, options:opts });
            hC = new Chart(document.getElementById('cH'), { type:'line', data:{ labels:lbs, datasets:[{ data:hs, borderWidth:2, pointRadius:2, tension:0.2, segment:{borderColor:c=>cH(c.p1.raw)}, fill:false }] }, options:opts });
        });
    } ld(24);

    document.getElementById('btnTg').onclick = function() {
        const a=this.dataset.a, i=document.getElementById('i-val').value, f=document.getElementById('f-val').value;
        if(a==='start' && parseInt(f) >= parseInt(i)) return alert('Fluxo deve ser MENOR que Intervalo.');
        fetch('index.php', { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:`action=${a}&intervalo=${i}&fluxo=${f}` }).then(()=>location.reload());
    };
    document.getElementById('btnDef').onclick = function() {
        const i=document.getElementById('i-val').value, f=document.getElementById('f-val').value;
        if(parseInt(f) >= parseInt(i)) return alert('Fluxo deve ser MENOR que Intervalo.');
        fetch('index.php', { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:`action=set_default&intervalo=${i}&fluxo=${f}` }).then(()=>alert('Padrão Salvo!'));
    };
</script>
</body>
</html>
EOF

# 7. Permissões Globais (Sudoers)
echo "[7/7] Configurando permissões do Apache (sudoers) e agendando Cron Jobs..."

# Remove regra antiga se existir e insere a nova
rm -f /etc/sudoers.d/clima_web
echo "www-data ALL=(ALL) NOPASSWD: /sbin/shutdown, /root/dht_venv/bin/python, /scripts/leitura_dht.py, /scripts/LigaReleGPIO4.sh" > /etc/sudoers.d/clima_web
chmod 0440 /etc/sudoers.d/clima_web

# 8. Agendamento do Cron
crontab -l > /tmp/cron_bkp 2>/dev/null || true
sed -i '/coleta_dht.sh/d' /tmp/cron_bkp
sed -i '/saneamento_csv.sh/d' /tmp/cron_bkp
echo "*/5 * * * * /scripts/coleta_dht.sh" >> /tmp/cron_bkp
echo "0 1 * * * /scripts/saneamento_csv.sh" >> /tmp/cron_bkp
crontab /tmp/cron_bkp
rm /tmp/cron_bkp

echo "========================================================================="
echo "INSTALAÇÃO CONCLUÍDA COM SUCESSO!"
echo "O sistema foi mapeado nos pinos GPIO 14 (DHT11) e GPIO 18 (Relé)."
echo "O CSV e a Interface Web estão operacionais."
echo "Você já pode acessar a página através do IP do Raspberry Pi."
echo "========================================================================="
  1. Execute: sudo bash install_clima.sh.

Nota de Segurança: O script injeta o conteúdo index.php (contendo todas as funções PHP, CSS e JS do dashboard) e define as regras no /etc/sudoers.d/clima_web. Consulte o arquivo fonte gerado anteriormente na documentação do projeto para a carga completa do heredoc se necessário.

5. Rede e Acesso (Network & Security)

  • Reserva DHCP: É estritamente recomendado configurar uma reserva de IP estático no roteador/DHCP server para a interface wlan0 (MAC Address: b8:27:eb:51:95:c4) para garantir o acesso ininterrupto via celular (ex: http://192.168.0.186).
  • Acesso Administrativo: Via protocolo SSH (Porta 22 padrão) utilizando usuário root ou sudoer (pi/thiago).
  • Autenticação Web: A página unificada no Apache opera sem autenticação, restrita ao acesso interno da LAN.

6. Monitoramento e Logs

Para auditoria de falhas de execução, os logs do sistema estão segregados nos seguintes caminhos absolutos:

  • Logs do Servidor Web (Apache2): /var/log/apache2/error.log.
  • Logs do Processo de Relé (Em background): /tmp/rele_script.log.
  • PID Ativo do Relé: /tmp/rele_script.pid.
  • Execuções do Agendador (Cron): Consulte o syslog filtrando o cron via grep CRON /var/log/syslog.

7. Guia de Solução de Problemas (Troubleshooting)

  • Sintoma A: A página Web está extremamente lenta para carregar e acusa “Erro DHT: Falha na leitura”.
    Causa Raiz: O script Python do backend (/scripts/leitura_dht.py) não encontrou o sensor na porta GPIO configurada. A biblioteca entra em loop de retentativas.
    Resolução: Verifique a integridade física do fio de dados (Pino 08). Garanta que no arquivo /scripts/leitura_dht.py a variável está apontada para a porta correta: PIN = 14.
  • Sintoma B: A válvula solenóide abre sozinha ao inicializar o Raspberry Pi (Boot).
    Causa Raiz: Jumper do módulo de Relé está na posição Low Level Trigger (L) ou script invertido.
    Resolução: Movimentar fisicamente o jumper da placa do relé HW-803 para os pinos “H”. Garantir que no script /scripts/rele_loop.py a diretiva seja: active_high=True.
  • Sintoma C: Ouve-se o “clique” do relé, a luz verde acende, mas não sai névoa/água.
    Causa Raiz 1 (Elétrica): A fonte de 12V está fora da tomada ou desarmou.
    Causa Raiz 2 (Hidráulica): Válvula solenóide está instalada invertida.
    Resolução: Verificar a tensão com multímetro nos bornes da válvula. Inverter fisicamente a válvula na mangueira seguindo a seta indicativa de fluxo d’água no plástico do corpo da solenóide.

8. Manutenção Preventiva e Backups

  • Limpeza Hidráulica (Semestral): Inspecionar e limpar o filtro tipo “Y” na entrada da solenóide. Desobstruir os bicos aspersores/nebulizadores deixando-os de molho em solução de limpeza anticalcária (vinagre/água morna) para manter a pressurização da névoa uniforme.
  • Limpeza Lógica (Diária Automática): O job cron /scripts/saneamento_csv.sh já purga dados históricos do arquivo /etc/grafana/csv/temperatura.csv superiores a 90 dias para evitar esgotamento de inodes ou espaço em disco.
  • Backup Físico (Anual): Recomenda-se realizar uma imagem (clone bit a bit) do cartão MicroSD funcional usando ferramentas como dd (Linux) ou BalenaEtcher para garantir a retomada do serviço em minutos em caso de falha do cartão.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.