From 8ee9368c8ba12a1dcabf1cce2c6a76e9cead0e4d Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Tue, 17 Mar 2026 14:26:07 -0300 Subject: [PATCH] Ajustes de conectividade mqtt --- AgroBase/AgroBase/Forms/frmPrincipal.cs | 2 +- AgroBase/AgroBase/Models/GeneralJoystick.cs | 2 +- .../Models/Operacoes/OperacaoModel.cs | 52 ++-- .../Models/Operadores/VisualWorkerModel.cs | 2 +- .../workers/health_worker/modulos/ponte_ip.py | 248 ++++++++++++------ .../Controls/MapViewControl.xaml.cs | 4 +- AgroBase/OperationControl/Models/Variaveis.cs | 11 +- .../Diagnostico/DiagnosticoViewModel.cs | 12 +- .../Monitoramento/ParametrizacaoViewModel.cs | 6 + .../ViewModels/Windows/DockWindowViewModel.cs | 29 ++ .../Views/Operacao/OperacaoCenterView.xaml.cs | 1 + 11 files changed, 250 insertions(+), 119 deletions(-) diff --git a/AgroBase/AgroBase/Forms/frmPrincipal.cs b/AgroBase/AgroBase/Forms/frmPrincipal.cs index de0d052ff..8b3311cf4 100644 --- a/AgroBase/AgroBase/Forms/frmPrincipal.cs +++ b/AgroBase/AgroBase/Forms/frmPrincipal.cs @@ -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; diff --git a/AgroBase/AgroBase/Models/GeneralJoystick.cs b/AgroBase/AgroBase/Models/GeneralJoystick.cs index 61d287761..3079a75fa 100644 --- a/AgroBase/AgroBase/Models/GeneralJoystick.cs +++ b/AgroBase/AgroBase/Models/GeneralJoystick.cs @@ -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; } } diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index d84c6fb3a..df1c02676 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -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>()); 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; - d.Comandar = c.Value.Item2; + 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; - d.Comandar = c.Value.Item2; + 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 ruasSelecionadas = Parametros.RuasPercorrer ?? new List(); 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("", 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 diff --git a/AgroBase/AgroBase/Models/Operadores/VisualWorkerModel.cs b/AgroBase/AgroBase/Models/Operadores/VisualWorkerModel.cs index cf844463e..767d79eda 100644 --- a/AgroBase/AgroBase/Models/Operadores/VisualWorkerModel.cs +++ b/AgroBase/AgroBase/Models/Operadores/VisualWorkerModel.cs @@ -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; diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/ponte_ip.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/ponte_ip.py index 53771490c..3a27db0b1 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/ponte_ip.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/ponte_ip.py @@ -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,129 +159,209 @@ 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: - 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") + + 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_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: - try: - self._mqtt.loop_stop() - self._mqtt.disconnect() - except: - pass - + # só assume como ativo depois de iniciar sem exception + old = self._mqtt self._mqtt = client - # on_connect vai setar _mqtt_conectado quando confirmar - except Exception: - # falhou: libera pra tentar de novo com backoff maior + if old is not None and old is not client: + try: + old.loop_stop() + except Exception: + pass + try: + old.disconnect() + except Exception: + pass + + self._mqtt_mostra_log(f"mqtt heartbeat conectado [client={cid}]") + + 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 + topic = f"agrobot/v1/rover/{self.rover_id}/heartbeat" + + 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") - - # Marca como "sem heartbeat" instantâneo (opcional) - # Aqui podemos forçar atraso grande - self.last_heartbeat_ts = 0 + # adiciona isso: + self._mqtt_last_attempt_ts = time.time() + self._mqtt_backoff_s = max(self._mqtt_backoff_s, 5.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 ( self.rtts_f, self.timeouts_f, self.loses_f, @@ -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 diff --git a/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs b/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs index 6aeed9358..b937ca72a 100644 --- a/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs +++ b/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs @@ -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() }; - 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; diff --git a/AgroBase/OperationControl/Models/Variaveis.cs b/AgroBase/OperationControl/Models/Variaveis.cs index 224b08748..60a03211b 100644 --- a/AgroBase/OperationControl/Models/Variaveis.cs +++ b/AgroBase/OperationControl/Models/Variaveis.cs @@ -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(() => diff --git a/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/Diagnostico/DiagnosticoViewModel.cs b/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/Diagnostico/DiagnosticoViewModel.cs index 898d0d39d..7aa07a855 100644 --- a/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/Diagnostico/DiagnosticoViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/Diagnostico/DiagnosticoViewModel.cs @@ -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()); - AddOrAppendSerie("Jitter", "ms", conexao?.detalhes?["jitter"]?.Value()); - AddOrAppendSerie("Timeout", "%", conexao?.detalhes?["tmout_pct"]?.Value()); - AddOrAppendSerie("Perda", "%", conexao?.detalhes?["loss_pct"]?.Value()); - AddOrAppendSerie("Banda", "Mbps", conexao?.detalhes?["bw_total_mbps"]?.Value()); - AddOrAppendSerie("Banda %", "%", conexao?.detalhes?["bw_util_pct"]?.Value()); + AddOrAppendSerie("Latência", "ms", conexao?.detalhes?["avg_rtt"]?.Value()); + AddOrAppendSerie("Jitter", "ms", conexao?.detalhes?["jitter"]?.Value()); + AddOrAppendSerie("Timeout", "%", conexao?.detalhes?["tmout_pct"]?.Value()); + AddOrAppendSerie("Perda", "%", conexao?.detalhes?["loss_pct"]?.Value()); + AddOrAppendSerie("Banda", "Mbps", conexao?.detalhes?["bw_total_mbps"]?.Value()); + AddOrAppendSerie("Banda %", "%", conexao?.detalhes?["bw_util_pct"]?.Value()); break; case T_Code.Gps: diff --git a/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/ParametrizacaoViewModel.cs b/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/ParametrizacaoViewModel.cs index 0be7e039f..1671c7868 100644 --- a/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/ParametrizacaoViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/ParametrizacaoViewModel.cs @@ -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) diff --git a/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs b/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs index 57c4974b9..08b583ddb 100644 --- a/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs @@ -366,8 +366,26 @@ namespace OperationControl.ViewModels if (rover == null || obj == null) return; + if (rover.RoverId != VariaveisControleOperacao.SelectedRoverId) + return; + var eventos = new List(); + 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(); + + 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; diff --git a/AgroBase/OperationControl/Views/Operacao/OperacaoCenterView.xaml.cs b/AgroBase/OperationControl/Views/Operacao/OperacaoCenterView.xaml.cs index c5a3ead3f..6663e2809 100644 --- a/AgroBase/OperationControl/Views/Operacao/OperacaoCenterView.xaml.cs +++ b/AgroBase/OperationControl/Views/Operacao/OperacaoCenterView.xaml.cs @@ -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); } }