From 7398624ad7da49b6206a7f7b81ab167f7449cfdb Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Fri, 13 Mar 2026 11:05:41 -0300 Subject: [PATCH] inclusao de motivos de controle e trajetoria --- .../Models/Operacoes/OperacaoModel.cs | 2 + .../Operacoes/OperacaoParametrosModel.cs | 7 +- .../Models/Operadores/ManagerWorkerModel.cs | 1 + .../Models/TrajetoriaMapaOperacaoModel.cs | 54 ++++--- .../Operadores/HealthWorkerService.cs | 3 +- .../Operadores/ManagerWorkerService.cs | 11 +- .../manager_worker/modulos/direcional.py | 1 + .../workers/manager_worker/modulos/mpc.py | 14 +- .../processadores/_4_em_andamento.py | 2 +- .../manager_worker/processadores/padroes.py | 3 +- .../workers/shared/contexto_global_redis.py | 6 +- .../Controls/AutoFitTextBlock.cs | 139 ++++++++++++++++++ .../ViewModels/Windows/DockWindowViewModel.cs | 12 +- .../Views/Operacao/OperacaoTopView.xaml | 9 +- 14 files changed, 221 insertions(+), 43 deletions(-) create mode 100644 AgroBase/OperationControl/Controls/AutoFitTextBlock.cs diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 4f8e5d79c..2fb86045e 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -2167,6 +2167,7 @@ namespace AgroBase.Models public double Latencia { get; set; } public double ErroLateral { get; set; } = 0.0; public Dictionary DebugCustoMpc { get; set; } = new Dictionary(); + public List Motivos { get; set; } = new List(); public void Reiniciar() { @@ -2197,6 +2198,7 @@ namespace AgroBase.Models EmFreio = EmFreio, ErroLateral = ErroLateral, DebugCustoMpc = new Dictionary(DebugCustoMpc ?? new Dictionary()), + Motivos = new List(Motivos ?? new List()) }; } } diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs index 6a5e2007b..9b6901d52 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs @@ -534,6 +534,7 @@ namespace AgroBase.Models.Operacoes HerbicidaSuficiente = t.AutonomiaCooredor.HerbicidaSuficiente, Liberado = t.AutonomiaCooredor.Liberado, Motivo = t.AutonomiaCooredor.Motivo, + Motivos = t.AutonomiaCooredor.Motivos.ToList(), Status = t.AutonomiaCooredor.Status }, AnguloCaminho = t.AnguloCaminho, @@ -566,6 +567,7 @@ namespace AgroBase.Models.Operacoes PercentualVelocidadeSPKmh = c.PercentualVelocidadeSPKmh, TipoMovimento = c.TipoMovimento, SimulacaoMPC = c.SimulacaoMPC, + Motivos = c.Motivos } : new OperacaoParametrosDadosControleModel(); ModulosSaude = new List(sen?.ModulosSaude ?? new List()); DispositivosMapeados = new List(sen?.DispositivosMapeados ?? new List()); @@ -888,6 +890,7 @@ namespace AgroBase.Models.Operacoes public bool HerbicidaSuficiente { get; set; } public bool Liberado { get; set; } public string Motivo { get; set; } + public List Motivos { get; set; } } @@ -898,6 +901,7 @@ namespace AgroBase.Models.Operacoes public TipoMovimentoDirecional TipoMovimento { get; set; } public double PercentualVelocidadeSPKmh { get; set; } public List SimulacaoMPC { get; set; } = new List(); + public List Motivos { get; set; } = new List(); public OperacaoParametrosDadosControleModel Clone() { @@ -907,7 +911,8 @@ namespace AgroBase.Models.Operacoes Angulo = Angulo, PercentualVelocidadeSPKmh = PercentualVelocidadeSPKmh, TipoMovimento = TipoMovimento, - SimulacaoMPC = SimulacaoMPC + SimulacaoMPC = SimulacaoMPC, + Motivos = new List(Motivos ?? new List()) }; } } diff --git a/AgroBase/AgroBase/Models/Operadores/ManagerWorkerModel.cs b/AgroBase/AgroBase/Models/Operadores/ManagerWorkerModel.cs index ed0a0b25d..ba64beffe 100644 --- a/AgroBase/AgroBase/Models/Operadores/ManagerWorkerModel.cs +++ b/AgroBase/AgroBase/Models/Operadores/ManagerWorkerModel.cs @@ -57,6 +57,7 @@ namespace AgroBase.Models.Operadores public double latencia { get; set; } public double erro_lateral { get; set; } public Dictionary debug_custo { get; set; } + public string[] motivos { get; set; } } public class ManagerWorkerMessageResponseDebugCustoModel diff --git a/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs b/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs index d0456d299..e29f8cf33 100644 --- a/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs +++ b/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs @@ -1014,6 +1014,7 @@ namespace AgroBase.Models dictParams.TryGetValue("latencia", out var _latencia); dictParams.TryGetValue("erro_lateral", out var _erro_lateral); dictParams.TryGetValue("debug_custo", out var _debug_custo); + dictParams.TryGetValue("motivos", out var _motivos); novoComando = new ManagerWorkerMessageResponseComandoModel() { angulo = Convert.ToDouble(_angulo), @@ -1025,6 +1026,7 @@ namespace AgroBase.Models latencia = Convert.ToDouble(_latencia.ToString().Replace(".", ",")), erro_lateral = Convert.ToDouble(_erro_lateral.ToString().Replace(".", ",")), debug_custo = JsonConvert.DeserializeObject>(_debug_custo.ToString()), + motivos = JsonConvert.DeserializeObject((_motivos ?? "").ToString()) }; } catch (Exception ex) @@ -1073,11 +1075,15 @@ namespace AgroBase.Models _Controle.ticks_sem_resposta = DateTime.MinValue; } if (novoComando != null) + { _Controle.heartbeat = novoComando.heartbeat; + _Controle.Motivos = novoComando.motivos.ToList(); + } if (_Controle.ticks_sem_resposta != DateTime.MinValue) { if (_Controle.ticks_sem_resposta.AddMilliseconds(max_ticks_sem_resposta) < agora) { + Variaveis.OperacaoEmAndamento.Sensoriamento?.InserirLog(T_Code.Npc, StatusModulo.Falha, 0, "Muito tempo sem receber novo comando do Manager Worker! Parando Movimento!"); Console.WriteLine("Muito tempo sem receber novo comando do Manager Worker! Parando Movimento!"); RedisService.AtualizarCampos( CtxKey.DadosControle, @@ -1086,6 +1092,7 @@ namespace AgroBase.Models } else if (_Controle.ticks_sem_resposta.AddMilliseconds(min_ticks_sem_resposta) < agora) { + Variaveis.OperacaoEmAndamento.Sensoriamento?.InserirLog(T_Code.Npc, StatusModulo.Alerta, 80, "Muito tempo sem receber novo comando do Manager Worker! Reduzindo Velocidade!"); Console.WriteLine("Muito tempo sem receber novo comando do Manager Worker! Reduzindo Velocidade!"); RedisService.AtualizarCampos( CtxKey.DadosControle, @@ -2742,6 +2749,9 @@ namespace AgroBase.Models [JsonProperty] public string Motivo { get; private set; } = ""; + [JsonProperty] + public List Motivos { get; private set; } = new List(); + // ========================== // Flags de conveniência // ========================== @@ -2785,7 +2795,7 @@ namespace AgroBase.Models if (!Iniciado) { Liberado = true; - Motivo = "Não iniciado"; + Motivos.Add("Não iniciado"); Status = AutonomiaCorredorStatus.Desconhecido; return; } @@ -2798,6 +2808,9 @@ namespace AgroBase.Models if (CorredoresLiberados.ContainsKey(idxCorredor) && CorredoresLiberados[idxCorredor].Liberado) return; + var motivosBloqueio = new List(); + Motivos = new List(); + // Atalho: interpretar que o operador “clicou em liberar” (pelo menos um sinal veio) bool solicitouLiberacaoHumana = bateria_liberada || reservatorio_liberado; @@ -2839,23 +2852,26 @@ namespace AgroBase.Models statusBase = AutonomiaCorredorStatus.Critico; if (criticoBateria) + { + motivosBloqueio.Add("Bateria insuficiente para concluir o corredor com segurança."); motivoBase += motivoBat; + } - if (!herbOk) + if (criticoHerbicida) + { + motivosBloqueio.Add("Herbicida insuficiente para pulverizar o corredor completo."); motivoBase += (string.IsNullOrEmpty(motivoBase) ? "" : "; ") + motivoHerb; + } } else { - // Não crítico if (!herbOk && !exigirPulverizacaoTotal) { - // Pode atravessar, mas não pulveriza tudo statusBase = AutonomiaCorredorStatus.OkSomenteTransito; motivoBase = motivoHerb; } else { - // Tudo ok statusBase = AutonomiaCorredorStatus.OkCompleto; } } @@ -2906,6 +2922,7 @@ namespace AgroBase.Models motivoFinal = $"Corredor {idxCorredor + 1}: BLOQUEADO. {detalhes} " + $"Aguardando liberação do operador pela base."; + motivosBloqueio.Add($"Corredor {idxCorredor + 1}: BLOQUEADO. Aguardando solução ou liberação manual do operador."); } else { @@ -2931,6 +2948,7 @@ namespace AgroBase.Models motivoFinal = $"Corredor {idxCorredor + 1}: LIBERAÇÃO MANUAL (override). " + (string.IsNullOrEmpty(motivoBase) ? "Autonomia insuficiente, mas liberado." : motivoBase); + motivosBloqueio.Add($"Corredor {idxCorredor + 1}: LIBERAÇÃO MANUAL. Autonomia insuficiente, mas houver liberação supervisionada."); // Destrava o corredor (o operador assumiu) _corredoresTravados.Remove(idxCorredor); @@ -2958,26 +2976,30 @@ namespace AgroBase.Models // ========================== Status = statusFinal; + Motivos = motivosBloqueio + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct() + .ToList(); + var res = new AutonomiaCorredorModel() { Iniciado = this.Iniciado, Liberado = liberadoFinal, Motivo = motivoFinal, + Motivos = Motivos, Status = statusFinal, DistanciaCorredor_m = DistanciaCorredor_m, DistanciaSeguraBateria_m = DistanciaSeguraBateria_m, DistanciaSeguraHerbicida_m = DistanciaSeguraHerbicida_m, }; - bool addLog = false; - if (CorredoresLiberados.ContainsKey(idxCorredor)) { // Se estava bloqueado e agora liberou, registra mudança if (CorredoresLiberados[idxCorredor].Liberado == false && liberadoFinal) { CorredoresLiberados[idxCorredor] = res; - addLog = true; + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Trj, liberadoFinal ? StatusModulo.Operante : StatusModulo.Alerta, liberadoFinal ? 100 : 0, $"Corredor {idxCorredor + 1} liberado pelo operador"); } else { @@ -2988,23 +3010,13 @@ namespace AgroBase.Models else { CorredoresLiberados.Add(idxCorredor, res); - addLog = true; - } - - if (addLog) - { - // Mantive tua ideia: loga operante quando liberado, alerta quando não - Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog( - T_Code.Trj, - liberadoFinal ? StatusModulo.Operante : StatusModulo.Alerta, - liberadoFinal ? 100 : 0, - res.Motivo - ); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Trj, liberadoFinal ? StatusModulo.Operante : StatusModulo.Alerta, liberadoFinal ? 100 : 0, $"Corredor {idxCorredor + 1} bloqueado por autonomia insuficiente"); } // Atualiza estado exposto Liberado = res.Liberado; Motivo = res.Motivo; + Motivos = res.Motivos; } // ========================== @@ -3016,7 +3028,7 @@ namespace AgroBase.Models { Iniciado = Iniciado, Liberado = Liberado, - Motivo = Motivo, + Motivos = new List(Motivos ?? new List()), Status = Status, DistanciaCorredor_m = DistanciaCorredor_m, DistanciaSeguraBateria_m = DistanciaSeguraBateria_m, diff --git a/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs b/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs index b6d786fac..dc6f97c51 100644 --- a/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs +++ b/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs @@ -94,6 +94,7 @@ namespace AgroBase.Services.Operadores ("iniciado", Variaveis.OperacaoEmAndamento.Iniciado), ("finalizando", Variaveis.OperacaoEmAndamento.Finalizando), ("calibrando", Variaveis.OperacaoEmAndamento.Calibrando), + ("simulando", Variaveis.OperacaoEmAndamento.Simulando), ("emergencia", Variaveis.OperacaoEmAndamento.Emergencia), ("pausa", Variaveis.OperacaoEmAndamento.Pausa) ); @@ -818,7 +819,7 @@ namespace AgroBase.Services.Operadores ("liberado", DadosTrj?.AutonomiaCooredor?.Liberado ?? false), ("bateria_ok", DadosTrj?.AutonomiaCooredor?.BateriaSuficiente ?? false), ("herbicida_ok", DadosTrj?.AutonomiaCooredor?.HerbicidaSuficiente ?? false), - ("motivo", DadosTrj?.AutonomiaCooredor?.Motivo) + ("motivos", DadosTrj?.AutonomiaCooredor?.Motivos ?? new List()) ); double distanciaBase = -1; diff --git a/AgroBase/AgroBase/Services/Operadores/ManagerWorkerService.cs b/AgroBase/AgroBase/Services/Operadores/ManagerWorkerService.cs index 19ee13a1a..9caa4e1f9 100644 --- a/AgroBase/AgroBase/Services/Operadores/ManagerWorkerService.cs +++ b/AgroBase/AgroBase/Services/Operadores/ManagerWorkerService.cs @@ -60,6 +60,10 @@ namespace AgroBase.Services.Operadores dictParams.TryGetValue("tipo_movimento_direcional", out object _movimento); dictParams.TryGetValue("simulacao", out object _simulacao); dictParams.TryGetValue("heartbeat", out object _heartbeat); + dictParams.TryGetValue("latencia", out var _latencia); + dictParams.TryGetValue("erro_lateral", out var _erro_lateral); + dictParams.TryGetValue("debug_custo", out var _debug_custo); + dictParams.TryGetValue("motivos", out var _motivos); novoComando = new ManagerWorkerMessageResponseComandoModel() { angulo = Convert.ToDouble(_angulo), @@ -67,7 +71,11 @@ namespace AgroBase.Services.Operadores em_freio = Convert.ToBoolean(_em_freio), tipo_movimento = (Enums.TipoMovimentoDirecional)Convert.ToInt32(_movimento), simulacao = JsonConvert.DeserializeObject>((_simulacao ?? "").ToString()), - heartbeat = Convert.ToInt32(_heartbeat) + heartbeat = Convert.ToInt32(_heartbeat), + latencia = Convert.ToDouble(_latencia.ToString().Replace(".", ",")), + erro_lateral = Convert.ToDouble(_erro_lateral.ToString().Replace(".", ",")), + debug_custo = JsonConvert.DeserializeObject>(_debug_custo.ToString()), + motivos = JsonConvert.DeserializeObject((_motivos ?? "").ToString()) }; } catch (Exception ex) @@ -76,6 +84,7 @@ namespace AgroBase.Services.Operadores } var _Controle = Variaveis.OperacaoEmAndamento.Controle; + _Controle.Motivos = (novoComando?.motivos ?? (new List()).ToArray()).ToList(); bool comandoMudou = (_Controle.Angulo != novoComando.angulo || _Controle.PercentualVelocidadeSP != novoComando.percentual_velocidade || _Controle.TipoMovimento != novoComando.tipo_movimento || _Controle.EmFreio != novoComando.em_freio); if (novoComando != null && comandoMudou) { diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/direcional.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/direcional.py index 425625d84..ace6da613 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/direcional.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/direcional.py @@ -57,6 +57,7 @@ def definir_comando(pid: PIDAdaptativo, envio_necessario: bool): Vmin = 0.1 # if _trajetoria.get("status", StatusCarroMapa.Parado.value) == StatusCarroMapa.CaminhandoRua.value else 0.8 contexto = { + "Simulando": _operacao.get("simulando", False), "GPS": { "Latitude": _gps.get("lat", 0), "Longitude": _gps.get("lon", 0), diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/mpc.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/mpc.py index 7d169499b..05115ee1a 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/mpc.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/mpc.py @@ -656,10 +656,12 @@ class ControladorMPC: pos_timestamp = GPS.get("Timestamp", 0.0) pos_latencia = now() - pos_timestamp + simulando = contexto.get("Simulando", False) + LAT_MAX = 2.0 if pos_latencia >= LAT_MAX: mostrar_log(f"[GPS] Grande latencia entre coordenadas detectado ({pos_latencia:.3f}/{LAT_MAX}): parando por fallback") - return self._comando_fallback_hot_stop(comando_anterior, latencia=pos_latencia) + return self._comando_fallback_hot_stop(comando_anterior, motivos=[] if simulando else [f"Grande latencia entre coordenadas detectado ({pos_latencia:.3f}/{LAT_MAX}): parando por fallback"], latencia=pos_latencia) if (pos_passos_atraso > 3): mostrar_log(f"[GPS] Atraso detectado de {(pos_passos_atraso / 3.0):.1f} passos, {pos_latencia:.3f} s") @@ -966,7 +968,8 @@ class ControladorMPC: "erro_lateral": erro_lateral, "erro_orientacao": erro_orientacao, "debug_custo": debug_custo, - "candidatos_testados": K + "candidatos_testados": K, + "motivos": [] } # -------------------- Debug opcional da matriz de custo -------------------- @@ -996,7 +999,7 @@ class ControladorMPC: except Exception as e: mostrar_log(f"❌ Erro ao processar MPC: {e}") - return self._comando_fallback_hot_stop(comando_anterior, latencia=pos_latencia) + return self._comando_fallback_hot_stop(comando_anterior, motivos=[f"Erro ao processar MPC: {e}"], latencia=pos_latencia) def _verifica_tempo_maximo_execucao(self, t_init, processo): @@ -1007,7 +1010,7 @@ class ControladorMPC: mostrar_log(f"🚨 Tempo maximo de execucao excedido: {delta}/{limite}, processo: {processo}") return hot_stop - def _comando_fallback_hot_stop(self, comando_anterior, latencia=0): + def _comando_fallback_hot_stop(self, comando_anterior, motivos, latencia=0): _cmd = { "enviar_comando": True, "parada_necessaria": True, @@ -1019,7 +1022,8 @@ class ControladorMPC: "erro_latreral": 0, "erro_orientacao": 0, "debug_custo": {}, - "candidatos_testados": 0 + "candidatos_testados": 0, + "motivos": motivos } return _cmd diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_4_em_andamento.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_4_em_andamento.py index 3dd6357fa..a645b068b 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_4_em_andamento.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_4_em_andamento.py @@ -73,7 +73,7 @@ class ProcessadorEmAndamento(ProcessadorBase): latencia=comando_dir.get("latencia", 0.0), erro_lateral=comando_dir.get("erro_lateral", 0.0), debug_custo=comando_dir.get("debug_custo", {}), - motivos=[] + motivos=comando_dir.get("motivos", []) if comando_dir.get("erro", False) else [] ) except Exception as e: from manager_worker.config import mostrar_log diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/padroes.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/padroes.py index d48529d6c..0c07ab5f3 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/padroes.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/padroes.py @@ -40,7 +40,8 @@ def comando_controle(percentual_velocidade: float, frear: bool, angulo: float, t "heartbeat": hb_novo, "latencia": latencia, "erro_lateral": erro_lateral, - "debug_custo": debug_custo + "debug_custo": debug_custo, + "motivos": motivos } def comando_parado(motivos=None): diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/shared/contexto_global_redis.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/shared/contexto_global_redis.py index dc310e078..86e737d45 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/shared/contexto_global_redis.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/shared/contexto_global_redis.py @@ -382,9 +382,9 @@ class ContextoGlobalRedis: trajetoria_ok = True if not trajetoria_liberada: trajetoria_ok = False - _motivo_traj = _t.get("motivo") - if _motivo_traj: - motivos_dos_modulos_mandatorios.append(_motivo_traj) + _motivos_traj = _t.get("motivos", []) + if len(_motivos_traj) > 0: + motivos_dos_modulos_mandatorios.extend(_motivos_traj) controle_ok = True _motivos_controle = cls.get_controle().get("motivos", []) diff --git a/AgroBase/OperationControl/Controls/AutoFitTextBlock.cs b/AgroBase/OperationControl/Controls/AutoFitTextBlock.cs new file mode 100644 index 000000000..54a305a5b --- /dev/null +++ b/AgroBase/OperationControl/Controls/AutoFitTextBlock.cs @@ -0,0 +1,139 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace OperationControl.Controls +{ + public class AutoFitTextBlock : TextBlock + { + public static readonly DependencyProperty MinFontSizeProperty = + DependencyProperty.Register( + nameof(MinFontSize), + typeof(double), + typeof(AutoFitTextBlock), + new PropertyMetadata(10.0, OnAutoFitPropertyChanged)); + + public static readonly DependencyProperty MaxLinesProperty = + DependencyProperty.Register( + nameof(MaxLines), + typeof(int), + typeof(AutoFitTextBlock), + new PropertyMetadata(2, OnAutoFitPropertyChanged)); + + private double _baseFontSize = 14; + private bool _isAdjusting; + + public double MinFontSize + { + get => (double)GetValue(MinFontSizeProperty); + set => SetValue(MinFontSizeProperty, value); + } + + public int MaxLines + { + get => (int)GetValue(MaxLinesProperty); + set => SetValue(MaxLinesProperty, value); + } + + public AutoFitTextBlock() + { + Loaded += AutoFitTextBlock_Loaded; + SizeChanged += (_, _) => AdjustFontSize(); + } + + private void AutoFitTextBlock_Loaded(object sender, RoutedEventArgs e) + { + _baseFontSize = FontSize; + + WatchProperty(TextProperty); + WatchProperty(FontSizeProperty); + WatchProperty(FontFamilyProperty); + WatchProperty(FontStyleProperty); + WatchProperty(FontWeightProperty); + WatchProperty(FontStretchProperty); + WatchProperty(TextWrappingProperty); + + AdjustFontSize(); + } + + private void WatchProperty(DependencyProperty dp) + { + var descriptor = DependencyPropertyDescriptor.FromProperty(dp, typeof(AutoFitTextBlock)); + descriptor?.AddValueChanged(this, (_, _) => + { + if (!_isAdjusting && dp == FontSizeProperty) + _baseFontSize = FontSize; + + AdjustFontSize(); + }); + } + + private static void OnAutoFitPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AutoFitTextBlock tb) + tb.AdjustFontSize(); + } + + private void AdjustFontSize() + { + if (_isAdjusting || !IsLoaded) + return; + + if (ActualWidth <= 0 || string.IsNullOrWhiteSpace(Text)) + return; + + _isAdjusting = true; + + try + { + double startFont = _baseFontSize; + double minFont = MinFontSize; + + FontSize = startFont; + + while (FontSize > minFont) + { + if (Fits(FontSize)) + return; + + FontSize -= 0.5; + } + + FontSize = minFont; + } + finally + { + _isAdjusting = false; + } + } + + private bool Fits(double fontSize) + { + double pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; + + var formattedText = new FormattedText( + Text ?? string.Empty, + CultureInfo.CurrentUICulture, + FlowDirection, + new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), + fontSize, + Foreground, + pixelsPerDip) + { + MaxTextWidth = Math.Max(0, ActualWidth), + Trimming = TextTrimming.None + }; + + if (TextWrapping == TextWrapping.Wrap) + formattedText.MaxLineCount = MaxLines; + + double lineHeight = fontSize * 1.25; + double maxAllowedHeight = lineHeight * MaxLines; + + return formattedText.Height <= maxAllowedHeight + 1; + } + } +} \ No newline at end of file diff --git a/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs b/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs index e40a108fe..d2efc4b14 100644 --- a/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs @@ -366,7 +366,7 @@ namespace OperationControl.ViewModels { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { - _viewOperacaoTop?._vm.AtualizarStatus(status, titulo, linha1, linha2, status_str, resumo); + _viewOperacaoTop?._vm.AtualizarStatus(status, titulo, linha1, linha2.Replace("\n\n", " • "), status_str, resumo); })); } @@ -546,8 +546,12 @@ namespace OperationControl.ViewModels if (rover.RoverId != VariaveisControleOperacao.SelectedRoverId) return; + string motivos_trajetoria = string.Join("", obj.Trajetoria?.AutonomiaCorredor?.Motivos ?? new List() { "Liberado" }); + string motivos_controle = string.Join("", obj.Controle?.Motivos ?? new List() { "Liberado" }); + AtualizarBarraSuperior(obj.StatusRover, rover.RoverId, $"Trajetória: {motivos_trajetoria}", $"Controle: {motivos_controle}", obj.StatusRover.ToString(), (obj.Operacao?.Status ?? StatusOperacao.NaoIniciado).ToString()); + // ESQUERDA - var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == AgroBase.Models.Enums.T_Code.Ipb); + var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == T_Code.Ipb); double c = conexao?.saude ?? 0; double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value() ?? 0.0; double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value() ?? 0.0; @@ -559,12 +563,12 @@ namespace OperationControl.ViewModels rover?.Descricao ?? "", obj.Controle?.PercentualVelocidadeSPKmh ?? 0, obj.Controle?.Angulo ?? 0, - obj.Controle?.TipoMovimento ?? AgroBase.Models.Enums.TipoMovimentoDirecional.Diagnostico, + obj.Controle?.TipoMovimento ?? TipoMovimentoDirecional.Diagnostico, obj.Refrigeracao?.Temperatura ?? 0, obj.Bateria?.PercentualBateria ?? 0, obj.Atuador?.PercentualReservatorio ?? 0, $"{c}%", - $"{obj.Gnss?.QualidadeFix ?? AgroBase.Models.Enums.TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm" + $"{obj.Gnss?.QualidadeFix ?? TiposCorrecaoGPS.SemCorrecao} {obj.Gnss?.PrecisaoCm:F2} cm" ); // DIREITA (RESUMO) diff --git a/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml b/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml index 779d8a09e..9c74b9ed3 100644 --- a/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml +++ b/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:controls="clr-namespace:OperationControl.Controls" mc:Ignorable="d" d:DesignWidth="1400" d:DesignHeight="92"> @@ -41,8 +42,8 @@ - - + + @@ -51,9 +52,7 @@ - - - +