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.Maximum = 100;
FuncoesGlobais.PreencheValorTrackBar(tkbVelocidade, Variaveis.OperacaoEmAndamento.Parametros.Controle.DirVelocidadeMovimento);
FuncoesGlobais.PreencheValorTrackBar(tkbVelocidade, Variaveis.OperacaoEmAndamento.Parametros.Controle?.DirVelocidadeMovimento ?? 0);
tkbAngulo.Minimum = 0;
tkbAngulo.Maximum = 90;

View File

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

View File

@ -338,10 +338,12 @@ namespace AgroBase.Models
OperacaoParametrosModel p = new OperacaoParametrosModel()
{
Modo = Modo,
Descricao = $"{Modo} - Padrão",
QtdCamerasSolo = VariaveisEquipamento.QuantidadeCamerasSolo,
QtdBicos = VariaveisEquipamento.QuantidadeBicosPulverizadores,
CapacidadeReservatorio = VariaveisEquipamento.CapacidadeReservatorio,
RoverId = VariaveisEquipamento.Parametros.serial_number,
IP = VariaveisEquipamento.Parametros.rover_ip,
RuasPercorrer = null,
Mapa = null,
DadosLeitura = new OperacaoParametrosDadosModel(),
@ -382,7 +384,7 @@ namespace AgroBase.Models
MpcMatrizCusto = false,
MpcHorizonte = 4.0,
MovVelocidadeSErvasPercent = 100,
MovVelocidadeSErvasPercent = 50,
MovVelocidadeCErvasPercent = 15,
DirAnguloMaximo = 25,
DirVelocidadeMovimento = 40,
@ -832,6 +834,7 @@ namespace AgroBase.Models
if (isagrobase)
{
Variaveis.OperacaoEmAndamento.Parametros = p;
Variaveis.OperacaoEmAndamento.DefinirComponentesEmUso();
Variaveis.OperacaoEmAndamento.Trajetoria = new TrajetoriaMapaOperacaoModel(new List<List<GPSModel>>());
Variaveis.OperacaoEmAndamento.Sensoriamento = new OperacaoSensoriamentoLogModel();
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("configurado", false));
@ -891,36 +894,38 @@ namespace AgroBase.Models
case T_Code.Mov:
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;
}
break;
case T_Code.Dir:
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;
}
break;
case T_Code.Atu:
var da = Variaveis.OperacaoEmAndamento.DispAtu.Dados;
var da = Variaveis.OperacaoEmAndamento.DispAtu?.Dados;
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)
{
s.Mandatorio = c.Value.Item1;
s.Aferir = c.Value.Item2;
continue;
}
var bc = da.BicosPulverizadores.FirstOrDefault(x => x.ID == c.Key);
var bc = da?.BicosPulverizadores?.FirstOrDefault(x => x.ID == c.Key);
if (bc != null)
{
bc.Mandatorio = c.Value.Item1;
bc.Comandar = c.Value.Item2;
continue;
}
var bo = da.BombasPressurizadoras.FirstOrDefault(x => x.ID == c.Key);
var bo = da?.BombasPressurizadoras?.FirstOrDefault(x => x.ID == c.Key);
if (bo != null)
{
bo.Mandatorio = c.Value.Item1;
@ -930,31 +935,31 @@ namespace AgroBase.Models
}
break;
case T_Code.Sen:
var ds = Variaveis.OperacaoEmAndamento.DispSen.Dados;
var ds = Variaveis.OperacaoEmAndamento.DispSen?.Dados;
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)
{
s.Mandatorio = c.Value.Item1;
s.Aferir = c.Value.Item2;
continue;
}
var sv = ds.Servos.FirstOrDefault(x => x.ID == c.Key);
var sv = ds?.Servos?.FirstOrDefault(x => x.ID == c.Key);
if (sv != null)
{
sv.Mandatorio = c.Value.Item1;
sv.Controlar = c.Value.Item2;
continue;
}
var rl = ds.Reles.FirstOrDefault(x => x.ID == c.Key);
var rl = ds?.Reles?.FirstOrDefault(x => x.ID == c.Key);
if (rl != null)
{
rl.Mandatorio = c.Value.Item1;
rl.Controlar = c.Value.Item2;
continue;
}
var ld = ds.Sinaleiros.FirstOrDefault(x => x.ID == c.Key);
var ld = ds?.Sinaleiros?.FirstOrDefault(x => x.ID == c.Key);
if (ld != null)
{
ld.Mandatorio = c.Value.Item1;
@ -973,15 +978,17 @@ namespace AgroBase.Models
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.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>();
if (Parametros.Mapa != null)
@ -1491,7 +1498,8 @@ namespace AgroBase.Models
{
case BotoesJoystick.Share: // Requisitar parametros da operacao
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));
break;
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)
{
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;
ErroLateral = Sonar?.Analises?.segmentacao?.erro_angular ?? 0;

View File

@ -49,17 +49,24 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
self.last_heartbeat_ts = time.time()
self.rover_id = None
self.sub = False
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_last_attempt_ts = 0.0
self._mqtt_backoff_s = 1.0 # começa rápido
self._mqtt_backoff_max_s = 15.0 # limite
self._mqtt_conectado = 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_future = None
@ -152,128 +159,208 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
finally:
self._running = False
def _start_mqtt_heartbeat(self):
try:
if self._mqtt_conectado:
# MQTT HEARTBEAT
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
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
return
self._mqtt = mqtt.Client()
self._mqtt.on_connect = self._on_connect
self._mqtt.on_message = self._on_message
self._mqtt.on_disconnect = self._on_disconnect
self._mqtt.connect(base_ip, 1883, 60)
self._mqtt.loop_start()
except:
#pass
print("Erro ao se conectar no broker mqtt")
self._mqtt_connecting = False
self._heartbeat_ok = False
self.sub = False
self._mqtt_last_attempt_ts = time.time()
self._mqtt_backoff_s = max(self._mqtt_backoff_s, 5.0)
if reason:
self._mqtt_mostra_log(f"Resetando client MQTT: {reason}")
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):
"""Dispara tentativa de conexão MQTT sem bloquear o loop."""
now = time.time()
with self._mqtt_lock:
if self._mqtt_conectado:
return
if self._mqtt_connecting:
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:
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_connecting = True
base_ip = self.get_base_ip()
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_mostra_log("tentando conectar mqtt heartbeat")
self._mqtt_future = self._mqtt_executor.submit(self._mqtt_connect_worker, base_ip)
def _mqtt_connect_worker(self, base_ip: str):
"""Roda em background. Pode bloquear aqui sem travar o health."""
client = None
try:
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_message = self._on_message
client.on_disconnect = self._on_disconnect
# dica V1: keepalive menor ajuda a detectar quedas mais rápido
keepalive = 15
# conecta (bloqueante)
client.connect(base_ip, 1883, keepalive)
# se chegou aqui, iniciou. loop_start é não-bloqueante
client.connect(base_ip, 1883, 15)
client.loop_start()
# troca o client com lock (evita race)
with self._mqtt_lock:
# se tinha um client antigo, tenta parar
if self._mqtt is not None:
# só assume como ativo depois de iniciar sem exception
old = self._mqtt
self._mqtt = client
if old is not None and old is not client:
try:
self._mqtt.loop_stop()
self._mqtt.disconnect()
except:
old.loop_stop()
except Exception:
pass
try:
old.disconnect()
except Exception:
pass
self._mqtt = client
# on_connect vai setar _mqtt_conectado quando confirmar
self._mqtt_mostra_log(f"mqtt heartbeat conectado [client={cid}]")
except Exception:
# falhou: libera pra tentar de novo com backoff maior
except Exception as e:
with self._mqtt_lock:
self._mqtt_conectado = 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):
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:
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
# 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._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")
if self.sub == False:
topic = f"agrobot/v1/rover/{self.rover_id}/heartbeat"
client.subscribe(topic)
print("[HEARTBEAT] Subscribado em:", topic)
self.sub = True
result, mid = client.subscribe(topic)
self.sub = (result == 0)
self._mqtt_mostra_log(f"[client={cid}] Subscribado em: {topic} | result={result} mid={mid}")
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):
cid = getattr(client, "_agro_client_local_id", "?")
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_connecting = False
self._heartbeat_ok = False
self.sub = False
print("[HEARTBEAT] MQTT desconectado!", rc)
self._mqtt = None
# se rc != 0 significa desconexão inesperada
if rc != 0:
print("[HEARTBEAT] Desconexão inesperada — pode ter perdido o link 900 MHz")
# adiciona isso:
self._mqtt_last_attempt_ts = time.time()
self._mqtt_backoff_s = max(self._mqtt_backoff_s, 5.0)
# Marca como "sem heartbeat" instantâneo (opcional)
# Aqui podemos forçar atraso grande
self.last_heartbeat_ts = 0
self._mqtt_mostra_log(f"[client={cid}] MQTT desconectado! rc={rc} | t={time.time():.3f}")
def _on_message(self, client, userdata, msg):
topic = msg.topic
if topic == f"agrobot/v1/rover/{self.rover_id}/heartbeat":
self.last_heartbeat_ts = time.time()
#print("[HEARTBEAT] recebido")
cid = getattr(client, "_agro_client_local_id", "?")
with self._mqtt_lock:
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):
for d in (
@ -654,6 +741,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
distancia_base = m.get("distancia_base", -1)
self._ajustar_limites_banda_por_distancia(distancia_m=distancia_base)
self._check_mqtt_heartbeat()
self._start_mqtt_heartbeat_async()
SAUDE_MIN_ALERTA = 80

View File

@ -865,10 +865,10 @@ namespace OperationControl.Controls
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>() };
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 type = ((dynamic)x.Feature).Geometry.GeometryType;

View File

@ -82,7 +82,7 @@ namespace OperationControl.Models
public static readonly string BaseMarkerID = "BASE";
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 AtualizandoListaRovers = false;
public static AgroBase.Models.Enums.StatusModulo StatusBase
@ -153,7 +153,7 @@ namespace OperationControl.Models
OperacaoParametrosModel rover;
lock (_RoversLock)
{
rover = RoversNaRede.FirstOrDefault(x => x.RoverId == device_id);
rover = RoversNaRede?.FirstOrDefault(x => x.RoverId == device_id);
if (rover != null)
{
obj.Momento = DateTime.Now;
@ -189,10 +189,8 @@ namespace OperationControl.Models
var rover = RoversNaRede.FirstOrDefault(x => x.RoverId == device_id);
if (rover != null)
{
obj.UltimoContato = DateTime.Now;
obj.IP = rover.IP;
RoversNaRede.Remove(rover);
RoversNaRede.Add(obj);
rover = obj;
rover.UltimoContato = DateTime.Now;
}
}
((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.RemoverAlertaDock(rover.RoverId, AgroBase.Models.Enums.T_Code.Ipb);
}
((App)Application.Current).Shell.Dock?._vm?.AtualizarBarraSuperior(rover);
}
Application.Current.Dispatcher.BeginInvoke(new Action(() =>

View File

@ -746,12 +746,12 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento.Diagnostico
case 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("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("Latência", "ms", conexao?.detalhes?["avg_rtt"]?.Value<double?>());
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?>());
break;
case T_Code.Gps:

View File

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

View File

@ -366,8 +366,26 @@ namespace OperationControl.ViewModels
if (rover == null || obj == null)
return;
if (rover.RoverId != VariaveisControleOperacao.SelectedRoverId)
return;
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)
{
// ============================
@ -535,6 +553,7 @@ namespace OperationControl.ViewModels
// Badge curto
badge = principal.Fonte switch
{
"Comunicacao" => "DESCONECTADO",
"Emergencia" => "EMERGÊNCIA",
"Pausa" => "PAUSADO",
"Trajetoria" => "TRAJETÓRIA",
@ -546,6 +565,7 @@ namespace OperationControl.ViewModels
// Resumo curto
resumo = principal.Fonte switch
{
"Comunicacao" => "Sem telemetria",
"Emergencia" => "Parada imediata",
"Pausa" => "Aguardando retomada",
"Trajetoria" => "Operação bloqueada",
@ -968,6 +988,15 @@ namespace OperationControl.ViewModels
}
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)
return;

View File

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