Compare commits
2 Commits
8d537383fb
...
777993dfbb
| Author | SHA1 | Date |
|---|---|---|
|
|
777993dfbb | |
|
|
db72144faa |
|
|
@ -56,9 +56,9 @@ namespace AgroBase.Models.Modules
|
||||||
};
|
};
|
||||||
//public int QuantidadeBicos { get; set; } = 4;
|
//public int QuantidadeBicos { get; set; } = 4;
|
||||||
public double DensidadeHerbicida { get; set; } = 1.0;
|
public double DensidadeHerbicida { get; set; } = 1.0;
|
||||||
public List<AtuadorBicoModel> BicosPulverizadores { get; set; } = new List<AtuadorBicoModel>();
|
public List<AtuadorBicoModel> BicosPulverizadores { get; set; }
|
||||||
public List<AtuadorBombaModel> BombasPressurizadoras { get; set; } = new List<AtuadorBombaModel>();
|
public List<AtuadorBombaModel> BombasPressurizadoras { get; set; }
|
||||||
public List<SensorModel> Sensores { get; set; } = new List<SensorModel>();
|
public List<SensorModel> Sensores { get; set; }
|
||||||
public string ScriptDeteccaoErvas { get; set; } = PythonService.ScriptWeedDetector;
|
public string ScriptDeteccaoErvas { get; set; } = PythonService.ScriptWeedDetector;
|
||||||
|
|
||||||
public AsyncTaskTimerModel tmrRegistrador;
|
public AsyncTaskTimerModel tmrRegistrador;
|
||||||
|
|
@ -67,7 +67,7 @@ namespace AgroBase.Models.Modules
|
||||||
public AtuadorMensagemJsonModel DadosLeitura { get; set; } = new AtuadorMensagemJsonModel();
|
public AtuadorMensagemJsonModel DadosLeitura { get; set; } = new AtuadorMensagemJsonModel();
|
||||||
public AtuHandler _AtuHandler = new AtuHandler();
|
public AtuHandler _AtuHandler = new AtuHandler();
|
||||||
|
|
||||||
public SensorModel SensorAnguloBarraEsquerda
|
private SensorModel SensorAnguloBarraEsquerda
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -75,7 +75,7 @@ namespace AgroBase.Models.Modules
|
||||||
return sMAG;
|
return sMAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public SensorModel SensorAnguloBarraDireita
|
private SensorModel SensorAnguloBarraDireita
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -83,7 +83,7 @@ namespace AgroBase.Models.Modules
|
||||||
return sMAG;
|
return sMAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public SensorModel SensorAnguloBarraAltura
|
private SensorModel SensorAnguloBarraAltura
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -1161,7 +1161,7 @@ namespace AgroBase.Models.Modules
|
||||||
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; } = new List<SensorValoresLeituraModel>();
|
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1268,7 +1268,7 @@ namespace AgroBase.Models.Modules
|
||||||
return !Mandatorio || (Comandar && Inicializado);
|
return !Mandatorio || (Comandar && Inicializado);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<FuncoesPinout> Funcoes { get; set; } = new List<FuncoesPinout>();
|
public List<FuncoesPinout> Funcoes { get; set; }
|
||||||
public bool Inicializado { get; set; }
|
public bool Inicializado { get; set; }
|
||||||
public DateTime UltimaLeitura
|
public DateTime UltimaLeitura
|
||||||
{
|
{
|
||||||
|
|
@ -1277,7 +1277,7 @@ namespace AgroBase.Models.Modules
|
||||||
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; } = new List<SensorValoresLeituraModel>();
|
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2462,8 +2462,8 @@ namespace AgroBase.Models.Modules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<FuncoesPinout> Funcoes { get; set; }
|
public List<FuncoesPinout> Funcoes { get; set; }
|
||||||
public List<SensorParametroModel> Parametros { get; set;} = new List<SensorParametroModel>();
|
public List<SensorParametroModel> Parametros { get; set; }
|
||||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; } = new List<SensorValoresLeituraModel>();
|
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||||
|
|
||||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel Pinout, bool conectar, T_Code Dispositivo)
|
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel Pinout, bool conectar, T_Code Dispositivo)
|
||||||
{
|
{
|
||||||
|
|
@ -2513,7 +2513,7 @@ namespace AgroBase.Models.Modules
|
||||||
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; } = new List<SensorValoresLeituraModel>();
|
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||||
|
|
||||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||||
{
|
{
|
||||||
|
|
@ -2571,7 +2571,7 @@ namespace AgroBase.Models.Modules
|
||||||
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; } = new List<SensorValoresLeituraModel>();
|
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||||
|
|
||||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||||
{
|
{
|
||||||
|
|
@ -2630,7 +2630,7 @@ namespace AgroBase.Models.Modules
|
||||||
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; } = new List<SensorValoresLeituraModel>();
|
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||||
|
|
||||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,624 @@
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import statistics
|
||||||
|
import socket
|
||||||
|
import psutil
|
||||||
|
import subprocess
|
||||||
|
from collections import deque
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
from shared.contexto_global_redis import ContextoGlobalRedis
|
||||||
|
from shared.enums import StatusModulo, T_Code
|
||||||
|
from health_worker.modulos.base import ModuloDiagnosticoBase
|
||||||
|
|
||||||
|
class ModuloIPBribge(ModuloDiagnosticoBase):
|
||||||
|
def __init__(self, window_size: int = 5):
|
||||||
|
self.t_code = T_Code.Ipb
|
||||||
|
self.nome = "IP_Brigde"
|
||||||
|
self.timeout = 5
|
||||||
|
|
||||||
|
self._thread_saude = None
|
||||||
|
self._running = False
|
||||||
|
|
||||||
|
self.nic_name = None
|
||||||
|
self.window_size = window_size
|
||||||
|
|
||||||
|
self.rtts = deque(maxlen=window_size) # ping válidos (ms)
|
||||||
|
self.timeouts = deque(maxlen=window_size) # True/False por tentativa
|
||||||
|
self.loses = deque(maxlen=window_size) # True/False por tentativa
|
||||||
|
|
||||||
|
self.last_nic_counters = None
|
||||||
|
self.nic_error_rates = deque(maxlen=window_size)
|
||||||
|
|
||||||
|
self.last_bw_counters = None
|
||||||
|
self.last_bw_ts = None
|
||||||
|
self.bw_rx_mbps = deque(maxlen=window_size)
|
||||||
|
self.bw_tx_mbps = deque(maxlen=window_size)
|
||||||
|
|
||||||
|
self.last_heartbeat_ts = time.time() # atualize isso de fora
|
||||||
|
|
||||||
|
self.rover_id = None
|
||||||
|
self._mqtt_conectado = False
|
||||||
|
self.sub = False
|
||||||
|
self._start_mqtt_heartbeat()
|
||||||
|
|
||||||
|
|
||||||
|
def atualizar_saude(self):
|
||||||
|
if self._running:
|
||||||
|
return
|
||||||
|
self._running = True
|
||||||
|
self._thread_saude = threading.Thread(target=self._job_saude, daemon=True)
|
||||||
|
self._thread_saude.start()
|
||||||
|
|
||||||
|
def _job_saude(self):
|
||||||
|
try:
|
||||||
|
self.atualizar_saude_interno()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar saude: {e}")
|
||||||
|
finally:
|
||||||
|
self._running = False
|
||||||
|
|
||||||
|
def _start_mqtt_heartbeat(self):
|
||||||
|
try:
|
||||||
|
if self._mqtt_conectado:
|
||||||
|
return
|
||||||
|
base_ip = self.get_base_ip()
|
||||||
|
if base_ip is None:
|
||||||
|
self._mqtt_conectado = False
|
||||||
|
return
|
||||||
|
self._mqtt = mqtt.Client()
|
||||||
|
self._mqtt.on_connect = self._on_connect
|
||||||
|
self._mqtt.on_message = self._on_message
|
||||||
|
self._mqtt.on_disconnect = self._on_disconnect
|
||||||
|
self._mqtt.connect(base_ip, 1883, 60)
|
||||||
|
self._mqtt.loop_start()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#print("Erro ao se conectar no broker mqtt")
|
||||||
|
|
||||||
|
def _on_connect(self, client, userdata, flags, rc):
|
||||||
|
if rc == 0:
|
||||||
|
print("[HEARTBEAT] MQTT conectado")
|
||||||
|
self._mqtt_conectado = True
|
||||||
|
|
||||||
|
self._reset_janelas()
|
||||||
|
|
||||||
|
self.rover_id = ContextoGlobalRedis.get_equipamento().get("serial_number")
|
||||||
|
|
||||||
|
if self.sub == False:
|
||||||
|
topic = f"agrobot/v1/rover/{self.rover_id}/heartbeat"
|
||||||
|
client.subscribe(topic)
|
||||||
|
print("[HEARTBEAT] Subscribado em:", topic)
|
||||||
|
self.sub = True
|
||||||
|
else:
|
||||||
|
print("Erro ao conectar MQTT:", rc)
|
||||||
|
|
||||||
|
def _on_disconnect(self, client, userdata, rc):
|
||||||
|
print("[HEARTBEAT] MQTT desconectado!", rc)
|
||||||
|
self._mqtt_conectado = False
|
||||||
|
self.sub = False
|
||||||
|
|
||||||
|
# se rc != 0 significa desconexão inesperada
|
||||||
|
if rc != 0:
|
||||||
|
print("[HEARTBEAT] Desconexão inesperada — pode ter perdido o link 900 MHz")
|
||||||
|
|
||||||
|
# Marca como "sem heartbeat" instantâneo (opcional)
|
||||||
|
# Aqui podemos forçar atraso grande
|
||||||
|
self.last_heartbeat_ts = 0
|
||||||
|
|
||||||
|
def _on_message(self, client, userdata, msg):
|
||||||
|
topic = msg.topic
|
||||||
|
if topic == f"agrobot/v1/rover/{self.rover_id}/heartbeat":
|
||||||
|
self.last_heartbeat_ts = time.time()
|
||||||
|
#print("[HEARTBEAT] recebido")
|
||||||
|
|
||||||
|
def _reset_janelas(self):
|
||||||
|
self.rtts.clear()
|
||||||
|
self.timeouts.clear()
|
||||||
|
self.loses.clear()
|
||||||
|
self.nic_error_rates.clear()
|
||||||
|
self.bw_rx_mbps.clear()
|
||||||
|
self.bw_tx_mbps.clear()
|
||||||
|
self.last_nic_counters = None
|
||||||
|
self.last_bw_counters = None
|
||||||
|
self.last_bw_ts = None
|
||||||
|
|
||||||
|
def _status_por_score(self, score: float) -> StatusModulo:
|
||||||
|
if score >= 80:
|
||||||
|
return StatusModulo.OPERANTE
|
||||||
|
elif score >= 50:
|
||||||
|
return StatusModulo.ALERTA
|
||||||
|
else:
|
||||||
|
return StatusModulo.FALHA
|
||||||
|
|
||||||
|
def get_base_ip(self):
|
||||||
|
return ContextoGlobalRedis.get_equipamento().get("base_ip")
|
||||||
|
|
||||||
|
def descobrir_nic_para_base(self) -> str | None:
|
||||||
|
try:
|
||||||
|
base_ip = self.get_base_ip()
|
||||||
|
if base_ip is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Abre socket UDP só para descobrir o IP local usado na rota
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.connect((base_ip, 80)) # porta não importa
|
||||||
|
local_ip = s.getsockname()[0]
|
||||||
|
s.close()
|
||||||
|
except Exception as e:
|
||||||
|
print("[Ponte IP] Erro ao descobrir IP local da rota:", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Agora, com o local_ip, encontrar qual NIC possui esse IP
|
||||||
|
addrs = psutil.net_if_addrs()
|
||||||
|
|
||||||
|
for nic_name, addr_list in addrs.items():
|
||||||
|
for addr in addr_list:
|
||||||
|
if addr.family == socket.AF_INET and addr.address == local_ip:
|
||||||
|
return nic_name
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _update_nic_bandwidth(self):
|
||||||
|
if self.nic_name is None:
|
||||||
|
self.nic_name = self.descobrir_nic_para_base()
|
||||||
|
if self.nic_name is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
counters = psutil.net_io_counters(pernic=True).get(self.nic_name)
|
||||||
|
if not counters:
|
||||||
|
return
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if self.last_bw_counters is not None and self.last_bw_ts is not None:
|
||||||
|
dt = now - self.last_bw_ts
|
||||||
|
if dt > 0:
|
||||||
|
delta_rx = counters.bytes_recv - self.last_bw_counters.bytes_recv
|
||||||
|
delta_tx = counters.bytes_sent - self.last_bw_counters.bytes_sent
|
||||||
|
|
||||||
|
# bytes → bits → Mbit/s
|
||||||
|
rx_mbps = (delta_rx * 8.0) / (dt * 1_000_000.0)
|
||||||
|
tx_mbps = (delta_tx * 8.0) / (dt * 1_000_000.0)
|
||||||
|
|
||||||
|
self.bw_rx_mbps.append(rx_mbps)
|
||||||
|
self.bw_tx_mbps.append(tx_mbps)
|
||||||
|
|
||||||
|
self.last_bw_counters = counters
|
||||||
|
self.last_bw_ts = now
|
||||||
|
|
||||||
|
def _ping_once(self):
|
||||||
|
"""
|
||||||
|
Retorna RTT em ms se ok, ou None se timeout.
|
||||||
|
Aqui exemplo pra Windows usando 'ping -n 1'.
|
||||||
|
Ajuste pro teu ambiente se precisar.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
base_ip = self.get_base_ip()
|
||||||
|
if base_ip is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# timeout de 1000 ms
|
||||||
|
proc = subprocess.run(
|
||||||
|
["ping", "-n", "5", "-w", "500", base_ip],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
#print("timeout")
|
||||||
|
return None, 100
|
||||||
|
|
||||||
|
#print(proc.stdout)
|
||||||
|
|
||||||
|
perda = None
|
||||||
|
media_tempo = None
|
||||||
|
for line in proc.stdout.splitlines():
|
||||||
|
line = line.lower()
|
||||||
|
if ("%" in line and perda is None):
|
||||||
|
perda = line.split('(')[1].split('%')[0]
|
||||||
|
perda = float(perda) if perda.isdigit() else None
|
||||||
|
elif (" = " in line and "ms" in line and media_tempo is None):
|
||||||
|
media_tempo = line.split(' = ')[3].split('ms')[0]
|
||||||
|
media_tempo = float(media_tempo) if media_tempo.isdigit() else None
|
||||||
|
if perda is not None and media_tempo is not None:
|
||||||
|
break
|
||||||
|
return media_tempo, perda
|
||||||
|
except Exception:
|
||||||
|
print("erro timeout")
|
||||||
|
return None, 100
|
||||||
|
|
||||||
|
def _update_nic_errors(self):
|
||||||
|
if self.nic_name is None:
|
||||||
|
self.nic_name = self.descobrir_nic_para_base()
|
||||||
|
if self.nic_name is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
counters = psutil.net_io_counters(pernic=True).get(self.nic_name)
|
||||||
|
if not counters:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.last_nic_counters is not None:
|
||||||
|
delta_err = (counters.errin - self.last_nic_counters.errin) + \
|
||||||
|
(counters.errout - self.last_nic_counters.errout)
|
||||||
|
delta_pkts = (counters.packets_sent - self.last_nic_counters.packets_sent) + \
|
||||||
|
(counters.packets_recv - self.last_nic_counters.packets_recv)
|
||||||
|
|
||||||
|
if delta_pkts > 0:
|
||||||
|
# erros por mil pacotes
|
||||||
|
rate = (delta_err / delta_pkts) * 1000.0
|
||||||
|
self.nic_error_rates.append(rate)
|
||||||
|
|
||||||
|
self.last_nic_counters = counters
|
||||||
|
|
||||||
|
def _metric_scores(self):
|
||||||
|
# valores brutos (default)
|
||||||
|
avg_rtt = None
|
||||||
|
loss_pct = 100.0
|
||||||
|
jitter = 0.0
|
||||||
|
err_rate = 0.0
|
||||||
|
atraso = time.time() - self.last_heartbeat_ts
|
||||||
|
|
||||||
|
# LATÊNCIA
|
||||||
|
if self.rtts:
|
||||||
|
avg_rtt = statistics.mean(self.rtts)
|
||||||
|
if avg_rtt <= 30:
|
||||||
|
latency_score = 100
|
||||||
|
elif avg_rtt <= 50:
|
||||||
|
latency_score = 80
|
||||||
|
elif avg_rtt <= 100:
|
||||||
|
latency_score = 50
|
||||||
|
elif avg_rtt <= 200:
|
||||||
|
latency_score = 20
|
||||||
|
else:
|
||||||
|
latency_score = 0
|
||||||
|
else:
|
||||||
|
latency_score = 0
|
||||||
|
|
||||||
|
# TIMEOUT
|
||||||
|
if self.timeouts:
|
||||||
|
timeout_pct = (sum(self.timeouts) / len(self.timeouts)) * 100.0
|
||||||
|
else:
|
||||||
|
timeout_pct = 100.0
|
||||||
|
|
||||||
|
if timeout_pct <= 5:
|
||||||
|
timeout_score = 100
|
||||||
|
elif timeout_pct <= 10:
|
||||||
|
timeout_score = 80
|
||||||
|
elif timeout_pct <= 15:
|
||||||
|
timeout_score = 50
|
||||||
|
elif timeout_pct <= 30:
|
||||||
|
timeout_score = 20
|
||||||
|
else:
|
||||||
|
timeout_score = 0
|
||||||
|
|
||||||
|
# LOSS
|
||||||
|
if self.loses:
|
||||||
|
loss_pct = (sum(self.loses) / len(self.loses))
|
||||||
|
else:
|
||||||
|
loss_pct = 100.0
|
||||||
|
|
||||||
|
if loss_pct <= 2:
|
||||||
|
loss_score = 100
|
||||||
|
elif loss_pct <= 5:
|
||||||
|
loss_score = 80
|
||||||
|
elif loss_pct <= 8:
|
||||||
|
loss_score = 50
|
||||||
|
elif loss_pct <= 15:
|
||||||
|
loss_score = 20
|
||||||
|
else:
|
||||||
|
loss_score = 0
|
||||||
|
|
||||||
|
# JITTER
|
||||||
|
if len(self.rtts) > 1:
|
||||||
|
jitter = statistics.pstdev(self.rtts)
|
||||||
|
if jitter <= 10:
|
||||||
|
jitter_score = 100
|
||||||
|
elif jitter <= 20:
|
||||||
|
jitter_score = 70
|
||||||
|
elif jitter <= 40:
|
||||||
|
jitter_score = 30
|
||||||
|
else:
|
||||||
|
jitter_score = 0
|
||||||
|
else:
|
||||||
|
jitter = 0.0
|
||||||
|
# Sem dados suficientes, melhor considerar "desconhecido" => score neutro/baixo
|
||||||
|
jitter_score = 0
|
||||||
|
|
||||||
|
# NIC ERRORS
|
||||||
|
if self.nic_error_rates:
|
||||||
|
err_rate = statistics.mean(self.nic_error_rates)
|
||||||
|
else:
|
||||||
|
err_rate = 0.0
|
||||||
|
|
||||||
|
if err_rate <= 0.1:
|
||||||
|
nic_score = 100
|
||||||
|
elif err_rate <= 1:
|
||||||
|
nic_score = 70
|
||||||
|
elif err_rate <= 5:
|
||||||
|
nic_score = 40
|
||||||
|
else:
|
||||||
|
nic_score = 0
|
||||||
|
|
||||||
|
# HEARTBEAT (score usando "atraso")
|
||||||
|
if atraso <= 1.5:
|
||||||
|
hb_score = 100
|
||||||
|
elif atraso <= 2:
|
||||||
|
hb_score = 80
|
||||||
|
elif atraso <= 5:
|
||||||
|
hb_score = 30
|
||||||
|
else:
|
||||||
|
hb_score = 0
|
||||||
|
|
||||||
|
# retorna scores + brutos
|
||||||
|
return (
|
||||||
|
latency_score, timeout_score, loss_score, jitter_score, nic_score, hb_score,
|
||||||
|
avg_rtt, timeout_pct, loss_pct, jitter, err_rate, atraso
|
||||||
|
)
|
||||||
|
|
||||||
|
def atualizar_saude_interno(self):
|
||||||
|
try:
|
||||||
|
self._start_mqtt_heartbeat()
|
||||||
|
|
||||||
|
SAUDE_MIN_ALERTA = 80
|
||||||
|
|
||||||
|
motivos = []
|
||||||
|
condicoes = []
|
||||||
|
saude_individual = []
|
||||||
|
conectado = self.get_base_ip() is not None and self.nic_name is not None and self._mqtt_conectado
|
||||||
|
|
||||||
|
(rtt, loss) = self._ping_once()
|
||||||
|
#print(f"rtt: {rtt}, loss: {loss}")
|
||||||
|
self.timeouts.append(loss == 100)
|
||||||
|
if rtt is not None:
|
||||||
|
self.rtts.append(rtt)
|
||||||
|
if loss is not None:
|
||||||
|
self.loses.append(loss)
|
||||||
|
|
||||||
|
self._update_nic_errors()
|
||||||
|
self._update_nic_bandwidth()
|
||||||
|
|
||||||
|
(ls, tmout, los, js, ns, hbs, avg_rtt, tmout_pct, loss_pct, jitter, err_rate, atraso) = self._metric_scores()
|
||||||
|
|
||||||
|
health = (
|
||||||
|
0.25 * ls +
|
||||||
|
0.10 * tmout +
|
||||||
|
0.30 * los +
|
||||||
|
0.15 * js +
|
||||||
|
0.10 * ns +
|
||||||
|
0.10 * hbs
|
||||||
|
)
|
||||||
|
saude = round(health, 1)
|
||||||
|
|
||||||
|
# -------- SAÚDE INDIVIDUAL / CONDIÇÕES / MOTIVOS --------
|
||||||
|
|
||||||
|
# 1) Latência
|
||||||
|
cond_lat = []
|
||||||
|
if ls < SAUDE_MIN_ALERTA:
|
||||||
|
severidade = int(max(0, 100 - ls))
|
||||||
|
c = {
|
||||||
|
"label": "Latência ICMP",
|
||||||
|
"valor": avg_rtt if avg_rtt is not None else -1,
|
||||||
|
"severidade": severidade,
|
||||||
|
"descricao": "Latência alta na comunicação com a base",
|
||||||
|
"acoes": [
|
||||||
|
"Reduzir taxa de envio de telemetria",
|
||||||
|
"Verificar alinhamento das antenas 900 MHz",
|
||||||
|
"Verificar interferência ou obstáculos entre base e rover"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
condicoes.append(c)
|
||||||
|
cond_lat.append(c)
|
||||||
|
motivos.append(
|
||||||
|
f"Latência ICMP elevada "
|
||||||
|
f"({avg_rtt:.0f} ms, score {ls:.0f})."
|
||||||
|
if avg_rtt is not None else
|
||||||
|
f"Latência ICMP comprometida (score {ls:.0f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": "ip_latency",
|
||||||
|
"label": "Latência ICMP",
|
||||||
|
"status": self._status_por_score(ls).value,
|
||||||
|
"saude": round(ls, 1),
|
||||||
|
"motivos": [m for m in motivos if "Latência" in m],
|
||||||
|
"condicoes_operacionais": cond_lat,
|
||||||
|
"em_uso": True,
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2) Timeouts
|
||||||
|
cond_timeout = []
|
||||||
|
if tmout < SAUDE_MIN_ALERTA:
|
||||||
|
severidade = int(max(0, 100 - tmout))
|
||||||
|
c = {
|
||||||
|
"label": "Timeout",
|
||||||
|
"valor": round(tmout_pct, 2),
|
||||||
|
"severidade": severidade,
|
||||||
|
"descricao": "Timeout na comunicação ICMP",
|
||||||
|
"acoes": [
|
||||||
|
"Checar conectores e cabo da ponte 900 MHz",
|
||||||
|
"Verificar nível de ruído / interferência no enlace",
|
||||||
|
"Reduzir banda utilizada (streaming de vídeo, logs, etc.)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
condicoes.append(c)
|
||||||
|
cond_timeout.append(c)
|
||||||
|
motivos.append(
|
||||||
|
f"Timeout elevado ({tmout_pct:.1f}%, score {tmout:.0f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": "ip_timeout",
|
||||||
|
"label": "Timeout",
|
||||||
|
"status": self._status_por_score(tmout).value,
|
||||||
|
"saude": round(tmout, 1),
|
||||||
|
"motivos": [m for m in motivos if "Timeout" in m],
|
||||||
|
"condicoes_operacionais": cond_timeout,
|
||||||
|
"em_uso": True,
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2) Perda de pacotes
|
||||||
|
cond_loss = []
|
||||||
|
if los < SAUDE_MIN_ALERTA:
|
||||||
|
severidade = int(max(0, 100 - los))
|
||||||
|
c = {
|
||||||
|
"label": "Perda de Pacotes",
|
||||||
|
"valor": round(loss_pct, 2),
|
||||||
|
"severidade": severidade,
|
||||||
|
"descricao": "Perda de pacotes na comunicação ICMP",
|
||||||
|
"acoes": [
|
||||||
|
"Checar conectores e cabo da ponte 900 MHz",
|
||||||
|
"Verificar nível de ruído / interferência no enlace",
|
||||||
|
"Reduzir banda utilizada (streaming de vídeo, logs, etc.)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
condicoes.append(c)
|
||||||
|
cond_loss.append(c)
|
||||||
|
motivos.append(
|
||||||
|
f"Perda de pacotes elevada ({loss_pct:.1f}%, score {los:.0f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": "ip_loss",
|
||||||
|
"label": "Perda de pacotes",
|
||||||
|
"status": self._status_por_score(los).value,
|
||||||
|
"saude": round(los, 1),
|
||||||
|
"motivos": [m for m in motivos if "Perda de pacotes" in m],
|
||||||
|
"condicoes_operacionais": cond_loss,
|
||||||
|
"em_uso": True,
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3) Jitter
|
||||||
|
cond_jit = []
|
||||||
|
if js < SAUDE_MIN_ALERTA:
|
||||||
|
severidade = int(max(0, 100 - js))
|
||||||
|
c = {
|
||||||
|
"label": "Jitter (variação de latência)",
|
||||||
|
"valor": round(jitter, 2),
|
||||||
|
"severidade": severidade,
|
||||||
|
"descricao": "Variação de latência acima do ideal",
|
||||||
|
"acoes": [
|
||||||
|
"Evitar tráfego pesado na mesma rede da ponte",
|
||||||
|
"Reduzir taxa de envio de mensagens de controle",
|
||||||
|
"Verificar qualidade do enlace 900 MHz"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
condicoes.append(c)
|
||||||
|
cond_jit.append(c)
|
||||||
|
motivos.append(
|
||||||
|
f"Jitter elevado ({jitter:.1f} ms, score {js:.0f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": "ip_jitter",
|
||||||
|
"label": "Jitter da conexão",
|
||||||
|
"status": self._status_por_score(js).value,
|
||||||
|
"saude": round(js, 1),
|
||||||
|
"motivos": [m for m in motivos if "Jitter" in m],
|
||||||
|
"condicoes_operacionais": cond_jit,
|
||||||
|
"em_uso": True,
|
||||||
|
})
|
||||||
|
|
||||||
|
# 4) Erros na NIC
|
||||||
|
cond_nic = []
|
||||||
|
if ns < SAUDE_MIN_ALERTA:
|
||||||
|
severidade = int(max(0, 100 - ns))
|
||||||
|
c = {
|
||||||
|
"label": "Erros na interface de rede",
|
||||||
|
"valor": round(err_rate, 3),
|
||||||
|
"severidade": severidade,
|
||||||
|
"descricao": "Erros na placa de rede ligada à ponte 900 MHz",
|
||||||
|
"acoes": [
|
||||||
|
"Verificar cabo de rede e conectores",
|
||||||
|
"Checar se há colisões ou problemas físicos no link",
|
||||||
|
"Substituir cabo ou porta de switch se necessário"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
condicoes.append(c)
|
||||||
|
cond_nic.append(c)
|
||||||
|
motivos.append(
|
||||||
|
f"Erros na NIC acima do normal ({err_rate:.3f} erros/mil pacotes, score {ns:.0f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": "ip_nic_errors",
|
||||||
|
"label": "Erros da interface de rede",
|
||||||
|
"status": self._status_por_score(ns).value,
|
||||||
|
"saude": round(ns, 1),
|
||||||
|
"motivos": [m for m in motivos if "NIC" in m or "interface de rede" in m],
|
||||||
|
"condicoes_operacionais": cond_nic,
|
||||||
|
"em_uso": True,
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5) Heartbeat
|
||||||
|
cond_hb = []
|
||||||
|
if hbs < SAUDE_MIN_ALERTA:
|
||||||
|
severidade = int(max(0, 100 - hbs))
|
||||||
|
c = {
|
||||||
|
"label": "Atraso de heartbeat",
|
||||||
|
"valor": round(atraso, 2),
|
||||||
|
"severidade": severidade,
|
||||||
|
"descricao": "Atraso na recepção de heartbeat do rover",
|
||||||
|
"acoes": [
|
||||||
|
"Verificar estado do serviço de telemetria no rover",
|
||||||
|
"Checar fila de mensagens MQTT/Redis",
|
||||||
|
"Garantir prioridade para mensagens de controle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
condicoes.append(c)
|
||||||
|
cond_hb.append(c)
|
||||||
|
motivos.append(
|
||||||
|
f"Heartbeat atrasado ({atraso:.1f} s sem atualização, score {hbs:.0f})."
|
||||||
|
)
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": "ip_heartbeat",
|
||||||
|
"label": "Heartbeat do rover",
|
||||||
|
"status": self._status_por_score(hbs).value,
|
||||||
|
"saude": round(hbs, 1),
|
||||||
|
"motivos": [m for m in motivos if "Heartbeat" in m],
|
||||||
|
"condicoes_operacionais": cond_hb,
|
||||||
|
"em_uso": True,
|
||||||
|
})
|
||||||
|
|
||||||
|
rx_mbps = statistics.mean(self.bw_rx_mbps) if self.bw_rx_mbps else 0.0
|
||||||
|
tx_mbps = statistics.mean(self.bw_tx_mbps) if self.bw_tx_mbps else 0.0
|
||||||
|
bw_total = rx_mbps + tx_mbps
|
||||||
|
|
||||||
|
# -------- STATUS GERAL DO MÓDULO --------
|
||||||
|
status = StatusModulo.OPERANTE
|
||||||
|
if not conectado:
|
||||||
|
status = StatusModulo.DESCONECTADO
|
||||||
|
elif saude <= 0:
|
||||||
|
status = StatusModulo.FALHA
|
||||||
|
elif saude < SAUDE_MIN_ALERTA:
|
||||||
|
status = StatusModulo.ALERTA
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"conectado": conectado,
|
||||||
|
"status": status.value,
|
||||||
|
"saude": saude,
|
||||||
|
"motivos": motivos,
|
||||||
|
"saude_individual": saude_individual,
|
||||||
|
"condicoes_operacionais": condicoes,
|
||||||
|
"detalhes": {
|
||||||
|
"ls": ls,
|
||||||
|
"tmout": tmout,
|
||||||
|
"los": los,
|
||||||
|
"js": js,
|
||||||
|
"ns": ns,
|
||||||
|
"hbs": hbs,
|
||||||
|
"avg_rtt": avg_rtt,
|
||||||
|
"tmout_pct": tmout_pct,
|
||||||
|
"loss_pct": loss_pct,
|
||||||
|
"jitter": jitter,
|
||||||
|
"err_rate": err_rate,
|
||||||
|
"atraso_hb": atraso,
|
||||||
|
"rx_mbps": rx_mbps,
|
||||||
|
"tx_mbps": tx_mbps,
|
||||||
|
"bw_total_mbps": bw_total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||||
|
ContextoGlobalRedis.ModKey(self.t_code),
|
||||||
|
saude=payload
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}")
|
||||||
|
|
@ -199,8 +199,14 @@ class ContextoGlobalRedis:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def atualizar_dados_operacao(cls):
|
def atualizar_dados_operacao(cls):
|
||||||
cls._atualizar_operacao_liberada()
|
try:
|
||||||
cls._atualiza_status_operacao()
|
cls._atualizar_operacao_liberada()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar operacao liberada: {e}")
|
||||||
|
try:
|
||||||
|
cls._atualiza_status_operacao()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar status da operacao: {e}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _atualizar_operacao_liberada(cls):
|
def _atualizar_operacao_liberada(cls):
|
||||||
|
|
@ -212,7 +218,8 @@ class ContextoGlobalRedis:
|
||||||
modulos_opcionais = set(_operacao.get("modulos_opcionais", []))
|
modulos_opcionais = set(_operacao.get("modulos_opcionais", []))
|
||||||
emergencia = _operacao.get("emergencia", False)
|
emergencia = _operacao.get("emergencia", False)
|
||||||
pausa = _operacao.get("pausa", False)
|
pausa = _operacao.get("pausa", False)
|
||||||
trajetoria_liberada = _contexto.get("Trajetoria", {}).get("CorredorAtual", {}).get("liberado", False)
|
_t = _contexto.get("Trajetoria", {})
|
||||||
|
trajetoria_liberada = True if _t is None or _t == "" else _t.get("CorredorAtual", {}).get("liberado", False)
|
||||||
|
|
||||||
modulos_presentes = set()
|
modulos_presentes = set()
|
||||||
status_modulos = []
|
status_modulos = []
|
||||||
|
|
@ -518,13 +525,14 @@ class ContextoGlobalRedis:
|
||||||
|
|
||||||
if status_atual != StatusOperacao.EmAndamento:
|
if status_atual != StatusOperacao.EmAndamento:
|
||||||
cls.atualizar_ctx_dict(CtxKey.DadosOperacao, status=StatusOperacao.EmAndamento.value)
|
cls.atualizar_ctx_dict(CtxKey.DadosOperacao, status=StatusOperacao.EmAndamento.value)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar status da operacao: {e}")
|
||||||
finally:
|
finally:
|
||||||
pass
|
pass
|
||||||
#_operacao = cls.get_operacao()
|
#_operacao = cls.get_operacao()
|
||||||
#status = _operacao.get("status")
|
#status = _operacao.get("status")
|
||||||
#liberado = _operacao.get("liberado")
|
#liberado = _operacao.get("liberado")
|
||||||
#print(f"Status Atual: {status.name}, Liberado: {liberado}")
|
#print(f"Status Atual: {StatusOperacao(status).name}, Liberado: {liberado}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _iniciar_operacao(cls):
|
def _iniciar_operacao(cls):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue