Ajustes em IMU, IPB, Alertas, Top Bar, Left Bar, Controle, Redis

This commit is contained in:
Diego Freitas 2026-03-19 07:55:56 -03:00
parent 3f55ed4fca
commit a7ac637032
15 changed files with 1233 additions and 886 deletions

View File

@ -170,10 +170,17 @@ namespace AgroBase.Models
{ {
public double timestamp { get; set; } public double timestamp { get; set; }
public double roll { get; set; } public double roll { get; set; }
public double roll_seg { get; set; }
public double pitch { get; set; } public double pitch { get; set; }
public double pitch_seg { get; set; }
public double yaw { get; set; } public double yaw { get; set; }
public double yaw_fixed { get; set; } public double yaw_fixed { get; set; }
public double vel_mps { get; set; } public double vel_mps { get; set; }
public bool em_movimento { get; set; }
public double movimento_idx { get; set; }
public double rugosidade_idx { get; set; }
public double impacto_idx { get; set; }
public double estabilidade_idx { get; set; }
public double latencia { get; set; } public double latencia { get; set; }
public double frequencia { get; set; } public double frequencia { get; set; }
} }

View File

@ -2407,6 +2407,11 @@ namespace AgroBase.Models
InclinacaoFrontal = imu_iniciado ? sIMU.roll : 0, InclinacaoFrontal = imu_iniciado ? sIMU.roll : 0,
Rotacao = imu_iniciado ? sIMU.yaw : 0, Rotacao = imu_iniciado ? sIMU.yaw : 0,
RotacaoCorrigida = imu_iniciado ? sIMU.yaw_fixed : 0, RotacaoCorrigida = imu_iniciado ? sIMU.yaw_fixed : 0,
EmMovimento = imu_iniciado ? sIMU.em_movimento : false,
IndiceMovimento = imu_iniciado ? sIMU.movimento_idx : 0,
IndiceRugosidade = imu_iniciado ? sIMU.rugosidade_idx : 0,
IndiceEstabilidade = imu_iniciado ? sIMU.estabilidade_idx : 0,
IndiceImpacto = imu_iniciado ? sIMU.impacto_idx : 0,
} : new OperacaoSensoriamentoLogImuModel(); } : new OperacaoSensoriamentoLogImuModel();
var dadosAtuador = _DispAtu != null ? new OperacaoSensoriamentoLogAtuModel() var dadosAtuador = _DispAtu != null ? new OperacaoSensoriamentoLogAtuModel()
@ -2693,6 +2698,11 @@ namespace AgroBase.Models
public double InclinacaoFrontal { get; set; } public double InclinacaoFrontal { get; set; }
public double Rotacao { get; set; } public double Rotacao { get; set; }
public double RotacaoCorrigida { get; set; } public double RotacaoCorrigida { get; set; }
public bool EmMovimento { get; set; }
public double IndiceMovimento { get; set; }
public double IndiceRugosidade { get; set; }
public double IndiceEstabilidade { get; set; }
public double IndiceImpacto { get; set; }
public OperacaoSensoriamentoLogImuModel Clone() public OperacaoSensoriamentoLogImuModel Clone()
{ {
@ -2703,6 +2713,11 @@ namespace AgroBase.Models
InclinacaoFrontal = InclinacaoFrontal, InclinacaoFrontal = InclinacaoFrontal,
Rotacao = Rotacao, Rotacao = Rotacao,
RotacaoCorrigida = RotacaoCorrigida, RotacaoCorrigida = RotacaoCorrigida,
EmMovimento = EmMovimento,
IndiceMovimento = IndiceMovimento,
IndiceImpacto = IndiceImpacto,
IndiceEstabilidade = IndiceEstabilidade,
IndiceRugosidade = IndiceRugosidade,
}; };
} }
} }

View File

@ -402,6 +402,11 @@ namespace AgroBase.Models
public static void MostrarLog(string Mensagem)
{
Console.WriteLine($"[AGROBASE] {Mensagem}");
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import numpy as np import numpy as np
import time import time
import threading import threading
from collections import deque
from ahrs.filters import Madgwick from ahrs.filters import Madgwick
from scipy.spatial.transform import Rotation as R from scipy.spatial.transform import Rotation as R
from shared.contexto_global_redis import ContextoGlobalRedis from shared.contexto_global_redis import ContextoGlobalRedis
@ -11,26 +12,53 @@ class IMUCamera(ModuloDiagnosticoBase):
def __init__(self, queue=None, freq=100, angulo_inicial=26.3): def __init__(self, queue=None, freq=100, angulo_inicial=26.3):
self.freq = freq self.freq = freq
if queue is None: return if queue is None:
return
self.imu_queue = queue self.imu_queue = queue
self.filtro_imu = Madgwick(beta=0.8, frequency=freq)
self.roll_inicial = 90 - angulo_inicial # graus # beta bem mais conservador
self.filtro_imu = Madgwick(beta=0.05, frequency=freq)
self.roll_inicial = 90 - angulo_inicial
self.pitch_inicial = 0.0 self.pitch_inicial = 0.0
self.yaw_inicial = 0.0 self.yaw_inicial = 0.0
r = R.from_euler('xyz', [self.roll_inicial, self.pitch_inicial, self.yaw_inicial], degrees=True) r = R.from_euler('xyz', [self.roll_inicial, self.pitch_inicial, self.yaw_inicial], degrees=True)
q = r.as_quat() # formato: [x, y, z, w] q = r.as_quat()
self.q = np.array([q[3], q[0], q[1], q[2]]) # reordenando para [w, x, y, z] como espera o filtro self.q = np.array([q[3], q[0], q[1], q[2]], dtype=np.float64)
self.imu_em_falha = False self.imu_em_falha = False
self.tempo_erro = 0 self.tempo_erro = 0
self.v_world = np.zeros(3, dtype=np.float32) self.v_world = np.zeros(3, dtype=np.float64)
self.last_imu_ts = None
self.g = 9.80665 self.g = 9.80665
self.last_loop_ts = None
# filtros de segurança
self.roll_seg = 0.0
self.pitch_seg = 0.0
self.alpha_seg = 0.12
# mediana curta para matar espinhos
self.roll_hist = deque(maxlen=5)
self.pitch_hist = deque(maxlen=5)
self.acc_norm_hist = deque(maxlen=100) # ~1s
self.acc_z_hist = deque(maxlen=100) # ~1s
self.gyro_norm_hist = deque(maxlen=60) # ~0.6s
self.roll_seg_hist = deque(maxlen=60) # ~0.6s
self.pitch_seg_hist = deque(maxlen=60) # ~0.6s
self.impacto_hist = deque(maxlen=20) # ~0.2s para pico recente
self.em_movimento = False
self.movimento_idx = 0.0
self.rugosidade_idx = 0.0
self.impacto_idx = 0.0
self.estabilidade_idx = 100.0
self.last_redis_ts = 0.0
self.redis_period = 0.10 # 100 ms
self.ativo = True self.ativo = True
self.imu_thread = threading.Thread(target=self.imu_task_loop, daemon=True) self.imu_thread = threading.Thread(target=self.imu_task_loop, daemon=True)
self.imu_thread.start() self.imu_thread.start()
@ -42,48 +70,185 @@ class IMUCamera(ModuloDiagnosticoBase):
self.ativo = False self.ativo = False
self.mostrar_log("Task parada") self.mostrar_log("Task parada")
def _mediana_curta(self, hist, valor):
hist.append(valor)
return float(np.median(hist))
def clamp(self, valor, vmin, vmax):
return max(vmin, min(vmax, valor))
def norm_to_100(self, valor, faixa_min, faixa_max):
if faixa_max <= faixa_min:
return 0.0
x = (valor - faixa_min) / (faixa_max - faixa_min)
return self.clamp(x * 100.0, 0.0, 100.0)
def rms(self, valores):
if not valores:
return 0.0
arr = np.asarray(valores, dtype=np.float64)
return float(np.sqrt(np.mean(arr ** 2)))
def stddev(self, valores):
if not valores:
return 0.0
arr = np.asarray(valores, dtype=np.float64)
return float(np.std(arr))
def _atualizar_indices(self, velocidade_mps, gx, gy, gz, a_lin):
acc_lin_norm = float(np.linalg.norm(a_lin))
gyro_norm_deg = float(np.rad2deg(np.linalg.norm([gx, gy, gz])))
# Históricos
self.acc_norm_hist.append(acc_lin_norm)
self.acc_z_hist.append(float(a_lin[2]))
self.gyro_norm_hist.append(gyro_norm_deg)
self.roll_seg_hist.append(float(self.roll_seg))
self.pitch_seg_hist.append(float(self.pitch_seg))
self.impacto_hist.append(acc_lin_norm)
# ============================
# 1) Flag em movimento
# ============================
vel_ok = velocidade_mps > 0.05
gyro_ok = gyro_norm_deg > 3.0
acc_ok = acc_lin_norm > 0.20
self.em_movimento = bool(vel_ok or gyro_ok or acc_ok)
# ============================
# 2) Movimento idx
# ============================
vel_score = self.norm_to_100(velocidade_mps, 0.0, 1.8)
gyro_score = self.norm_to_100(gyro_norm_deg, 0.0, 40.0)
acc_score = self.norm_to_100(acc_lin_norm, 0.0, 1.5)
movimento_idx = (
0.50 * vel_score +
0.25 * gyro_score +
0.25 * acc_score
)
self.movimento_idx = round(self.clamp(movimento_idx, 0.0, 100.0), 1)
# ============================
# 3) Rugosidade idx
# ============================
rug_z = self.rms(self.acc_z_hist)
rug_total = self.stddev(self.acc_norm_hist)
rug_z_score = self.norm_to_100(rug_z, 0.02, 0.80)
rug_total_score = self.norm_to_100(rug_total, 0.01, 0.60)
rugosidade_idx = (
0.65 * rug_z_score +
0.35 * rug_total_score
)
self.rugosidade_idx = round(self.clamp(rugosidade_idx, 0.0, 100.0), 1)
# ============================
# 4) Impacto idx
# ============================
pico_impacto = max(self.impacto_hist) if self.impacto_hist else 0.0
impacto_bruto = self.norm_to_100(pico_impacto, 0.4, 4.0)
# decaimento suave
self.impacto_idx = max(impacto_bruto, self.impacto_idx * 0.85)
self.impacto_idx = round(self.clamp(self.impacto_idx, 0.0, 100.0), 1)
# ============================
# 5) Estabilidade idx
# ============================
roll_std = self.stddev(self.roll_seg_hist)
pitch_std = self.stddev(self.pitch_seg_hist)
osc_ang = float(np.sqrt(roll_std**2 + pitch_std**2))
osc_score = self.norm_to_100(osc_ang, 0.2, 8.0)
instabilidade = (
0.60 * osc_score +
0.40 * self.rugosidade_idx
)
estabilidade_idx = 100.0 - instabilidade
self.estabilidade_idx = round(self.clamp(estabilidade_idx, 0.0, 100.0), 1)
def imu_task_loop(self): def imu_task_loop(self):
t0 = time.time() t0 = time.perf_counter()
while self.ativo: while self.ativo:
latencia = 0 latencia = 0.0
try: try:
agora = time.time() agora = time.perf_counter()
t_tick = agora - t0 t_tick = agora - t0
t0 = agora t0 = agora
f_tick = 1.0 / (t_tick + 1e-9) f_tick = 1.0 / max(t_tick, 1e-6)
imuData = self.imu_queue.tryGet() imuData = self.imu_queue.tryGet()
if imuData is not None and self.imu_em_falha: if imuData is not None and self.imu_em_falha:
self.mostrar_log("Reconectado com sucesso, voltando ao modo normal") self.mostrar_log("Reconectado com sucesso, voltando ao modo normal")
self.imu_em_falha = False self.imu_em_falha = False
if imuData is not None: if imuData is not None and len(imuData.packets) > 0:
# dt estimado por pacote now = time.perf_counter()
now = time.time() if self.last_loop_ts is None:
if self.last_imu_ts is None: dt_total = 1.0 / self.freq
dt = 1.0 / max(1e-3, f_tick) # fallback
else: else:
dt = max(0.0, now - self.last_imu_ts) dt_total = max(1e-4, now - self.last_loop_ts)
self.last_imu_ts = now self.last_loop_ts = now
dt = min(dt, 0.05) # clamp anti-bursts (<=50 ms)
self.filtro_imu.Dt = float(dt) dt_total = min(dt_total, 0.05)
num_packets = max(1, len(imuData.packets))
dt_packet = max(1e-4, dt_total / num_packets)
for packet in imuData.packets: for packet in imuData.packets:
accel = packet.acceleroMeter accel = packet.acceleroMeter
gyro = packet.gyroscope gyro = packet.gyroscope
ax = accel.x ax = float(accel.x)
ay = accel.y ay = float(accel.y)
az = accel.z az = float(accel.z)
gx = np.deg2rad(gyro.x) gx = float(np.deg2rad(gyro.x))
gy = np.deg2rad(gyro.y) gy = float(np.deg2rad(gyro.y))
gz = np.deg2rad(gyro.z) gz = float(np.deg2rad(gyro.z))
a_body = np.array([ax, ay, az], dtype=np.float64)
acc_norm = np.linalg.norm(a_body)
self.filtro_imu.Dt = float(dt_packet)
# Confia no acelerômetro só quando ele parece representar gravidade
usa_acc = abs(acc_norm - self.g) < 0.20
if usa_acc:
self.q = self.filtro_imu.updateIMU(
q=self.q,
gyr=np.array([gx, gy, gz], dtype=np.float64),
acc=a_body
)
else:
# fallback: mantém última orientação integrando com gyro "na mão"
# caso sua lib não aceite acc=None
omega = np.array([gx, gy, gz], dtype=np.float64)
omega_norm = np.linalg.norm(omega)
if omega_norm > 1e-9:
theta = omega_norm * dt_packet
axis = omega / omega_norm
dq_xyz = axis * np.sin(theta / 2.0)
dq_w = np.cos(theta / 2.0)
dq = np.array([dq_w, dq_xyz[0], dq_xyz[1], dq_xyz[2]], dtype=np.float64)
w1, x1, y1, z1 = self.q
w2, x2, y2, z2 = dq
self.q = np.array([
w1*w2 - x1*x2 - y1*y2 - z1*z2,
w1*x2 + x1*w2 + y1*z2 - z1*y2,
w1*y2 - x1*z2 + y1*w2 + z1*x2,
w1*z2 + x1*y2 - y1*x2 + z1*w2
], dtype=np.float64)
self.q /= max(np.linalg.norm(self.q), 1e-9)
self.q = self.filtro_imu.updateIMU(
q=self.q,
gyr=np.array([gx, gy, gz]),
acc=np.array([ax, ay, az])
)
r = R.from_quat([self.q[1], self.q[2], self.q[3], self.q[0]]) r = R.from_quat([self.q[1], self.q[2], self.q[3], self.q[0]])
roll, pitch, yaw = r.as_euler('xyz', degrees=True) roll, pitch, yaw = r.as_euler('xyz', degrees=True)
@ -91,48 +256,67 @@ class IMUCamera(ModuloDiagnosticoBase):
pitch -= self.pitch_inicial pitch -= self.pitch_inicial
yaw -= self.yaw_inicial yaw -= self.yaw_inicial
# mata picos curtíssimos
roll = self._mediana_curta(self.roll_hist, roll)
pitch = self._mediana_curta(self.pitch_hist, pitch)
# filtro leve para segurança
self.roll_seg = (1.0 - self.alpha_seg) * self.roll_seg + self.alpha_seg * roll
self.pitch_seg = (1.0 - self.alpha_seg) * self.pitch_seg + self.alpha_seg * pitch
# velocidade linear
Rwb = r.as_matrix() Rwb = r.as_matrix()
a_world = Rwb @ a_body
a_lin = a_world - np.array([0.0, 0.0, self.g], dtype=np.float64)
# 1) aceleração no mundo dead = 0.08
a_body = np.array([ax, ay, az], dtype=np.float64)
a_world = Rwb @ a_body
# 2) remove gravidade
a_lin = a_world - np.array([0.0, 0.0, self.g], dtype=np.float64)
# 3) filtra ruído muito baixo (deadzone)
dead = 0.05 # m/s²
a_lin[np.abs(a_lin) < dead] = 0.0 a_lin[np.abs(a_lin) < dead] = 0.0
# 4) ZUPT (Zero-velocity update) se parado: parado = (
# - giros muito baixos e módulo da aceleração ~ g np.linalg.norm([gx, gy, gz]) < np.deg2rad(1.5) and
if (np.linalg.norm([gx, gy, gz]) < np.deg2rad(2.0) abs(np.linalg.norm(a_body) - self.g) < 0.10
and abs(np.linalg.norm(a_body) - self.g) < 0.12): )
# parado: puxe para zero agressivo
self.v_world *= 0.2 if parado:
else: self.v_world *= 0.1
# 5) integra velocidade else:
self.v_world += (a_lin * dt) self.v_world += (a_lin * dt_packet)
# 6) projeta no plano do chão (z=0) e computa velocidade escalar
v_planar = self.v_world.copy() v_planar = self.v_world.copy()
v_planar[2] = 0.0 v_planar[2] = 0.0
velocidade_mps = float(np.linalg.norm(v_planar)) velocidade_mps = float(np.linalg.norm(v_planar))
t1 = time.time() self._atualizar_indices(
latencia = t1 - t0 velocidade_mps=velocidade_mps,
f_exec = 1.0 / (latencia + 1e-9) gx=gx, gy=gy, gz=gz,
a_lin=a_lin
)
t1 = time.perf_counter()
latencia = t1 - agora
t_pub = time.perf_counter()
if (t_pub - self.last_redis_ts) >= self.redis_period:
self.last_redis_ts = t_pub
ContextoGlobalRedis.atualizar_ctx_dict( ContextoGlobalRedis.atualizar_ctx_dict(
ContextoGlobalRedis.ModKey(T_Code.Imu), ContextoGlobalRedis.ModKey(T_Code.Imu),
roll=round(-roll, 2), # frontal roll=round(-roll, 2),
pitch=round(pitch, 2), # lateral pitch=round(pitch, 2),
yaw=round(yaw, 2), yaw=round(yaw, 2),
roll_seg=round(-self.roll_seg, 2),
pitch_seg=round(self.pitch_seg, 2),
vel_mps=round(velocidade_mps, 3), vel_mps=round(velocidade_mps, 3),
timestamp=(t1 * 1000), em_movimento=self.em_movimento,
movimento_idx=round(self.movimento_idx, 1),
rugosidade_idx=round(self.rugosidade_idx, 1),
impacto_idx=round(self.impacto_idx, 1),
estabilidade_idx=round(self.estabilidade_idx, 1),
timestamp=(time.time() * 1000),
latencia=latencia, latencia=latencia,
frequencia=f_tick frequencia=f_tick
) )
#self.mostrar_log(f"IMU | Roll: {roll:.2f} | Pitch: {pitch:.2f} | Yaw: {yaw:.2f} | Freq: {f_tick:.2f} Hz | Latência: {latencia:.3f} s")
except Exception as e: except Exception as e:
self.mostrar_log(f"Erro no loop: {e}") self.mostrar_log(f"Erro no loop: {e}")
if not self.imu_em_falha: if not self.imu_em_falha:
@ -143,14 +327,12 @@ class IMUCamera(ModuloDiagnosticoBase):
self.ativo = False self.ativo = False
finally: finally:
if self.imu_em_falha: if self.imu_em_falha:
# Espera mais tempo pra evitar flood de erro
time.sleep(5.0) time.sleep(5.0)
else: else:
delay_corrigido = max(0, (1.0 / self.freq) - latencia) delay_corrigido = max(0.0, (1.0 / self.freq) - latencia)
time.sleep(delay_corrigido) time.sleep(delay_corrigido)
def atualizar_saude(self): def atualizar_saude(self):
#self.mostrar_log("Atualizando saude")
try: try:
modulo = ContextoGlobalRedis.get_modulo(T_Code.Imu) modulo = ContextoGlobalRedis.get_modulo(T_Code.Imu)
FREQ_BASE = self.freq FREQ_BASE = self.freq
@ -178,7 +360,6 @@ class IMUCamera(ModuloDiagnosticoBase):
if p > 0: if p > 0:
motivos.append(f"Frequência abaixo do ideal: {freq_hz:.2f} Hz (penalidade {p}%)") motivos.append(f"Frequência abaixo do ideal: {freq_hz:.2f} Hz (penalidade {p}%)")
# Penaliza se latência está alta (> 0.5s)
if latencia > 0.5: if latencia > 0.5:
p = int(min(latencia * 100, 30)) p = int(min(latencia * 100, 30))
saude -= p saude -= p

View File

@ -4,6 +4,7 @@ import time
import threading import threading
import statistics import statistics
import socket import socket
import numpy as np
import psutil import psutil
import subprocess import subprocess
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
@ -82,6 +83,8 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
self._ping_timeout_ms = 400 # recomendado: 300500 self._ping_timeout_ms = 400 # recomendado: 300500
self._ping_count = 2 # recomendado: 1 (janela já suaviza) self._ping_count = 2 # recomendado: 1 (janela já suaviza)
self._ping_min_interval = 0.6 # não precisa pingar a cada 100ms self._ping_min_interval = 0.6 # não precisa pingar a cada 100ms
self._hb_last_rx_monotonic = 0.0
self._last_ping_sample_applied_ts = 0.0
self.bw_max_mbps = 10.0 # capacidade aproximada do link (ajuste por teste) self.bw_max_mbps = 10.0 # capacidade aproximada do link (ajuste por teste)
self.bw_safe_mbps = 05.0 # alvo saudável (até aqui não penaliza) self.bw_safe_mbps = 05.0 # alvo saudável (até aqui não penaliza)
@ -366,6 +369,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
now = time.time() now = time.time()
with self._mqtt_lock: with self._mqtt_lock:
self.last_heartbeat_ts = now self.last_heartbeat_ts = now
self._hb_last_rx_monotonic = time.perf_counter()
self._heartbeat_ok = True self._heartbeat_ok = True
self._mqtt_mostra_log(f"[client={cid}] heartbeat recebido | t={now:.3f}") self._mqtt_mostra_log(f"[client={cid}] heartbeat recebido | t={now:.3f}")
@ -449,45 +453,6 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
self.last_bw_counters = counters self.last_bw_counters = counters
self.last_bw_ts = now self.last_bw_ts = now
def _ping_once_old(self, pings=5, timeout=500):
"""
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", f"{pings}", "-w", f"{timeout}", 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 _ping_once(self, base_ip: str, pings: int, timeout_ms: int): def _ping_once(self, base_ip: str, pings: int, timeout_ms: int):
""" """
Retorna (ok: bool, avg_rtt_ms: float|None, loss_pct: float) Retorna (ok: bool, avg_rtt_ms: float|None, loss_pct: float)
@ -771,24 +736,19 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
# 3) usa snapshot pra alimentar suas janelas rtts/loses/timeouts # 3) usa snapshot pra alimentar suas janelas rtts/loses/timeouts
ok, rtt, loss, ping_ts = self._get_ping_snapshot() ok, rtt, loss, ping_ts = self._get_ping_snapshot()
#print(f"ok: {ok}, rtt: {rtt}, loss: {loss}, ping_ts: {ping_ts}") #print(f"ok: {ok}, rtt: {rtt}, loss: {loss}, ping_ts: {ping_ts}")
self.timeouts_f.append(loss == 100) if ping_ts > self._last_ping_sample_applied_ts:
self.timeouts_s.append(loss == 100) self._last_ping_sample_applied_ts = ping_ts
if ok and rtt is not None: self.timeouts_f.append(loss == 100)
self.rtts_f.append(rtt) self.timeouts_s.append(loss == 100)
self.rtts_s.append(rtt)
if loss is not None: if ok and rtt is not None:
self.loses_f.append(loss) self.rtts_f.append(rtt)
self.loses_s.append(loss) self.rtts_s.append(rtt)
#(rtt, loss) = self._ping_once_old(pings=3, timeout=200) if loss is not None:
#print(f"rtt: {rtt}, loss: {loss}") self.loses_f.append(loss)
#self.timeouts.append(loss == 100) self.loses_s.append(loss)
#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_errors()
self._update_nic_bandwidth() self._update_nic_bandwidth()
@ -1118,21 +1078,21 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
risk_score = max(0.0, min(100.0, risk_score)) risk_score = max(0.0, min(100.0, risk_score))
degradado_agora = ( degradado_agora = (
loss_pct_s >= 5 or loss_pct_s >= 7 or
tmout_pct_s >= 4 or tmout_pct_s >= 6 or
atraso_s >= 4.0 or atraso_s >= 5.0 or
(loss_pct_f >= 8 and loss_pct_s >= 3) or (loss_pct_f >= 12 and loss_pct_s >= 4) or
(tmout_pct_f >= 10 and tmout_pct_s >= 3) or (tmout_pct_f >= 15 and tmout_pct_s >= 4) or
(jitter_s >= 35 and loss_pct_s >= 3) (jitter_s >= 45 and loss_pct_s >= 4)
) )
critico_agora = ( critico_agora = (
loss_pct_s >= 10 or loss_pct_s >= 15 or
tmout_pct_s >= 8 or tmout_pct_s >= 12 or
atraso_s >= 6.0 or atraso_s >= 8.0 or
(loss_pct_f >= 15 and loss_pct_s >= 5) or (loss_pct_f >= 20 and loss_pct_s >= 6) or
(tmout_pct_f >= 20 and tmout_pct_s >= 5) or (tmout_pct_f >= 25 and tmout_pct_s >= 6) or
((loss_pct_s >= 6) and (atraso_s >= 5.0)) ((loss_pct_s >= 8) and (atraso_s >= 6.0))
) )
colapso_agora = ( colapso_agora = (
@ -1216,11 +1176,12 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
# Condição 2: Cerca invisível de rede violada # Condição 2: Cerca invisível de rede violada
dist_violando_cerca = ( dist_violando_cerca = (
self.link_state == "DEGRADED" and self.link_state == "DEGRADED" and
self._cycles_degraded >= 15 and
risk_score >= 70 and
self._distancia_inicio_degradado is not None and self._distancia_inicio_degradado is not None and
distancia_base is not None and distancia_base is not None and
distancia_base >= 0 and distancia_base >= 0 and
self._max_distancia_desde_degradado > self._distancia_inicio_degradado + 2.0 and distancia_base > self._distancia_inicio_degradado + 5.0
distancia_base > self._distancia_inicio_degradado + 1.0
) )
if dist_violando_cerca: if dist_violando_cerca:
condicoes.append({ condicoes.append({
@ -1338,3 +1299,5 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
) )
except Exception as e: except Exception as e:
print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}") print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}")

View File

@ -37,7 +37,7 @@ def verifica_controle_liberado(ignorar_parada=False):
_equipamento = ContextoGlobalRedis.get_equipamento() _equipamento = ContextoGlobalRedis.get_equipamento()
ang_roll_max = _equipamento.get("angulo_roll_max", 15.0) ang_roll_max = _equipamento.get("angulo_roll_max", 15.0)
ang_pitch_max = _equipamento.get("angulo_pitch_max", 30.0) ang_pitch_max = _equipamento.get("angulo_pitch_max", 30.0)
inclinacao_perigosa = abs(_imu.get("pitch", 0.0)) > ang_pitch_max or abs(_imu.get("roll", 0.0)) > ang_roll_max inclinacao_perigosa = abs(_imu.get("pitch_seg", 0.0)) > ang_pitch_max or abs(_imu.get("roll_seg", 0.0)) > ang_roll_max
if not imu_operante: if not imu_operante:
motivos.append(f"IMU mandatório não operante: {_imu_saude.name}") motivos.append(f"IMU mandatório não operante: {_imu_saude.name}")

View File

@ -129,6 +129,27 @@ namespace OperationControl.Models
Angulo = 0, Angulo = 0,
PercentualVelocidadeSPKmh = 0, PercentualVelocidadeSPKmh = 0,
TipoMovimento = TipoMovimentoDirecional.RodasDianteiras TipoMovimento = TipoMovimentoDirecional.RodasDianteiras
},
Gnss = new AgroBase.Models.Operacoes.OperacaoParametrosDadosGNSSModel()
{
Latitude = -22.17254631649402,
Longitude = -47.395203906184044,
OrientacaoReal = 187
},
ModulosSaude = new List<AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel>()
{
new AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel()
{
modulo = T_Code.Gps,
saude = 0,
status = StatusModulo.Desconectado,
},
new AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel()
{
modulo = T_Code.Can,
saude = 70,
status = StatusModulo.Alerta,
},
} }
}; };
r.Controle = new AgroBase.Models.Operacoes.OperacaoParametrosControleModel() r.Controle = new AgroBase.Models.Operacoes.OperacaoParametrosControleModel()

View File

@ -366,7 +366,7 @@ namespace OperationControl.Models
return Variaveis.UdpChannel.SendBurstAsync(0x01, msg.ToBytes(), count: 6, intervalMs: 25, requestAck: false); return Variaveis.UdpChannel.SendBurstAsync(0x01, msg.ToBytes(), count: 6, intervalMs: 25, requestAck: false);
} }
public static void EnviarComandoReferenciamento(string mod_id = null) public static void EnviarComandoReferenciamento(string? mod_id = null)
{ {
EnviarDadosControle(SelectedRoverId, new OperacaoComandoBaseModel() EnviarDadosControle(SelectedRoverId, new OperacaoComandoBaseModel()
{ {

View File

@ -54,6 +54,10 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
LimparTeclas(); LimparTeclas();
EnviarNeutros(); EnviarNeutros();
} }
else
{
VariaveisControleOperacao.EnviarComandoIniciarUDP(VariaveisControleOperacao.RoverEmFoco.IP, true);
}
} }
} }
} }

View File

@ -750,8 +750,7 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
AddOrAppendSerie("Jitter", "ms", conexao?.detalhes?["jitter"]?.Value<double?>()); AddOrAppendSerie("Jitter", "ms", conexao?.detalhes?["jitter"]?.Value<double?>());
AddOrAppendSerie("Timeout", "%", conexao?.detalhes?["tmout_pct"]?.Value<double?>()); AddOrAppendSerie("Timeout", "%", conexao?.detalhes?["tmout_pct"]?.Value<double?>());
AddOrAppendSerie("Perda", "%", conexao?.detalhes?["loss_pct"]?.Value<double?>()); AddOrAppendSerie("Perda", "%", conexao?.detalhes?["loss_pct"]?.Value<double?>());
AddOrAppendSerie("Banda", "Mbps", conexao?.detalhes?["bw_total_mbps"]?.Value<double?>()); AddOrAppendSerie("Banda", "%", conexao?.detalhes?["bw_util_pct"]?.Value<double?>());
AddOrAppendSerie("Banda %", "%", conexao?.detalhes?["bw_util_pct"]?.Value<double?>());
break; break;
case T_Code.Gps: case T_Code.Gps:
@ -768,6 +767,14 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
AddOrAppendSerie("Corrente", "A", bat?.CorrenteInstantanea); AddOrAppendSerie("Corrente", "A", bat?.CorrenteInstantanea);
AddOrAppendSerie("Temperatura", "°C", bat?.Temperatura); AddOrAppendSerie("Temperatura", "°C", bat?.Temperatura);
break; break;
case T_Code.Imu:
var imu = rover.Imu;
AddOrAppendSerie("Movimento", "", imu?.IndiceMovimento);
AddOrAppendSerie("Estabilidade", "", imu?.IndiceEstabilidade);
AddOrAppendSerie("Rugosidade", "", imu?.IndiceRugosidade);
AddOrAppendSerie("Impacto", "", imu?.IndiceImpacto);
break;
} }
} }

View File

@ -104,6 +104,7 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
public event Action<TipoFrameCamera>? CameraErvasFrameTipoAlterado; public event Action<TipoFrameCamera>? CameraErvasFrameTipoAlterado;
public ICommand RetornoBaseCommand { get; } public ICommand RetornoBaseCommand { get; }
public ICommand ReferenciamentoCommand { get; }
public MonitoramentoViewModel() public MonitoramentoViewModel()
{ {
@ -123,6 +124,7 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
CameraErvasFrameTipoSelecionado = CameraErvasFrameTipos.FirstOrDefault(); CameraErvasFrameTipoSelecionado = CameraErvasFrameTipos.FirstOrDefault();
RetornoBaseCommand = new RelayCommand(_ => ExecutarRetornoBase(), _ => PodeExecutarRetornoBase()); RetornoBaseCommand = new RelayCommand(_ => ExecutarRetornoBase(), _ => PodeExecutarRetornoBase());
ReferenciamentoCommand = new RelayCommand(_ => ExecutarReferenciamento(), _ => PodeExecutarReferenciamento());
} }
public void AtualizarDadosCameras(CameraWorkerItemModel? snr, CameraWorkerItemModel? cam, StatusCarroMapa? status_carro, double? pct_ervas) public void AtualizarDadosCameras(CameraWorkerItemModel? snr, CameraWorkerItemModel? cam, StatusCarroMapa? status_carro, double? pct_ervas)
@ -165,6 +167,19 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
return true; return true;
} }
private void ExecutarReferenciamento()
{
if (System.Windows.MessageBox.Show("Deseja executar o referenciamento do equipamento?", "Referenciamento", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
VariaveisControleOperacao.EnviarComandoReferenciamento();
}
}
private bool PodeExecutarReferenciamento()
{
return true;
}
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)

View File

@ -209,15 +209,20 @@ namespace OperationControl.ViewModels.Views.Operacao
if (ind.status == StatusModulo.Operante) if (ind.status == StatusModulo.Operante)
continue; continue;
alertasEsperados.Add(new AlertaModel bool mandatorio = ind.em_uso;
if (mandatorio)
{ {
Rover_ID = rover.RoverId, alertasEsperados.Add(new AlertaModel
Modulo = mod.modulo, {
Mod_ID = ind.label, Rover_ID = rover.RoverId,
Severidade = DeParaStatus(ind.status, ind.em_uso), Modulo = mod.modulo,
Mensagem = string.Join(", ", ind.motivos ?? new List<string>()) Mod_ID = ind.label,
// NÃO define Timestamp aqui ainda Severidade = DeParaStatus(ind.status, mandatorio),
}); Mensagem = string.Join(", ", ind.motivos ?? new List<string>())
// NÃO define Timestamp aqui ainda
});
}
} }
} }
else else
@ -226,17 +231,20 @@ namespace OperationControl.ViewModels.Views.Operacao
continue; continue;
bool mandatorio = rover.ModulosMandatorios?.Any(x => bool mandatorio = rover.ModulosMandatorios?.Any(x =>
x.Dispositivo == mod.modulo && x.Mandatorio) ?? false; x.Dispositivo == mod.modulo && x.Utilizar) ?? false;
alertasEsperados.Add(new AlertaModel if (mandatorio)
{ {
Rover_ID = rover.RoverId, alertasEsperados.Add(new AlertaModel
Modulo = mod.modulo, {
Mod_ID = null, Rover_ID = rover.RoverId,
Severidade = DeParaStatus(mod.status, mandatorio), Modulo = mod.modulo,
Mensagem = string.Join(", ", mod.motivos ?? new List<string>()) Mod_ID = null,
// NÃO define Timestamp aqui ainda Severidade = DeParaStatus(mod.status, mandatorio),
}); Mensagem = string.Join(", ", mod.motivos ?? new List<string>() { "Motivo desconhecido" })
// NÃO define Timestamp aqui ainda
});
}
} }
} }
} }

View File

@ -8,6 +8,7 @@ using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes; using Brushes = System.Windows.Media.Brushes;
using SolidColorBrush = System.Windows.Media.SolidColorBrush; using SolidColorBrush = System.Windows.Media.SolidColorBrush;
using Color = System.Windows.Media.Color; using Color = System.Windows.Media.Color;
using AgroBase.Models.Operacoes;
namespace OperationControl.ViewModels.Views.Operacao namespace OperationControl.ViewModels.Views.Operacao
{ {
@ -191,6 +192,21 @@ namespace OperationControl.ViewModels.Views.Operacao
} }
} }
private string _distanciaBase = "--";
public string DistanciaBase
{
get => DistanciaOuTracos(_distanciaBase);
set
{
if (_distanciaBase != value)
{
_distanciaBase = value;
OnPropertyChanged(nameof(DistanciaBase));
}
}
}
public Brush CorTemperatura => public Brush CorTemperatura =>
!RoverSelecionado ? Brushes.Gray : !RoverSelecionado ? Brushes.Gray :
_temperaturaNumerica >= 55 ? Brushes.OrangeRed : _temperaturaNumerica >= 55 ? Brushes.OrangeRed :
@ -217,9 +233,19 @@ namespace OperationControl.ViewModels.Views.Operacao
!RoverSelecionado ? Brushes.Gray : !RoverSelecionado ? Brushes.Gray :
CorPorTexto(_saudeGnss); CorPorTexto(_saudeGnss);
public Brush CorDistBase =>
!RoverSelecionado ? Brushes.Gray :
_distanciaBaseNumerico <= 50 ? Brushes.LimeGreen :
_distanciaBaseNumerico <= 150 ? Brushes.Green :
_distanciaBaseNumerico <= 400 ? Brushes.YellowGreen :
_distanciaBaseNumerico <= 800 ? Brushes.Gold :
_distanciaBaseNumerico <= 1200 ? Brushes.OrangeRed :
Brushes.DarkRed;
private double _temperaturaNumerica; private double _temperaturaNumerica;
private double _bateriaNumerica; private double _bateriaNumerica;
private double _reservatorioNumerico; private double _reservatorioNumerico;
private double _distanciaBaseNumerico;
public OperacaoLeftViewModel() public OperacaoLeftViewModel()
{ {
@ -237,6 +263,7 @@ namespace OperationControl.ViewModels.Views.Operacao
_temperaturaNumerica = 0; _temperaturaNumerica = 0;
_bateriaNumerica = 0; _bateriaNumerica = 0;
_reservatorioNumerico = 0; _reservatorioNumerico = 0;
_distanciaBaseNumerico = 0;
Velocidade = "--"; Velocidade = "--";
AnguloDirecional = "--"; AnguloDirecional = "--";
@ -246,31 +273,35 @@ namespace OperationControl.ViewModels.Views.Operacao
Reservatorio = "--"; Reservatorio = "--";
SaudeRede = "--"; SaudeRede = "--";
SaudeGnss = "--"; SaudeGnss = "--";
DistanciaBase = "--";
OnPropertyChanged(nameof(CorTemperatura)); OnPropertyChanged(nameof(CorTemperatura));
OnPropertyChanged(nameof(CorBateria)); OnPropertyChanged(nameof(CorBateria));
OnPropertyChanged(nameof(CorReservatorio)); OnPropertyChanged(nameof(CorReservatorio));
OnPropertyChanged(nameof(CorRede)); OnPropertyChanged(nameof(CorRede));
OnPropertyChanged(nameof(CorGnss)); OnPropertyChanged(nameof(CorGnss));
OnPropertyChanged(nameof(CorDistBase));
} }
public void AtualizarRover(string nomeRover, double velocidadeKmh, double anguloDirecionalGraus, TipoMovimentoDirecional tipoMovimento, double temperaturaC, double bateriaPct, double reservatorioPct, string saudeRede, string saudeGnss, bool op_iniciada) public void AtualizarRover(string descricao, OperacaoParametrosDadosControleModel? controle, double temperaturaC, double bateriaPct, double reservatorioPct, string saudeRede, string saudeGnss, double distanciaBase, bool op_iniciada)
{ {
RoverSelecionado = true; RoverSelecionado = true;
NomeRover = nomeRover; NomeRover = descricao;
_temperaturaNumerica = temperaturaC; _temperaturaNumerica = temperaturaC;
_bateriaNumerica = bateriaPct; _bateriaNumerica = bateriaPct;
_reservatorioNumerico = reservatorioPct; _reservatorioNumerico = reservatorioPct;
_distanciaBaseNumerico = distanciaBase;
Velocidade = velocidadeKmh.ToString("0.0"); Velocidade = $"{controle?.PercentualVelocidadeSPKmh ?? 0:F2}";
AnguloDirecional = anguloDirecionalGraus.ToString("0.0"); AnguloDirecional = $"{controle?.Angulo ?? 0:F1}";
TipoMovimento = tipoMovimento; TipoMovimento = controle?.TipoMovimento ?? TipoMovimentoDirecional.RodasDianteiras;
Temperatura = temperaturaC.ToString("0.0"); Temperatura = temperaturaC.ToString("0.0");
Bateria = bateriaPct.ToString("0"); Bateria = bateriaPct.ToString("0");
Reservatorio = reservatorioPct.ToString("0"); Reservatorio = reservatorioPct.ToString("0");
SaudeRede = saudeRede; SaudeRede = saudeRede;
SaudeGnss = saudeGnss; SaudeGnss = saudeGnss;
DistanciaBase = $"{distanciaBase:F2}";
OperacaoEmExecucao = op_iniciada; OperacaoEmExecucao = op_iniciada;
@ -279,6 +310,7 @@ namespace OperationControl.ViewModels.Views.Operacao
OnPropertyChanged(nameof(CorReservatorio)); OnPropertyChanged(nameof(CorReservatorio));
OnPropertyChanged(nameof(CorRede)); OnPropertyChanged(nameof(CorRede));
OnPropertyChanged(nameof(CorGnss)); OnPropertyChanged(nameof(CorGnss));
OnPropertyChanged(nameof(CorDistBase));
OnPropertyChanged(nameof(OperacaoEmExecucao)); OnPropertyChanged(nameof(OperacaoEmExecucao));
OnPropertyChanged(nameof(TextoBotaoOperacao)); OnPropertyChanged(nameof(TextoBotaoOperacao));
OnPropertyChanged(nameof(CorBotaoOperacao)); OnPropertyChanged(nameof(CorBotaoOperacao));
@ -331,6 +363,7 @@ namespace OperationControl.ViewModels.Views.Operacao
private string VelocidadeValor(string valor) => RoverSelecionado ? $"{valor} km/h" : "--"; private string VelocidadeValor(string valor) => RoverSelecionado ? $"{valor} km/h" : "--";
private string TemperaturaValor(string valor) => RoverSelecionado ? $"{valor} °C" : "--"; private string TemperaturaValor(string valor) => RoverSelecionado ? $"{valor} °C" : "--";
private string PercentualOuTracos(string valor) => RoverSelecionado ? $"{valor}%" : "--"; private string PercentualOuTracos(string valor) => RoverSelecionado ? $"{valor}%" : "--";
private string DistanciaOuTracos(string valor) => RoverSelecionado ? $"{valor} m" : "--";
private Brush CorPorTexto(string valor) private Brush CorPorTexto(string valor)
{ {

View File

@ -372,7 +372,7 @@ namespace OperationControl.ViewModels
var eventos = new List<MotivoTopBarModel>(); var eventos = new List<MotivoTopBarModel>();
double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds; double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds;
bool semComunicacao = tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo; bool semComunicacao = !AppShell.Mock && tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo;
if (semComunicacao) if (semComunicacao)
{ {
@ -394,7 +394,7 @@ namespace OperationControl.ViewModels
eventos.Add(new MotivoTopBarModel eventos.Add(new MotivoTopBarModel
{ {
Prioridade = 1100, Prioridade = 1100,
Fonte = "Núcleo Central de Processamento", Fonte = "NCP",
Titulo = "NÚCLEO CENTRAL DE PROCESSAMENTO DESCONECTADO", Titulo = "NÚCLEO CENTRAL DE PROCESSAMENTO DESCONECTADO",
Descricao = "O núcleo central de processamento foi desconectado, tentando reconectar...", Descricao = "O núcleo central de processamento foi desconectado, tentando reconectar...",
StatusVisual = StatusModulo.Falha StatusVisual = StatusModulo.Falha
@ -475,29 +475,68 @@ namespace OperationControl.ViewModels
// 5) Módulos com condições operacionais críticas // 5) Módulos com condições operacionais críticas
// ============================ // ============================
var modulosCriticos = obj.ModulosSaude? var modulosCriticos = obj.ModulosSaude?
.Where(mod => mod?.condicoes_operacionais?.Any(cond => cond?.severidade > 90) ?? false) .Where(mod =>
(mod?.condicoes_operacionais?.Any(cond => cond?.severidade > 90) ?? false) ||
(
!new List<StatusModulo>() { StatusModulo.Operante, StatusModulo.Alerta }.Contains(mod?.status ?? StatusModulo.Desconectado) &&
(rover?.ModulosMandatorios?.Any(x =>
(x?.Dispositivo ?? T_Code.Vzo) == (mod?.modulo ?? T_Code.Vzo) &&
(x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false)
) ?? false)
)
)
.ToList() ?? new List<AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel>(); .ToList() ?? new List<AgroBase.Models.Operadores.ManagerWorkerMessageResponseModulosPendentesModel>();
foreach (var modulo in modulosCriticos) foreach (var modulo in modulosCriticos)
{ {
var nomeModulo = (modulo?.modulo ?? T_Code.Vzo).ToString(); var nomeModulo = (modulo?.modulo ?? T_Code.Vzo).ToString();
var descricoesCriticas = modulo?.condicoes_operacionais? bool temCondicaoSevera = modulo?.condicoes_operacionais?.Any(cond => cond?.severidade > 90) ?? false;
.Where(cond => cond?.severidade > 90) bool moduloMandatorioNaoOperante =
.Select(cond => cond?.descricao ?? "") !new List<StatusModulo>() { StatusModulo.Operante, StatusModulo.Alerta }.Contains(modulo?.status ?? StatusModulo.Desconectado) &&
.Where(desc => !string.IsNullOrWhiteSpace(desc)) (rover?.ModulosMandatorios?.Any(x =>
.Distinct() (x?.Dispositivo ?? T_Code.Vzo) == (modulo?.modulo ?? T_Code.Vzo) &&
.ToList() ?? new List<string>(); (x?.Utilizar ?? false) &&
(x?.Mandatorio ?? false)
) ?? false);
foreach (var desc in descricoesCriticas) // Caso 1: condições operacionais severas
if (temCondicaoSevera)
{ {
var descricoesCriticas = modulo?.condicoes_operacionais?
.Where(cond => cond?.severidade > 90)
.Select(cond => cond?.descricao ?? "")
.Where(desc => !string.IsNullOrWhiteSpace(desc))
.Distinct()
.ToList() ?? new List<string>();
foreach (var desc in descricoesCriticas)
{
eventos.Add(new MotivoTopBarModel
{
Prioridade = 600,
Fonte = "ModuloCritico",
Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS",
Descricao = $"{nomeModulo}: {desc}",
StatusVisual = StatusModulo.Alerta,
Modulo = modulo?.modulo
});
}
}
// Caso 2: módulo mandatório não operante
if (moduloMandatorioNaoOperante)
{
var statusTexto = (modulo?.status ?? StatusModulo.Desconectado).ToString();
eventos.Add(new MotivoTopBarModel eventos.Add(new MotivoTopBarModel
{ {
Prioridade = 600, Prioridade = 610,
Fonte = "ModuloCritico", Fonte = "ModuloCritico",
Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS", Titulo = "MÓDULO MANDATÓRIO NÃO OPERACIONAL",
Descricao = $"{nomeModulo}: {desc}", Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}'",
StatusVisual = StatusModulo.Alerta, StatusVisual = StatusModulo.Falha,
Modulo = modulo?.modulo Modulo = modulo?.modulo
}); });
} }
@ -507,9 +546,11 @@ namespace OperationControl.ViewModels
{ {
{ T_Code.Ipb, 1 }, { T_Code.Ipb, 1 },
{ T_Code.Npc, 2 }, { T_Code.Npc, 2 },
{ T_Code.Bat, 3 }, { T_Code.Can, 3 },
{ T_Code.Atu, 4 }, { T_Code.Bat, 4 },
{ T_Code.Sen, 5 } { T_Code.Gps, 5 },
{ T_Code.Atu, 6 },
{ T_Code.Sen, 7 }
}; };
// ============================ // ============================
@ -590,7 +631,7 @@ namespace OperationControl.ViewModels
if (string.IsNullOrWhiteSpace(linha2)) if (string.IsNullOrWhiteSpace(linha2))
linha2 = "Sem detalhes adicionais."; linha2 = "Sem detalhes adicionais.";
Variaveis.MostrarLog($"LINHA 1: {linha1}"); Models.Variaveis.MostrarLog($"LINHA 1: {linha1}");
// ============================ // ============================
// 8) Atualiza UI // 8) Atualiza UI
@ -619,7 +660,7 @@ namespace OperationControl.ViewModels
public void AtualizarDadosGnss(AgroBase.Models.GPSModel dados) public void AtualizarDadosGnss(AgroBase.Models.GPSModel dados)
{ {
var bf = Variaveis.GpsService.BaseFix; var bf = Models.Variaveis.GpsService.BaseFix;
if (bf.CorrecaoEmAndamento) if (bf.CorrecaoEmAndamento)
{ {
AtualizarProgressoFixacaoBase(bf.Progresso, bf.ProgressoStr); AtualizarProgressoFixacaoBase(bf.Progresso, bf.ProgressoStr);
@ -805,7 +846,7 @@ namespace OperationControl.ViewModels
// ESQUERDA // ESQUERDA
var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Ipb); var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Ipb);
double c = conexao?.saude ?? 0; double saude_rede = conexao?.saude ?? 0;
double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value<double?>() ?? 0.0; double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value<double?>() ?? 0.0;
double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double?>() ?? 0.0; double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double?>() ?? 0.0;
double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double?>() ?? 0.0; double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double?>() ?? 0.0;
@ -814,14 +855,13 @@ namespace OperationControl.ViewModels
_viewOperacaoLeft?._vm?.AtualizarRover( _viewOperacaoLeft?._vm?.AtualizarRover(
rover?.Descricao ?? "", rover?.Descricao ?? "",
obj.Controle?.PercentualVelocidadeSPKmh ?? 0, obj.Controle,
obj.Controle?.Angulo ?? 0,
obj.Controle?.TipoMovimento ?? TipoMovimentoDirecional.Diagnostico,
obj.Refrigeracao?.Temperatura ?? 0, obj.Refrigeracao?.Temperatura ?? 0,
obj.Bateria?.PercentualBateria ?? 0, obj.Bateria?.PercentualBateria ?? 0,
obj.Atuador?.PercentualReservatorio ?? 0, obj.Atuador?.PercentualReservatorio ?? 0,
$"{c}%", $"{saude_rede}%",
$"{obj.Gnss?.QualidadeFix ?? TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm", $"{obj.Gnss?.QualidadeFix ?? TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm",
AgroBase.Models.GPSUtils.DistanciaEntrePontos(new AgroBase.Models.GPSModel() { Latitude = obj.Gnss?.Latitude ?? 0, Longitude = obj.Gnss?.Longitude ?? 0 }, Models.Variaveis.GpsService.UltimaLeitura),
obj.Operacao?.Iniciada ?? false obj.Operacao?.Iniciada ?? false
); );