Ajustes em IMU, IPB, Alertas, Top Bar, Left Bar, Controle, Redis
This commit is contained in:
parent
3f55ed4fca
commit
a7ac637032
|
|
@ -170,10 +170,17 @@ namespace AgroBase.Models
|
|||
{
|
||||
public double timestamp { get; set; }
|
||||
public double roll { get; set; }
|
||||
public double roll_seg { get; set; }
|
||||
public double pitch { get; set; }
|
||||
public double pitch_seg { get; set; }
|
||||
public double yaw { get; set; }
|
||||
public double yaw_fixed { 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 frequencia { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2407,6 +2407,11 @@ namespace AgroBase.Models
|
|||
InclinacaoFrontal = imu_iniciado ? sIMU.roll : 0,
|
||||
Rotacao = imu_iniciado ? sIMU.yaw : 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();
|
||||
|
||||
var dadosAtuador = _DispAtu != null ? new OperacaoSensoriamentoLogAtuModel()
|
||||
|
|
@ -2693,6 +2698,11 @@ namespace AgroBase.Models
|
|||
public double InclinacaoFrontal { get; set; }
|
||||
public double Rotacao { 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()
|
||||
{
|
||||
|
|
@ -2703,6 +2713,11 @@ namespace AgroBase.Models
|
|||
InclinacaoFrontal = InclinacaoFrontal,
|
||||
Rotacao = Rotacao,
|
||||
RotacaoCorrigida = RotacaoCorrigida,
|
||||
EmMovimento = EmMovimento,
|
||||
IndiceMovimento = IndiceMovimento,
|
||||
IndiceImpacto = IndiceImpacto,
|
||||
IndiceEstabilidade = IndiceEstabilidade,
|
||||
IndiceRugosidade = IndiceRugosidade,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -1,6 +1,7 @@
|
|||
import numpy as np
|
||||
import time
|
||||
import threading
|
||||
from collections import deque
|
||||
from ahrs.filters import Madgwick
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
from shared.contexto_global_redis import ContextoGlobalRedis
|
||||
|
|
@ -10,27 +11,54 @@ from health_worker.modulos.base import ModuloDiagnosticoBase
|
|||
class IMUCamera(ModuloDiagnosticoBase):
|
||||
def __init__(self, queue=None, freq=100, angulo_inicial=26.3):
|
||||
self.freq = freq
|
||||
|
||||
if queue is None: return
|
||||
|
||||
if queue is None:
|
||||
return
|
||||
|
||||
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.yaw_inicial = 0.0
|
||||
|
||||
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]
|
||||
self.q = np.array([q[3], q[0], q[1], q[2]]) # reordenando para [w, x, y, z] como espera o filtro
|
||||
q = r.as_quat()
|
||||
self.q = np.array([q[3], q[0], q[1], q[2]], dtype=np.float64)
|
||||
|
||||
self.imu_em_falha = False
|
||||
self.tempo_erro = 0
|
||||
|
||||
self.v_world = np.zeros(3, dtype=np.float32)
|
||||
self.last_imu_ts = None
|
||||
self.v_world = np.zeros(3, dtype=np.float64)
|
||||
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.imu_thread = threading.Thread(target=self.imu_task_loop, daemon=True)
|
||||
self.imu_thread.start()
|
||||
|
|
@ -42,48 +70,185 @@ class IMUCamera(ModuloDiagnosticoBase):
|
|||
self.ativo = False
|
||||
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):
|
||||
t0 = time.time()
|
||||
t0 = time.perf_counter()
|
||||
|
||||
while self.ativo:
|
||||
latencia = 0
|
||||
latencia = 0.0
|
||||
try:
|
||||
agora = time.time()
|
||||
agora = time.perf_counter()
|
||||
t_tick = agora - t0
|
||||
t0 = agora
|
||||
f_tick = 1.0 / (t_tick + 1e-9)
|
||||
f_tick = 1.0 / max(t_tick, 1e-6)
|
||||
|
||||
imuData = self.imu_queue.tryGet()
|
||||
|
||||
if imuData is not None and self.imu_em_falha:
|
||||
self.mostrar_log("Reconectado com sucesso, voltando ao modo normal")
|
||||
self.imu_em_falha = False
|
||||
|
||||
if imuData is not None:
|
||||
# dt estimado por pacote
|
||||
now = time.time()
|
||||
if self.last_imu_ts is None:
|
||||
dt = 1.0 / max(1e-3, f_tick) # fallback
|
||||
if imuData is not None and len(imuData.packets) > 0:
|
||||
now = time.perf_counter()
|
||||
if self.last_loop_ts is None:
|
||||
dt_total = 1.0 / self.freq
|
||||
else:
|
||||
dt = max(0.0, now - self.last_imu_ts)
|
||||
self.last_imu_ts = now
|
||||
dt = min(dt, 0.05) # clamp anti-bursts (<=50 ms)
|
||||
self.filtro_imu.Dt = float(dt)
|
||||
dt_total = max(1e-4, now - self.last_loop_ts)
|
||||
self.last_loop_ts = now
|
||||
|
||||
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:
|
||||
accel = packet.acceleroMeter
|
||||
gyro = packet.gyroscope
|
||||
|
||||
ax = accel.x
|
||||
ay = accel.y
|
||||
az = accel.z
|
||||
ax = float(accel.x)
|
||||
ay = float(accel.y)
|
||||
az = float(accel.z)
|
||||
|
||||
gx = np.deg2rad(gyro.x)
|
||||
gy = np.deg2rad(gyro.y)
|
||||
gz = np.deg2rad(gyro.z)
|
||||
gx = float(np.deg2rad(gyro.x))
|
||||
gy = float(np.deg2rad(gyro.y))
|
||||
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]])
|
||||
roll, pitch, yaw = r.as_euler('xyz', degrees=True)
|
||||
|
||||
|
|
@ -91,48 +256,67 @@ class IMUCamera(ModuloDiagnosticoBase):
|
|||
pitch -= self.pitch_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()
|
||||
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
|
||||
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²
|
||||
dead = 0.08
|
||||
a_lin[np.abs(a_lin) < dead] = 0.0
|
||||
|
||||
# 4) ZUPT (Zero-velocity update) se parado:
|
||||
# - giros muito baixos e módulo da aceleração ~ g
|
||||
if (np.linalg.norm([gx, gy, gz]) < np.deg2rad(2.0)
|
||||
and abs(np.linalg.norm(a_body) - self.g) < 0.12):
|
||||
# parado: puxe para zero agressivo
|
||||
self.v_world *= 0.2
|
||||
else:
|
||||
# 5) integra velocidade
|
||||
self.v_world += (a_lin * dt)
|
||||
parado = (
|
||||
np.linalg.norm([gx, gy, gz]) < np.deg2rad(1.5) and
|
||||
abs(np.linalg.norm(a_body) - self.g) < 0.10
|
||||
)
|
||||
|
||||
if parado:
|
||||
self.v_world *= 0.1
|
||||
else:
|
||||
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[2] = 0.0
|
||||
velocidade_mps = float(np.linalg.norm(v_planar))
|
||||
|
||||
t1 = time.time()
|
||||
latencia = t1 - t0
|
||||
f_exec = 1.0 / (latencia + 1e-9)
|
||||
self._atualizar_indices(
|
||||
velocidade_mps=velocidade_mps,
|
||||
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.ModKey(T_Code.Imu),
|
||||
roll=round(-roll, 2), # frontal
|
||||
pitch=round(pitch, 2), # lateral
|
||||
ContextoGlobalRedis.ModKey(T_Code.Imu),
|
||||
roll=round(-roll, 2),
|
||||
pitch=round(pitch, 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),
|
||||
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,
|
||||
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:
|
||||
self.mostrar_log(f"Erro no loop: {e}")
|
||||
if not self.imu_em_falha:
|
||||
|
|
@ -143,14 +327,12 @@ class IMUCamera(ModuloDiagnosticoBase):
|
|||
self.ativo = False
|
||||
finally:
|
||||
if self.imu_em_falha:
|
||||
# Espera mais tempo pra evitar flood de erro
|
||||
time.sleep(5.0)
|
||||
else:
|
||||
delay_corrigido = max(0, (1.0 / self.freq) - latencia)
|
||||
delay_corrigido = max(0.0, (1.0 / self.freq) - latencia)
|
||||
time.sleep(delay_corrigido)
|
||||
|
||||
def atualizar_saude(self):
|
||||
#self.mostrar_log("Atualizando saude")
|
||||
try:
|
||||
modulo = ContextoGlobalRedis.get_modulo(T_Code.Imu)
|
||||
FREQ_BASE = self.freq
|
||||
|
|
@ -178,7 +360,6 @@ class IMUCamera(ModuloDiagnosticoBase):
|
|||
if p > 0:
|
||||
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:
|
||||
p = int(min(latencia * 100, 30))
|
||||
saude -= p
|
||||
|
|
@ -210,4 +391,4 @@ class IMUCamera(ModuloDiagnosticoBase):
|
|||
self.mostrar_log(f"Erro ao atualizar saude: {e}")
|
||||
|
||||
def mostrar_log(self, mensagem):
|
||||
print(f"{time.time()} - [IMU] {mensagem}")
|
||||
print(f"{time.time()} - [IMU] {mensagem}")
|
||||
|
|
@ -4,6 +4,7 @@ import time
|
|||
import threading
|
||||
import statistics
|
||||
import socket
|
||||
import numpy as np
|
||||
import psutil
|
||||
import subprocess
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
|
@ -82,6 +83,8 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
|
|||
self._ping_timeout_ms = 400 # recomendado: 300–500
|
||||
self._ping_count = 2 # recomendado: 1 (janela já suaviza)
|
||||
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_safe_mbps = 05.0 # alvo saudável (até aqui não penaliza)
|
||||
|
|
@ -366,6 +369,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
|
|||
now = time.time()
|
||||
with self._mqtt_lock:
|
||||
self.last_heartbeat_ts = now
|
||||
self._hb_last_rx_monotonic = time.perf_counter()
|
||||
self._heartbeat_ok = True
|
||||
|
||||
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_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):
|
||||
"""
|
||||
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
|
||||
ok, rtt, loss, ping_ts = self._get_ping_snapshot()
|
||||
#print(f"ok: {ok}, rtt: {rtt}, loss: {loss}, ping_ts: {ping_ts}")
|
||||
self.timeouts_f.append(loss == 100)
|
||||
self.timeouts_s.append(loss == 100)
|
||||
if ping_ts > self._last_ping_sample_applied_ts:
|
||||
self._last_ping_sample_applied_ts = ping_ts
|
||||
|
||||
if ok and rtt is not None:
|
||||
self.rtts_f.append(rtt)
|
||||
self.rtts_s.append(rtt)
|
||||
self.timeouts_f.append(loss == 100)
|
||||
self.timeouts_s.append(loss == 100)
|
||||
|
||||
if loss is not None:
|
||||
self.loses_f.append(loss)
|
||||
self.loses_s.append(loss)
|
||||
if ok and rtt is not None:
|
||||
self.rtts_f.append(rtt)
|
||||
self.rtts_s.append(rtt)
|
||||
|
||||
#(rtt, loss) = self._ping_once_old(pings=3, timeout=200)
|
||||
#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)
|
||||
if loss is not None:
|
||||
self.loses_f.append(loss)
|
||||
self.loses_s.append(loss)
|
||||
|
||||
self._update_nic_errors()
|
||||
self._update_nic_bandwidth()
|
||||
|
|
@ -1118,21 +1078,21 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
|
|||
risk_score = max(0.0, min(100.0, risk_score))
|
||||
|
||||
degradado_agora = (
|
||||
loss_pct_s >= 5 or
|
||||
tmout_pct_s >= 4 or
|
||||
atraso_s >= 4.0 or
|
||||
(loss_pct_f >= 8 and loss_pct_s >= 3) or
|
||||
(tmout_pct_f >= 10 and tmout_pct_s >= 3) or
|
||||
(jitter_s >= 35 and loss_pct_s >= 3)
|
||||
loss_pct_s >= 7 or
|
||||
tmout_pct_s >= 6 or
|
||||
atraso_s >= 5.0 or
|
||||
(loss_pct_f >= 12 and loss_pct_s >= 4) or
|
||||
(tmout_pct_f >= 15 and tmout_pct_s >= 4) or
|
||||
(jitter_s >= 45 and loss_pct_s >= 4)
|
||||
)
|
||||
|
||||
critico_agora = (
|
||||
loss_pct_s >= 10 or
|
||||
tmout_pct_s >= 8 or
|
||||
atraso_s >= 6.0 or
|
||||
(loss_pct_f >= 15 and loss_pct_s >= 5) or
|
||||
(tmout_pct_f >= 20 and tmout_pct_s >= 5) or
|
||||
((loss_pct_s >= 6) and (atraso_s >= 5.0))
|
||||
loss_pct_s >= 15 or
|
||||
tmout_pct_s >= 12 or
|
||||
atraso_s >= 8.0 or
|
||||
(loss_pct_f >= 20 and loss_pct_s >= 6) or
|
||||
(tmout_pct_f >= 25 and tmout_pct_s >= 6) or
|
||||
((loss_pct_s >= 8) and (atraso_s >= 6.0))
|
||||
)
|
||||
|
||||
colapso_agora = (
|
||||
|
|
@ -1216,11 +1176,12 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
|
|||
# Condição 2: Cerca invisível de rede violada
|
||||
dist_violando_cerca = (
|
||||
self.link_state == "DEGRADED" and
|
||||
self._cycles_degraded >= 15 and
|
||||
risk_score >= 70 and
|
||||
self._distancia_inicio_degradado is not None and
|
||||
distancia_base is not None and
|
||||
distancia_base >= 0 and
|
||||
self._max_distancia_desde_degradado > self._distancia_inicio_degradado + 2.0 and
|
||||
distancia_base > self._distancia_inicio_degradado + 1.0
|
||||
distancia_base > self._distancia_inicio_degradado + 5.0
|
||||
)
|
||||
if dist_violando_cerca:
|
||||
condicoes.append({
|
||||
|
|
@ -1338,3 +1299,5 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
|
|||
)
|
||||
except Exception as e:
|
||||
print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def verifica_controle_liberado(ignorar_parada=False):
|
|||
_equipamento = ContextoGlobalRedis.get_equipamento()
|
||||
ang_roll_max = _equipamento.get("angulo_roll_max", 15.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:
|
||||
motivos.append(f"IMU mandatório não operante: {_imu_saude.name}")
|
||||
|
|
|
|||
|
|
@ -129,6 +129,27 @@ namespace OperationControl.Models
|
|||
Angulo = 0,
|
||||
PercentualVelocidadeSPKmh = 0,
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ namespace OperationControl.Models
|
|||
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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
|
|||
LimparTeclas();
|
||||
EnviarNeutros();
|
||||
}
|
||||
else
|
||||
{
|
||||
VariaveisControleOperacao.EnviarComandoIniciarUDP(VariaveisControleOperacao.RoverEmFoco.IP, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -750,8 +750,7 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
|
|||
AddOrAppendSerie("Jitter", "ms", conexao?.detalhes?["jitter"]?.Value<double?>());
|
||||
AddOrAppendSerie("Timeout", "%", conexao?.detalhes?["tmout_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;
|
||||
|
||||
case T_Code.Gps:
|
||||
|
|
@ -768,6 +767,14 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
|
|||
AddOrAppendSerie("Corrente", "A", bat?.CorrenteInstantanea);
|
||||
AddOrAppendSerie("Temperatura", "°C", bat?.Temperatura);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
|
|||
public event Action<TipoFrameCamera>? CameraErvasFrameTipoAlterado;
|
||||
|
||||
public ICommand RetornoBaseCommand { get; }
|
||||
public ICommand ReferenciamentoCommand { get; }
|
||||
|
||||
public MonitoramentoViewModel()
|
||||
{
|
||||
|
|
@ -123,6 +124,7 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
|
|||
CameraErvasFrameTipoSelecionado = CameraErvasFrameTipos.FirstOrDefault();
|
||||
|
||||
RetornoBaseCommand = new RelayCommand(_ => ExecutarRetornoBase(), _ => PodeExecutarRetornoBase());
|
||||
ReferenciamentoCommand = new RelayCommand(_ => ExecutarReferenciamento(), _ => PodeExecutarReferenciamento());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
|
|
|
|||
|
|
@ -209,15 +209,20 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
if (ind.status == StatusModulo.Operante)
|
||||
continue;
|
||||
|
||||
alertasEsperados.Add(new AlertaModel
|
||||
bool mandatorio = ind.em_uso;
|
||||
|
||||
if (mandatorio)
|
||||
{
|
||||
Rover_ID = rover.RoverId,
|
||||
Modulo = mod.modulo,
|
||||
Mod_ID = ind.label,
|
||||
Severidade = DeParaStatus(ind.status, ind.em_uso),
|
||||
Mensagem = string.Join(", ", ind.motivos ?? new List<string>())
|
||||
// NÃO define Timestamp aqui ainda
|
||||
});
|
||||
alertasEsperados.Add(new AlertaModel
|
||||
{
|
||||
Rover_ID = rover.RoverId,
|
||||
Modulo = mod.modulo,
|
||||
Mod_ID = ind.label,
|
||||
Severidade = DeParaStatus(ind.status, mandatorio),
|
||||
Mensagem = string.Join(", ", ind.motivos ?? new List<string>())
|
||||
// NÃO define Timestamp aqui ainda
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -226,17 +231,20 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
continue;
|
||||
|
||||
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,
|
||||
Modulo = mod.modulo,
|
||||
Mod_ID = null,
|
||||
Severidade = DeParaStatus(mod.status, mandatorio),
|
||||
Mensagem = string.Join(", ", mod.motivos ?? new List<string>())
|
||||
// NÃO define Timestamp aqui ainda
|
||||
});
|
||||
alertasEsperados.Add(new AlertaModel
|
||||
{
|
||||
Rover_ID = rover.RoverId,
|
||||
Modulo = mod.modulo,
|
||||
Mod_ID = null,
|
||||
Severidade = DeParaStatus(mod.status, mandatorio),
|
||||
Mensagem = string.Join(", ", mod.motivos ?? new List<string>() { "Motivo desconhecido" })
|
||||
// NÃO define Timestamp aqui ainda
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Brush = System.Windows.Media.Brush;
|
|||
using Brushes = System.Windows.Media.Brushes;
|
||||
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
|
||||
using Color = System.Windows.Media.Color;
|
||||
using AgroBase.Models.Operacoes;
|
||||
|
||||
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 =>
|
||||
!RoverSelecionado ? Brushes.Gray :
|
||||
_temperaturaNumerica >= 55 ? Brushes.OrangeRed :
|
||||
|
|
@ -217,9 +233,19 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
!RoverSelecionado ? Brushes.Gray :
|
||||
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 _bateriaNumerica;
|
||||
private double _reservatorioNumerico;
|
||||
private double _distanciaBaseNumerico;
|
||||
|
||||
public OperacaoLeftViewModel()
|
||||
{
|
||||
|
|
@ -237,6 +263,7 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
_temperaturaNumerica = 0;
|
||||
_bateriaNumerica = 0;
|
||||
_reservatorioNumerico = 0;
|
||||
_distanciaBaseNumerico = 0;
|
||||
|
||||
Velocidade = "--";
|
||||
AnguloDirecional = "--";
|
||||
|
|
@ -246,31 +273,35 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
Reservatorio = "--";
|
||||
SaudeRede = "--";
|
||||
SaudeGnss = "--";
|
||||
DistanciaBase = "--";
|
||||
|
||||
OnPropertyChanged(nameof(CorTemperatura));
|
||||
OnPropertyChanged(nameof(CorBateria));
|
||||
OnPropertyChanged(nameof(CorReservatorio));
|
||||
OnPropertyChanged(nameof(CorRede));
|
||||
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;
|
||||
NomeRover = nomeRover;
|
||||
NomeRover = descricao;
|
||||
|
||||
_temperaturaNumerica = temperaturaC;
|
||||
_bateriaNumerica = bateriaPct;
|
||||
_reservatorioNumerico = reservatorioPct;
|
||||
_distanciaBaseNumerico = distanciaBase;
|
||||
|
||||
Velocidade = velocidadeKmh.ToString("0.0");
|
||||
AnguloDirecional = anguloDirecionalGraus.ToString("0.0");
|
||||
TipoMovimento = tipoMovimento;
|
||||
Velocidade = $"{controle?.PercentualVelocidadeSPKmh ?? 0:F2}";
|
||||
AnguloDirecional = $"{controle?.Angulo ?? 0:F1}";
|
||||
TipoMovimento = controle?.TipoMovimento ?? TipoMovimentoDirecional.RodasDianteiras;
|
||||
Temperatura = temperaturaC.ToString("0.0");
|
||||
Bateria = bateriaPct.ToString("0");
|
||||
Reservatorio = reservatorioPct.ToString("0");
|
||||
SaudeRede = saudeRede;
|
||||
SaudeGnss = saudeGnss;
|
||||
DistanciaBase = $"{distanciaBase:F2}";
|
||||
|
||||
OperacaoEmExecucao = op_iniciada;
|
||||
|
||||
|
|
@ -279,6 +310,7 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
OnPropertyChanged(nameof(CorReservatorio));
|
||||
OnPropertyChanged(nameof(CorRede));
|
||||
OnPropertyChanged(nameof(CorGnss));
|
||||
OnPropertyChanged(nameof(CorDistBase));
|
||||
OnPropertyChanged(nameof(OperacaoEmExecucao));
|
||||
OnPropertyChanged(nameof(TextoBotaoOperacao));
|
||||
OnPropertyChanged(nameof(CorBotaoOperacao));
|
||||
|
|
@ -331,6 +363,7 @@ namespace OperationControl.ViewModels.Views.Operacao
|
|||
private string VelocidadeValor(string valor) => RoverSelecionado ? $"{valor} km/h" : "--";
|
||||
private string TemperaturaValor(string valor) => RoverSelecionado ? $"{valor} °C" : "--";
|
||||
private string PercentualOuTracos(string valor) => RoverSelecionado ? $"{valor}%" : "--";
|
||||
private string DistanciaOuTracos(string valor) => RoverSelecionado ? $"{valor} m" : "--";
|
||||
|
||||
private Brush CorPorTexto(string valor)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ namespace OperationControl.ViewModels
|
|||
var eventos = new List<MotivoTopBarModel>();
|
||||
|
||||
double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds;
|
||||
bool semComunicacao = tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo;
|
||||
bool semComunicacao = !AppShell.Mock && tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo;
|
||||
|
||||
if (semComunicacao)
|
||||
{
|
||||
|
|
@ -394,7 +394,7 @@ namespace OperationControl.ViewModels
|
|||
eventos.Add(new MotivoTopBarModel
|
||||
{
|
||||
Prioridade = 1100,
|
||||
Fonte = "Núcleo Central de Processamento",
|
||||
Fonte = "NCP",
|
||||
Titulo = "NÚCLEO CENTRAL DE PROCESSAMENTO DESCONECTADO",
|
||||
Descricao = "O núcleo central de processamento foi desconectado, tentando reconectar...",
|
||||
StatusVisual = StatusModulo.Falha
|
||||
|
|
@ -475,29 +475,68 @@ namespace OperationControl.ViewModels
|
|||
// 5) Módulos com condições operacionais críticas
|
||||
// ============================
|
||||
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>();
|
||||
|
||||
foreach (var modulo in modulosCriticos)
|
||||
{
|
||||
var nomeModulo = (modulo?.modulo ?? T_Code.Vzo).ToString();
|
||||
|
||||
var descricoesCriticas = modulo?.condicoes_operacionais?
|
||||
.Where(cond => cond?.severidade > 90)
|
||||
.Select(cond => cond?.descricao ?? "")
|
||||
.Where(desc => !string.IsNullOrWhiteSpace(desc))
|
||||
.Distinct()
|
||||
.ToList() ?? new List<string>();
|
||||
bool temCondicaoSevera = modulo?.condicoes_operacionais?.Any(cond => cond?.severidade > 90) ?? false;
|
||||
bool moduloMandatorioNaoOperante =
|
||||
!new List<StatusModulo>() { StatusModulo.Operante, StatusModulo.Alerta }.Contains(modulo?.status ?? StatusModulo.Desconectado) &&
|
||||
(rover?.ModulosMandatorios?.Any(x =>
|
||||
(x?.Dispositivo ?? T_Code.Vzo) == (modulo?.modulo ?? T_Code.Vzo) &&
|
||||
(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
|
||||
{
|
||||
Prioridade = 600,
|
||||
Prioridade = 610,
|
||||
Fonte = "ModuloCritico",
|
||||
Titulo = "CONDIÇÕES OPERACIONAIS CRÍTICAS",
|
||||
Descricao = $"{nomeModulo}: {desc}",
|
||||
StatusVisual = StatusModulo.Alerta,
|
||||
Titulo = "MÓDULO MANDATÓRIO NÃO OPERACIONAL",
|
||||
Descricao = $"{nomeModulo}: módulo mandatório em estado '{statusTexto}'",
|
||||
StatusVisual = StatusModulo.Falha,
|
||||
Modulo = modulo?.modulo
|
||||
});
|
||||
}
|
||||
|
|
@ -507,9 +546,11 @@ namespace OperationControl.ViewModels
|
|||
{
|
||||
{ T_Code.Ipb, 1 },
|
||||
{ T_Code.Npc, 2 },
|
||||
{ T_Code.Bat, 3 },
|
||||
{ T_Code.Atu, 4 },
|
||||
{ T_Code.Sen, 5 }
|
||||
{ T_Code.Can, 3 },
|
||||
{ T_Code.Bat, 4 },
|
||||
{ T_Code.Gps, 5 },
|
||||
{ T_Code.Atu, 6 },
|
||||
{ T_Code.Sen, 7 }
|
||||
};
|
||||
|
||||
// ============================
|
||||
|
|
@ -590,7 +631,7 @@ namespace OperationControl.ViewModels
|
|||
if (string.IsNullOrWhiteSpace(linha2))
|
||||
linha2 = "Sem detalhes adicionais.";
|
||||
|
||||
Variaveis.MostrarLog($"LINHA 1: {linha1}");
|
||||
Models.Variaveis.MostrarLog($"LINHA 1: {linha1}");
|
||||
|
||||
// ============================
|
||||
// 8) Atualiza UI
|
||||
|
|
@ -619,7 +660,7 @@ namespace OperationControl.ViewModels
|
|||
|
||||
public void AtualizarDadosGnss(AgroBase.Models.GPSModel dados)
|
||||
{
|
||||
var bf = Variaveis.GpsService.BaseFix;
|
||||
var bf = Models.Variaveis.GpsService.BaseFix;
|
||||
if (bf.CorrecaoEmAndamento)
|
||||
{
|
||||
AtualizarProgressoFixacaoBase(bf.Progresso, bf.ProgressoStr);
|
||||
|
|
@ -805,7 +846,7 @@ namespace OperationControl.ViewModels
|
|||
|
||||
// ESQUERDA
|
||||
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_perda = conexao?.detalhes?["loss_pct"]?.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(
|
||||
rover?.Descricao ?? "",
|
||||
obj.Controle?.PercentualVelocidadeSPKmh ?? 0,
|
||||
obj.Controle?.Angulo ?? 0,
|
||||
obj.Controle?.TipoMovimento ?? TipoMovimentoDirecional.Diagnostico,
|
||||
obj.Controle,
|
||||
obj.Refrigeracao?.Temperatura ?? 0,
|
||||
obj.Bateria?.PercentualBateria ?? 0,
|
||||
obj.Atuador?.PercentualReservatorio ?? 0,
|
||||
$"{c}%",
|
||||
$"{saude_rede}%",
|
||||
$"{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
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue