Ajustes de conectividade mqtt

This commit is contained in:
Diego Freitas 2026-03-17 14:26:07 -03:00
parent ca86e1b1e2
commit 8ee9368c8b
11 changed files with 250 additions and 119 deletions

View File

@ -172,7 +172,7 @@ namespace AgroBase
tkbVelocidade.Minimum = 0; tkbVelocidade.Minimum = 0;
tkbVelocidade.Maximum = 100; tkbVelocidade.Maximum = 100;
FuncoesGlobais.PreencheValorTrackBar(tkbVelocidade, Variaveis.OperacaoEmAndamento.Parametros.Controle.DirVelocidadeMovimento); FuncoesGlobais.PreencheValorTrackBar(tkbVelocidade, Variaveis.OperacaoEmAndamento.Parametros.Controle?.DirVelocidadeMovimento ?? 0);
tkbAngulo.Minimum = 0; tkbAngulo.Minimum = 0;
tkbAngulo.Maximum = 90; tkbAngulo.Maximum = 90;

View File

@ -612,7 +612,7 @@ namespace AgroBase
_Controle.Angulo = solto ? 0 : pControle.DirAnguloMaximo * percent; _Controle.Angulo = solto ? 0 : pControle.DirAnguloMaximo * percent;
break; break;
case T_Code.Mov: case T_Code.Mov:
_Controle.PercentualVelocidadeSP = solto ? 0 : pControle.MovVelocidadeSErvasPercent * percent; _Controle.PercentualVelocidadeSP = solto ? 0 : Math.Max(pControle.MovVelocidadeCErvasPercent, pControle.MovVelocidadeSErvasPercent * percent);
break; break;
} }
} }

View File

@ -338,10 +338,12 @@ namespace AgroBase.Models
OperacaoParametrosModel p = new OperacaoParametrosModel() OperacaoParametrosModel p = new OperacaoParametrosModel()
{ {
Modo = Modo, Modo = Modo,
Descricao = $"{Modo} - Padrão",
QtdCamerasSolo = VariaveisEquipamento.QuantidadeCamerasSolo, QtdCamerasSolo = VariaveisEquipamento.QuantidadeCamerasSolo,
QtdBicos = VariaveisEquipamento.QuantidadeBicosPulverizadores, QtdBicos = VariaveisEquipamento.QuantidadeBicosPulverizadores,
CapacidadeReservatorio = VariaveisEquipamento.CapacidadeReservatorio, CapacidadeReservatorio = VariaveisEquipamento.CapacidadeReservatorio,
RoverId = VariaveisEquipamento.Parametros.serial_number, RoverId = VariaveisEquipamento.Parametros.serial_number,
IP = VariaveisEquipamento.Parametros.rover_ip,
RuasPercorrer = null, RuasPercorrer = null,
Mapa = null, Mapa = null,
DadosLeitura = new OperacaoParametrosDadosModel(), DadosLeitura = new OperacaoParametrosDadosModel(),
@ -382,7 +384,7 @@ namespace AgroBase.Models
MpcMatrizCusto = false, MpcMatrizCusto = false,
MpcHorizonte = 4.0, MpcHorizonte = 4.0,
MovVelocidadeSErvasPercent = 100, MovVelocidadeSErvasPercent = 50,
MovVelocidadeCErvasPercent = 15, MovVelocidadeCErvasPercent = 15,
DirAnguloMaximo = 25, DirAnguloMaximo = 25,
DirVelocidadeMovimento = 40, DirVelocidadeMovimento = 40,
@ -832,6 +834,7 @@ namespace AgroBase.Models
if (isagrobase) if (isagrobase)
{ {
Variaveis.OperacaoEmAndamento.Parametros = p; Variaveis.OperacaoEmAndamento.Parametros = p;
Variaveis.OperacaoEmAndamento.DefinirComponentesEmUso();
Variaveis.OperacaoEmAndamento.Trajetoria = new TrajetoriaMapaOperacaoModel(new List<List<GPSModel>>()); Variaveis.OperacaoEmAndamento.Trajetoria = new TrajetoriaMapaOperacaoModel(new List<List<GPSModel>>());
Variaveis.OperacaoEmAndamento.Sensoriamento = new OperacaoSensoriamentoLogModel(); Variaveis.OperacaoEmAndamento.Sensoriamento = new OperacaoSensoriamentoLogModel();
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("configurado", false)); RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("configurado", false));
@ -891,36 +894,38 @@ namespace AgroBase.Models
case T_Code.Mov: case T_Code.Mov:
foreach (var c in m.ComponentesEmUso) foreach (var c in m.ComponentesEmUso)
{ {
var d = Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.FirstOrDefault(x => x.MovMotor.Mod_ID == c.Key).MovMotor; var d = Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x?.MovMotor?.Mod_ID == c.Key)?.MovMotor;
if (d != null)
d.Comandar = c.Value.Item2; d.Comandar = c.Value.Item2;
} }
break; break;
case T_Code.Dir: case T_Code.Dir:
foreach (var c in m.ComponentesEmUso) foreach (var c in m.ComponentesEmUso)
{ {
var d = Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.FirstOrDefault(x => x.DirMotor.Mod_ID == c.Key).DirMotor; var d = Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x?.DirMotor?.Mod_ID == c.Key)?.DirMotor;
if (d != null)
d.Comandar = c.Value.Item2; d.Comandar = c.Value.Item2;
} }
break; break;
case T_Code.Atu: case T_Code.Atu:
var da = Variaveis.OperacaoEmAndamento.DispAtu.Dados; var da = Variaveis.OperacaoEmAndamento.DispAtu?.Dados;
foreach (var c in m.ComponentesEmUso) foreach (var c in m.ComponentesEmUso)
{ {
var s = da.Sensores.FirstOrDefault(x => x.ID == c.Key); var s = da?.Sensores?.FirstOrDefault(x => x.ID == c.Key);
if (s != null) if (s != null)
{ {
s.Mandatorio = c.Value.Item1; s.Mandatorio = c.Value.Item1;
s.Aferir = c.Value.Item2; s.Aferir = c.Value.Item2;
continue; continue;
} }
var bc = da.BicosPulverizadores.FirstOrDefault(x => x.ID == c.Key); var bc = da?.BicosPulverizadores?.FirstOrDefault(x => x.ID == c.Key);
if (bc != null) if (bc != null)
{ {
bc.Mandatorio = c.Value.Item1; bc.Mandatorio = c.Value.Item1;
bc.Comandar = c.Value.Item2; bc.Comandar = c.Value.Item2;
continue; continue;
} }
var bo = da.BombasPressurizadoras.FirstOrDefault(x => x.ID == c.Key); var bo = da?.BombasPressurizadoras?.FirstOrDefault(x => x.ID == c.Key);
if (bo != null) if (bo != null)
{ {
bo.Mandatorio = c.Value.Item1; bo.Mandatorio = c.Value.Item1;
@ -930,31 +935,31 @@ namespace AgroBase.Models
} }
break; break;
case T_Code.Sen: case T_Code.Sen:
var ds = Variaveis.OperacaoEmAndamento.DispSen.Dados; var ds = Variaveis.OperacaoEmAndamento.DispSen?.Dados;
foreach (var c in m.ComponentesEmUso) foreach (var c in m.ComponentesEmUso)
{ {
var s = ds.Sensores.FirstOrDefault(x => x.ID == c.Key); var s = ds?.Sensores?.FirstOrDefault(x => x.ID == c.Key);
if (s != null) if (s != null)
{ {
s.Mandatorio = c.Value.Item1; s.Mandatorio = c.Value.Item1;
s.Aferir = c.Value.Item2; s.Aferir = c.Value.Item2;
continue; continue;
} }
var sv = ds.Servos.FirstOrDefault(x => x.ID == c.Key); var sv = ds?.Servos?.FirstOrDefault(x => x.ID == c.Key);
if (sv != null) if (sv != null)
{ {
sv.Mandatorio = c.Value.Item1; sv.Mandatorio = c.Value.Item1;
sv.Controlar = c.Value.Item2; sv.Controlar = c.Value.Item2;
continue; continue;
} }
var rl = ds.Reles.FirstOrDefault(x => x.ID == c.Key); var rl = ds?.Reles?.FirstOrDefault(x => x.ID == c.Key);
if (rl != null) if (rl != null)
{ {
rl.Mandatorio = c.Value.Item1; rl.Mandatorio = c.Value.Item1;
rl.Controlar = c.Value.Item2; rl.Controlar = c.Value.Item2;
continue; continue;
} }
var ld = ds.Sinaleiros.FirstOrDefault(x => x.ID == c.Key); var ld = ds?.Sinaleiros?.FirstOrDefault(x => x.ID == c.Key);
if (ld != null) if (ld != null)
{ {
ld.Mandatorio = c.Value.Item1; ld.Mandatorio = c.Value.Item1;
@ -973,15 +978,17 @@ namespace AgroBase.Models
CarregarParametrosOperacaoPadrao((ModoOperacao)Parametros.Modo, true); CarregarParametrosOperacaoPadrao((ModoOperacao)Parametros.Modo, true);
Variaveis.OperacaoEmAndamento.Parametros = Parametros;
Variaveis.OperacaoEmAndamento.Parametros.RoverId = VariaveisEquipamento.Parametros.serial_number;
Variaveis.OperacaoEmAndamento.Parametros.DadosLeitura = new OperacaoParametrosDadosModel();
Variaveis.OperacaoEmAndamento.Sensoriamento.AtualizarDados();
Variaveis.OperacaoEmAndamento.Parametros.ModulosMandatorios.ForEach(m => m.ComponentesEmUso = Variaveis.OperacaoEmAndamento.PreencherComponentesEmUso(m.Dispositivo));
Variaveis.OperacaoEmAndamento.DefinirComponentesEmUso();
Variaveis.OperacaoEmAndamento.Descricao = Parametros.Descricao; Variaveis.OperacaoEmAndamento.Descricao = Parametros.Descricao;
Variaveis.OperacaoEmAndamento.OprPath = $"Base ({VariaveisEquipamento.Parametros.base_ip})"; Variaveis.OperacaoEmAndamento.OprPath = $"Base ({VariaveisEquipamento.Parametros.base_ip})";
var p = Variaveis.OperacaoEmAndamento.Parametros;
p.Descricao = Parametros.Descricao;
p.CapacidadeReservatorio = Parametros.CapacidadeReservatorio;
p.Controle = Parametros.Controle;
p.QtdBicos = Parametros.QtdBicos;
p.QtdCamerasSolo = Parametros.QtdCamerasSolo;
p.RuasPercorrer = Parametros.RuasPercorrer;
p.ModulosMandatorios = Parametros.ModulosMandatorios;
p.ParametrosMandatorios = Parametros.ParametrosMandatorios;
List<string> ruasSelecionadas = Parametros.RuasPercorrer ?? new List<string>(); List<string> ruasSelecionadas = Parametros.RuasPercorrer ?? new List<string>();
if (Parametros.Mapa != null) if (Parametros.Mapa != null)
@ -1491,7 +1498,8 @@ namespace AgroBase.Models
{ {
case BotoesJoystick.Share: // Requisitar parametros da operacao case BotoesJoystick.Share: // Requisitar parametros da operacao
var topico = Variaveis.MqttServiceBase.Topicos.FirstOrDefault(x => x.Topico == VariaveisEquipamento.TopicoMqttParametros.Replace("<id>", VariaveisEquipamento.Parametros.serial_number)); var topico = Variaveis.MqttServiceBase.Topicos.FirstOrDefault(x => x.Topico == VariaveisEquipamento.TopicoMqttParametros.Replace("<id>", VariaveisEquipamento.Parametros.serial_number));
string msg = JsonConvert.SerializeObject(Variaveis.OperacaoEmAndamento.Parametros); var p = Variaveis.OperacaoEmAndamento.Parametros;
string msg = JsonConvert.SerializeObject(p);
Task.Run(async () => await Variaveis.MqttServiceBase.PublishAsync(topico, msg)); Task.Run(async () => await Variaveis.MqttServiceBase.PublishAsync(topico, msg));
break; break;
case BotoesJoystick.Options: // Operacao nao iniciada, carrega todos os parametros compeltos case BotoesJoystick.Options: // Operacao nao iniciada, carrega todos os parametros compeltos

View File

@ -66,7 +66,7 @@ namespace AgroBase.Models.Operadores
public void AtualizarStatusBloqueio(bool consideraEmMovimento) public void AtualizarStatusBloqueio(bool consideraEmMovimento)
{ {
var Sonar = Variaveis.OperacaoEmAndamento.Sensoriamento.OperadorVisual; var Sonar = Variaveis.OperacaoEmAndamento.Sensoriamento.OperadorVisual;
ObstaculoDetectado = (Sonar.Analises?.matriz_confianca?.block?.decision?.parar ?? false); ObstaculoDetectado = (Sonar?.Analises?.matriz_confianca?.block?.decision?.parar ?? false);
StatusCarro = Sonar?.Analises?.segmentacao?.status_corredor ?? StatusCarroMapa.Indefinido; StatusCarro = Sonar?.Analises?.segmentacao?.status_corredor ?? StatusCarroMapa.Indefinido;
ErroLateral = Sonar?.Analises?.segmentacao?.erro_angular ?? 0; ErroLateral = Sonar?.Analises?.segmentacao?.erro_angular ?? 0;

View File

@ -49,17 +49,24 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
self.last_heartbeat_ts = time.time() self.last_heartbeat_ts = time.time()
self.rover_id = None self.rover_id = None
self.sub = False
self._mqtt = None self._mqtt = None
self._mqtt_conectado = False
self._mqtt_executor = ThreadPoolExecutor(max_workers=1)
self._mqtt_future = None
self._mqtt_lock = threading.Lock() self._mqtt_lock = threading.Lock()
self._mqtt_last_attempt_ts = 0.0 self._mqtt_conectado = False
self._mqtt_backoff_s = 1.0 # começa rápido
self._mqtt_backoff_max_s = 15.0 # limite
self._mqtt_connecting = False self._mqtt_connecting = False
self._heartbeat_ok = False
self._mqtt_last_attempt_ts = 0.0
self._mqtt_backoff_s = 1.0
self._mqtt_backoff_max_s = 30.0
self._mqtt_connected_ts = 0.0
self.last_heartbeat_ts = 0.0
self._heartbeat_timeout_s = 10.0
self._heartbeat_grace_after_connect_s = 15.0
self.sub = False
self._mqtt_future = None
self._mqtt_executor = ThreadPoolExecutor(max_workers=1)
self._mqtt_client_seq = 0
self._mqtt_debug = False
self._ping_executor = ThreadPoolExecutor(max_workers=1) self._ping_executor = ThreadPoolExecutor(max_workers=1)
self._ping_future = None self._ping_future = None
@ -152,128 +159,208 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
finally: finally:
self._running = False self._running = False
def _start_mqtt_heartbeat(self):
try: # MQTT HEARTBEAT
if self._mqtt_conectado: def _mqtt_mostra_log(self, msg):
if self._mqtt_debug:
print(f"[HEARTBEAT] {msg}")
def _check_mqtt_heartbeat(self):
now = time.time()
client_to_close = None
with self._mqtt_lock:
if not self._mqtt_conectado:
return return
base_ip = self.get_base_ip()
if base_ip is None: connected_ts = self._mqtt_connected_ts
last_hb = self.last_heartbeat_ts
# janela de graça após reconnect
if (now - connected_ts) < self._heartbeat_grace_after_connect_s:
return
atraso_s = now - last_hb
if atraso_s >= self._heartbeat_timeout_s:
self._mqtt_mostra_log(f"atraso de {atraso_s:.1f}s detectado")
with self._mqtt_lock:
self._heartbeat_ok = False
client_to_close = self._mqtt_detach_client_locked("heartbeat atrasado")
self._mqtt_close_client(client_to_close)
def _is_active_client_locked(self, client):
return self._mqtt is client
def _mqtt_detach_client_locked(self, reason=""):
client = self._mqtt
self._mqtt = None
self._mqtt_conectado = False self._mqtt_conectado = False
return self._mqtt_connecting = False
self._mqtt = mqtt.Client() self._heartbeat_ok = False
self._mqtt.on_connect = self._on_connect self.sub = False
self._mqtt.on_message = self._on_message self._mqtt_last_attempt_ts = time.time()
self._mqtt.on_disconnect = self._on_disconnect self._mqtt_backoff_s = max(self._mqtt_backoff_s, 5.0)
self._mqtt.connect(base_ip, 1883, 60)
self._mqtt.loop_start() if reason:
except: self._mqtt_mostra_log(f"Resetando client MQTT: {reason}")
#pass
print("Erro ao se conectar no broker mqtt") return client
def _mqtt_close_client(self, client):
if client is not None:
cid = getattr(client, "_agro_client_local_id", "?")
self._mqtt_mostra_log(f"[client={cid}] fechando client antigo")
try:
client.loop_stop()
except Exception:
pass
try:
client.disconnect()
except Exception:
pass
def _start_mqtt_heartbeat_async(self): def _start_mqtt_heartbeat_async(self):
"""Dispara tentativa de conexão MQTT sem bloquear o loop."""
now = time.time() now = time.time()
with self._mqtt_lock: with self._mqtt_lock:
if self._mqtt_conectado: if self._mqtt_conectado:
return return
if self._mqtt_connecting: if self._mqtt_connecting:
return return
# respeita backoff if self._mqtt_future is not None and not self._mqtt_future.done():
return
if (now - self._mqtt_last_attempt_ts) < self._mqtt_backoff_s: if (now - self._mqtt_last_attempt_ts) < self._mqtt_backoff_s:
return return
base_ip = self.get_base_ip()
if not base_ip:
self._mqtt_mostra_log("mqtt heartbeat sem ip base definido")
return
self._mqtt_last_attempt_ts = now self._mqtt_last_attempt_ts = now
self._mqtt_connecting = True self._mqtt_connecting = True
base_ip = self.get_base_ip() self._mqtt_mostra_log("tentando conectar mqtt heartbeat")
if base_ip is None:
with self._mqtt_lock:
self._mqtt_connecting = False
return
# se já tem uma future rodando, não dispara outra
if self._mqtt_future is not None and not self._mqtt_future.done():
return
self._mqtt_future = self._mqtt_executor.submit(self._mqtt_connect_worker, base_ip) self._mqtt_future = self._mqtt_executor.submit(self._mqtt_connect_worker, base_ip)
def _mqtt_connect_worker(self, base_ip: str): def _mqtt_connect_worker(self, base_ip: str):
"""Roda em background. Pode bloquear aqui sem travar o health.""" client = None
try: try:
client = mqtt.Client() client = mqtt.Client()
with self._mqtt_lock:
self._mqtt_client_seq += 1
cid = self._mqtt_client_seq
client._agro_client_local_id = cid
client.on_connect = self._on_connect client.on_connect = self._on_connect
client.on_message = self._on_message client.on_message = self._on_message
client.on_disconnect = self._on_disconnect client.on_disconnect = self._on_disconnect
# dica V1: keepalive menor ajuda a detectar quedas mais rápido client.connect(base_ip, 1883, 15)
keepalive = 15
# conecta (bloqueante)
client.connect(base_ip, 1883, keepalive)
# se chegou aqui, iniciou. loop_start é não-bloqueante
client.loop_start() client.loop_start()
# troca o client com lock (evita race)
with self._mqtt_lock: with self._mqtt_lock:
# se tinha um client antigo, tenta parar # só assume como ativo depois de iniciar sem exception
if self._mqtt is not None: old = self._mqtt
self._mqtt = client
if old is not None and old is not client:
try: try:
self._mqtt.loop_stop() old.loop_stop()
self._mqtt.disconnect() except Exception:
except: pass
try:
old.disconnect()
except Exception:
pass pass
self._mqtt = client self._mqtt_mostra_log(f"mqtt heartbeat conectado [client={cid}]")
# on_connect vai setar _mqtt_conectado quando confirmar
except Exception: except Exception as e:
# falhou: libera pra tentar de novo com backoff maior
with self._mqtt_lock: with self._mqtt_lock:
self._mqtt_conectado = False
self._mqtt_connecting = False self._mqtt_connecting = False
self._mqtt_backoff_s = min(self._mqtt_backoff_s * 2.0, self._mqtt_backoff_max_s) self._mqtt_conectado = False
self._heartbeat_ok = False
self.sub = False
self._mqtt_last_attempt_ts = time.time()
self._mqtt_backoff_s = min(max(self._mqtt_backoff_s * 2.0, 5.0), self._mqtt_backoff_max_s)
self._mqtt_mostra_log(f"mqtt heartbeat erro ao conectar [client={getattr(client, '_agro_client_local_id', '?')}]: {e}")
def _on_connect(self, client, userdata, flags, rc): def _on_connect(self, client, userdata, flags, rc):
cid = getattr(client, "_agro_client_local_id", "?")
self._mqtt_mostra_log(f"[client={cid}] on_connect rc={rc} | t={time.time():.3f}")
with self._mqtt_lock: with self._mqtt_lock:
self._mqtt_conectado = (rc == 0) if not self._is_active_client_locked(client):
self._mqtt_mostra_log(f"[client={cid}] on_connect IGNORADO (client antigo)")
return
self._mqtt_connecting = False self._mqtt_connecting = False
# reset backoff quando conecta
if self._mqtt_conectado: if rc == 0:
self._mqtt_conectado = True
self._heartbeat_ok = False
self._mqtt_backoff_s = 1.0 self._mqtt_backoff_s = 1.0
self._reset_janelas() self._mqtt_connected_ts = time.time()
self.last_heartbeat_ts = self._mqtt_connected_ts
self.rover_id = ContextoGlobalRedis.get_equipamento().get("serial_number") self.rover_id = ContextoGlobalRedis.get_equipamento().get("serial_number")
if self.sub == False:
topic = f"agrobot/v1/rover/{self.rover_id}/heartbeat" topic = f"agrobot/v1/rover/{self.rover_id}/heartbeat"
client.subscribe(topic)
print("[HEARTBEAT] Subscribado em:", topic) result, mid = client.subscribe(topic)
self.sub = True self.sub = (result == 0)
self._mqtt_mostra_log(f"[client={cid}] Subscribado em: {topic} | result={result} mid={mid}")
else: else:
print("Erro ao conectar MQTT:", rc) self._mqtt_conectado = False
self._heartbeat_ok = False
self.sub = False
self._mqtt_mostra_log(f"[client={cid}] Erro ao conectar MQTT: rc={rc}")
def _on_disconnect(self, client, userdata, rc): def _on_disconnect(self, client, userdata, rc):
cid = getattr(client, "_agro_client_local_id", "?")
with self._mqtt_lock: with self._mqtt_lock:
if not self._is_active_client_locked(client):
self._mqtt_mostra_log(f"[client={cid}] on_disconnect IGNORADO (client antigo) rc={rc}")
return
self._mqtt_conectado = False self._mqtt_conectado = False
self._mqtt_connecting = False self._mqtt_connecting = False
self._heartbeat_ok = False
self.sub = False self.sub = False
print("[HEARTBEAT] MQTT desconectado!", rc) self._mqtt = None
# se rc != 0 significa desconexão inesperada # adiciona isso:
if rc != 0: self._mqtt_last_attempt_ts = time.time()
print("[HEARTBEAT] Desconexão inesperada — pode ter perdido o link 900 MHz") self._mqtt_backoff_s = max(self._mqtt_backoff_s, 5.0)
# Marca como "sem heartbeat" instantâneo (opcional) self._mqtt_mostra_log(f"[client={cid}] MQTT desconectado! rc={rc} | t={time.time():.3f}")
# Aqui podemos forçar atraso grande
self.last_heartbeat_ts = 0
def _on_message(self, client, userdata, msg): def _on_message(self, client, userdata, msg):
topic = msg.topic cid = getattr(client, "_agro_client_local_id", "?")
if topic == f"agrobot/v1/rover/{self.rover_id}/heartbeat":
self.last_heartbeat_ts = time.time() with self._mqtt_lock:
#print("[HEARTBEAT] recebido") if not self._is_active_client_locked(client):
self._mqtt_mostra_log(f"[client={cid}] on_message IGNORADO (client antigo)")
return
if msg.topic == f"agrobot/v1/rover/{self.rover_id}/heartbeat":
now = time.time()
with self._mqtt_lock:
self.last_heartbeat_ts = now
self._heartbeat_ok = True
self._mqtt_mostra_log(f"[client={cid}] heartbeat recebido | t={now:.3f}")
def _reset_janelas(self): def _reset_janelas(self):
for d in ( for d in (
@ -654,6 +741,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
distancia_base = m.get("distancia_base", -1) distancia_base = m.get("distancia_base", -1)
self._ajustar_limites_banda_por_distancia(distancia_m=distancia_base) self._ajustar_limites_banda_por_distancia(distancia_m=distancia_base)
self._check_mqtt_heartbeat()
self._start_mqtt_heartbeat_async() self._start_mqtt_heartbeat_async()
SAUDE_MIN_ALERTA = 80 SAUDE_MIN_ALERTA = 80

View File

@ -865,10 +865,10 @@ namespace OperationControl.Controls
return dict.TryGetValue(fieldName, out var v) ? v : null; return dict.TryGetValue(fieldName, out var v) ? v : null;
} }
public AgroBase.Models.MapaFeatureCollectionModel CriarDadosMapa() public AgroBase.Models.MapaFeatureCollectionModel CriarDadosMapa(bool selecionadas = false)
{ {
AgroBase.Models.MapaFeatureCollectionModel _mapa = new AgroBase.Models.MapaFeatureCollectionModel() { type = "FeatureCollection", features = new List<AgroBase.Models.MapaFeatureModel>() }; AgroBase.Models.MapaFeatureCollectionModel _mapa = new AgroBase.Models.MapaFeatureCollectionModel() { type = "FeatureCollection", features = new List<AgroBase.Models.MapaFeatureModel>() };
foreach (var x in RuasMapaCarregado.Where(x => x.Selected)) foreach (var x in RuasMapaCarregado.Where(x => selecionadas ? x.Selected : true))
{ {
string id = ((dynamic)x.Feature).Id.ToString(); string id = ((dynamic)x.Feature).Id.ToString();
string type = ((dynamic)x.Feature).Geometry.GeometryType; string type = ((dynamic)x.Feature).Geometry.GeometryType;

View File

@ -82,7 +82,7 @@ namespace OperationControl.Models
public static readonly string BaseMarkerID = "BASE"; public static readonly string BaseMarkerID = "BASE";
private static readonly object _RoversLock = new object(); private static readonly object _RoversLock = new object();
private static double TempoRoverVivo = 5.0; public static double TempoRoverVivo = 5.0;
private static bool AdicionandoNovoRover = false; private static bool AdicionandoNovoRover = false;
private static bool AtualizandoListaRovers = false; private static bool AtualizandoListaRovers = false;
public static AgroBase.Models.Enums.StatusModulo StatusBase public static AgroBase.Models.Enums.StatusModulo StatusBase
@ -153,7 +153,7 @@ namespace OperationControl.Models
OperacaoParametrosModel rover; OperacaoParametrosModel rover;
lock (_RoversLock) lock (_RoversLock)
{ {
rover = RoversNaRede.FirstOrDefault(x => x.RoverId == device_id); rover = RoversNaRede?.FirstOrDefault(x => x.RoverId == device_id);
if (rover != null) if (rover != null)
{ {
obj.Momento = DateTime.Now; obj.Momento = DateTime.Now;
@ -189,10 +189,8 @@ namespace OperationControl.Models
var rover = RoversNaRede.FirstOrDefault(x => x.RoverId == device_id); var rover = RoversNaRede.FirstOrDefault(x => x.RoverId == device_id);
if (rover != null) if (rover != null)
{ {
obj.UltimoContato = DateTime.Now; rover = obj;
obj.IP = rover.IP; rover.UltimoContato = DateTime.Now;
RoversNaRede.Remove(rover);
RoversNaRede.Add(obj);
} }
} }
((App)Application.Current).Shell.Main?.AtualizarDadosTela_ParametrosOperacao(obj.Controle); ((App)Application.Current).Shell.Main?.AtualizarDadosTela_ParametrosOperacao(obj.Controle);
@ -253,6 +251,7 @@ namespace OperationControl.Models
((App)Application.Current).Shell.Main?.RemoverAlerta(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb); ((App)Application.Current).Shell.Main?.RemoverAlerta(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb);
((App)Application.Current).Shell.RemoverAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb); ((App)Application.Current).Shell.RemoverAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb);
} }
((App)Application.Current).Shell.Dock?._vm?.AtualizarBarraSuperior(rover);
} }
Application.Current.Dispatcher.BeginInvoke(new Action(() => Application.Current.Dispatcher.BeginInvoke(new Action(() =>

View File

@ -746,12 +746,12 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
case T_Code.Ipb: case T_Code.Ipb:
var conexao = rover.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Ipb); var conexao = rover.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Ipb);
AddOrAppendSerie("Latência", "ms", conexao?.detalhes?["avg_rtt"]?.Value<double>()); AddOrAppendSerie("Latência", "ms", conexao?.detalhes?["avg_rtt"]?.Value<double?>());
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", "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:

View File

@ -1,5 +1,6 @@
using AgroBase.Models; using AgroBase.Models;
using AgroBase.Models.Operacoes; using AgroBase.Models.Operacoes;
using OperationControl.Controls;
using OperationControl.Helpers; using OperationControl.Helpers;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Input; using System.Windows.Input;
@ -51,6 +52,11 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento
Parametros = origem; Parametros = origem;
} }
public void ProcessarStreetMapClicked(MapViewControl.StreetMapClickedEventArgs e)
{
}
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string nome) private void OnPropertyChanged(string nome)

View File

@ -366,8 +366,26 @@ namespace OperationControl.ViewModels
if (rover == null || obj == null) if (rover == null || obj == null)
return; return;
if (rover.RoverId != VariaveisControleOperacao.SelectedRoverId)
return;
var eventos = new List<MotivoTopBarModel>(); var eventos = new List<MotivoTopBarModel>();
double tempoSemResposta = (DateTime.Now - rover.UltimoContato).TotalSeconds;
bool semComunicacao = tempoSemResposta >= VariaveisControleOperacao.TempoRoverVivo;
if (semComunicacao)
{
eventos.Add(new MotivoTopBarModel
{
Prioridade = 2000,
Fonte = "Comunicacao",
Titulo = "PERDA DE COMUNICAÇÃO COM O ROVER",
Descricao = $"Sem resposta do rover há {tempoSemResposta:F1} s. Tentando reconectar automaticamente.",
StatusVisual = StatusModulo.Desconectado
});
}
if (obj.ModulosSaude?.All(x => (x?.status ?? StatusModulo.Desconectado) == StatusModulo.Desconectado) ?? false) if (obj.ModulosSaude?.All(x => (x?.status ?? StatusModulo.Desconectado) == StatusModulo.Desconectado) ?? false)
{ {
// ============================ // ============================
@ -535,6 +553,7 @@ namespace OperationControl.ViewModels
// Badge curto // Badge curto
badge = principal.Fonte switch badge = principal.Fonte switch
{ {
"Comunicacao" => "DESCONECTADO",
"Emergencia" => "EMERGÊNCIA", "Emergencia" => "EMERGÊNCIA",
"Pausa" => "PAUSADO", "Pausa" => "PAUSADO",
"Trajetoria" => "TRAJETÓRIA", "Trajetoria" => "TRAJETÓRIA",
@ -546,6 +565,7 @@ namespace OperationControl.ViewModels
// Resumo curto // Resumo curto
resumo = principal.Fonte switch resumo = principal.Fonte switch
{ {
"Comunicacao" => "Sem telemetria",
"Emergencia" => "Parada imediata", "Emergencia" => "Parada imediata",
"Pausa" => "Aguardando retomada", "Pausa" => "Aguardando retomada",
"Trajetoria" => "Operação bloqueada", "Trajetoria" => "Operação bloqueada",
@ -968,6 +988,15 @@ namespace OperationControl.ViewModels
} }
private void OnSalvarParametrosSolicitado(OperacaoParametrosModel parametros) private void OnSalvarParametrosSolicitado(OperacaoParametrosModel parametros)
{ {
var dadosMapa = _viewOperacaoCenter?.Mapa?.CriarDadosMapa();
var ruasPercorrer = _viewOperacaoCenter?.Mapa?.RuasMapaCarregado?.Where(x => x.Selected)?.Select(x => x.Id)?.ToList() ?? new List<string>();
if ((dadosMapa?.features?.Count ?? 0) == 0 || !ruasPercorrer.Any())
{
System.Windows.MessageBox.Show("Selecione as ruas no mapa onde o equipamento irá operar", "Ruas não selecionadas", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (VariaveisControleOperacao.RoverEmFoco == null) if (VariaveisControleOperacao.RoverEmFoco == null)
return; return;

View File

@ -29,6 +29,7 @@ namespace OperationControl.Views.Operacao
private void MAP_StreetMapClicked(object? sender, MapViewControl.StreetMapClickedEventArgs e) private void MAP_StreetMapClicked(object? sender, MapViewControl.StreetMapClickedEventArgs e)
{ {
_vm.ProcessarStreetMapClicked(e); _vm.ProcessarStreetMapClicked(e);
((App)System.Windows.Application.Current).Shell.Dock?._vm?._viewOperacaoRight?.areaParametrizacao?._vm?.ProcessarStreetMapClicked(e);
} }
} }