diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 24be6721a..6af63eaa2 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -2482,6 +2482,7 @@ namespace AgroBase.Models EnergiaConsumida_Wh = _Bateria.EnergiaConsumida_Wh, WhPorMetro = _Bateria.WhPorMetro, DistanciaEstimadaRestanteMetros = _Bateria.DistanciaEstimadaRestante_m, + Temperatura = _Bateria.Temperatura, Barramentos = _DispSen?.Dados?.Sensores?.Where(x => x.Componente == S_Code.sCOR)?.Select(x => new OperacaoSensoriamentoLogBateriaBarramentoModel() { ID = x.ID, @@ -2823,6 +2824,7 @@ namespace AgroBase.Models public double WhPorMetro { get; set; } public double TempoEstimadoRestanteMinutos { get; set; } public double DistanciaEstimadaRestanteMetros { get; set; } + public double Temperatura { get; set; } public List Barramentos { get; set; } public OperacaoSensoriamentoLogBateriaModel Clone() @@ -2841,6 +2843,7 @@ namespace AgroBase.Models WhPorMetro = WhPorMetro, TempoEstimadoRestanteMinutos = TempoEstimadoRestanteMinutos, DistanciaEstimadaRestanteMetros = DistanciaEstimadaRestanteMetros, + Temperatura = Temperatura, Barramentos = new List(Barramentos ?? new List()) }; } diff --git a/AgroBase/AgroBase/Services/CoulombCounterService.cs b/AgroBase/AgroBase/Services/CoulombCounterService.cs index cd71bdea2..d7f2e11f8 100644 --- a/AgroBase/AgroBase/Services/CoulombCounterService.cs +++ b/AgroBase/AgroBase/Services/CoulombCounterService.cs @@ -88,6 +88,7 @@ namespace AgroBase.Services return CargaAtualAmp * tensaoNominal; // Ah * V = Wh } } + public double Temperatura { get; private set; } public double WhPorMetro { get; private set; } public double WhPorMetroBase { get; set; } // configurado externamente @@ -123,6 +124,7 @@ namespace AgroBase.Services double bms_corrente = DalyBms?.Pack?.Corrente_A ?? 0; double bms_potencia = bms_tensao * bms_corrente; double bms_soc = DalyBms?.Pack?.SOC_percent ?? 0; + double bms_tmp = DalyBms?.TemperaturaMax ?? 0; var Sensor = Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Sensores?.FirstOrDefault(x => x.ID == "AB36V"); bool sen_iniciado = Sensor?.Inicializado ?? false; @@ -132,6 +134,7 @@ namespace AgroBase.Services double sen_corrente = (Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Corrente)?.atual?.valor ?? 0) / 1000.0; double sen_potencia = Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0; double sen_soc = Math.Round(FuncoesMatematicas.Map(sen_tensao, VariaveisEquipamento.TensaoMinimaBateria, VariaveisEquipamento.TensaoMaximaBateria, 0.0, 100.0), 2); + double sen_tmp = Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Temperatura)?.atual?.valor ?? 0; Iniciado = bms_iniciado || sen_iniciado; BmsLigado = bms_iniciado; @@ -145,6 +148,7 @@ namespace AgroBase.Services CorrenteInstantanea = BmsLigado ? bms_corrente : sen_corrente; PotenciaInstantanea = BmsLigado ? bms_potencia : sen_potencia; PercentualBateria = BmsLigado ? bms_soc : sen_soc; + Temperatura = BmsLigado ? bms_tmp : sen_tmp; PercentualBateria = FuncoesMatematicas.Clamp(PercentualBateria, 0, 100); // Verifique se as datas são válidas (não MinValue, MaxValue ou valores anômalos) diff --git a/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs b/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs index 097273218..b6d786fac 100644 --- a/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs +++ b/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs @@ -821,6 +821,16 @@ namespace AgroBase.Services.Operadores ("motivo", DadosTrj?.AutonomiaCooredor?.Motivo) ); + double distanciaBase = -1; + if (VariaveisOperacao.PosicaoBase?.Inicializado == true && _Sensoriamento?.Gps?.Inicializado == true) + { + distanciaBase = GPSUtils.DistanciaEntrePontos(VariaveisOperacao.PosicaoBase, _Sensoriamento.Gps); + } + RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Ipb), + ("timestamp", agora), + ("iniciado", Variaveis.MqttServiceBase?.StatusConexao() ?? false), + ("distancia_base", distanciaBase) + ); } diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_gal.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_gal.py index 22105c329..3e0d3545c 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_gal.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_gal.py @@ -470,7 +470,7 @@ class CameraGal: ipb = ContextoGlobalRedis.get_modulo(T_Code.Ipb) or {} # ajuste se sua key for diferente saude = (ipb.get("saude") or {}) detalhes = (saude.get("detalhes") or {}) - return float(detalhes.get("bw_util_pct") or 0.0) + return float(detalhes.get("bw_pressure_pct") or detalhes.get("bw_util_pct") or 0.0) def _mode_params(self, mode: int): # mode 0..3 diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_oak.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_oak.py index 3c46866be..4e2ade4c9 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_oak.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/camera_oak.py @@ -797,7 +797,7 @@ class CameraOak: ipb = ContextoGlobalRedis.get_modulo(T_Code.Ipb) or {} # ajuste se sua key for diferente saude = (ipb.get("saude") or {}) detalhes = (saude.get("detalhes") or {}) - return float(detalhes.get("bw_util_pct") or 0.0) + return float(detalhes.get("bw_pressure_pct") or detalhes.get("bw_util_pct") or 0.0) def _mode_params(self, mode: int): # mode 0..3 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 9ea624794..506c99e49 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 @@ -1,3 +1,4 @@ +import math import re import time import threading @@ -567,10 +568,92 @@ class ModuloIPBribge(ModuloDiagnosticoBase): state = "ALTO" if x >= 0.5 else "MEDIO" return max(0.0, min(100.0, score)), util_pct, state + def _bandwidth_pressure_pct(self, bw_util_pct: float, ls: float, tmout: float, los: float, js: float, ns: float, hbs: float): + """ + Pressão real do link para guiar vídeo adaptativo. + 0 = folgado + 100 = estrangulado + """ + + bw_util_pct = max(0.0, min(100.0, float(bw_util_pct or 0.0))) + + p_lat = 100.0 - max(0.0, min(100.0, float(ls or 0.0))) + p_tmout = 100.0 - max(0.0, min(100.0, float(tmout or 0.0))) + p_loss = 100.0 - max(0.0, min(100.0, float(los or 0.0))) + p_jit = 100.0 - max(0.0, min(100.0, float(js or 0.0))) + p_nic = 100.0 - max(0.0, min(100.0, float(ns or 0.0))) + p_hb = 100.0 - max(0.0, min(100.0, float(hbs or 0.0))) + + pressure_rel = ( + 0.30 * p_tmout + + 0.25 * p_loss + + 0.15 * p_hb + + 0.15 * p_jit + + 0.10 * p_lat + + 0.05 * p_nic + ) + + # gatilhos de proteção + if tmout < 50: + pressure_rel = max(pressure_rel, 85.0) + if los < 50: + pressure_rel = max(pressure_rel, 85.0) + if hbs < 50: + pressure_rel = max(pressure_rel, 80.0) + if js < 30: + pressure_rel = max(pressure_rel, 75.0) + + pressure = max(bw_util_pct, pressure_rel) + return round(max(0.0, min(100.0, pressure)), 1) + + def _interp_log(self, x, x0, x1, y0, y1): + x = max(x0, min(x, x1)) + t = (math.log10(x) - math.log10(x0)) / (math.log10(x1) - math.log10(x0)) + return y0 + t * (y1 - y0) + + def _ajustar_limites_banda_por_distancia(self, distancia_m: float): + """ + Ajusta bw_max/safe/hard com base na distância da base. + Curva conservadora para Wi-Fi HaLow 915 MHz em campo. + """ + # fallback quando não há posição válida + if distancia_m is None or distancia_m < 0: + self.bw_max_mbps = 3.0 + self.bw_safe_mbps = 2.0 + self.bw_hard_mbps = 2.5 + return + + # limita a curva + d = max(10.0, min(float(distancia_m), 1200.0)) + + # 10 m -> 10 Mbps + # 1200 m -> 0.4 Mbps + bw_max = self._interp_log(d, 10.0, 1200.0, 10.0, 0.4) + + # opcional: um pequeno piso e teto + bw_max = max(0.35, min(bw_max, 10.0)) + + bw_safe = bw_max * 0.68 + bw_hard = bw_max * 0.82 + + # garantias mínimas + bw_safe = max(0.20, bw_safe) + bw_hard = max(bw_safe + 0.05, bw_hard) + + self.bw_max_mbps = round(bw_max, 3) + self.bw_safe_mbps = round(bw_safe, 3) + self.bw_hard_mbps = round(bw_hard, 3) + + + def atualizar_saude_interno(self): #print("atualizando saude IPB...") try: + m = ContextoGlobalRedis.get_modulo(self.t_code) or {} + distancia_base = m.get("distancia_base", -1) + self._ajustar_limites_banda_por_distancia(distancia_m=distancia_base) + self._start_mqtt_heartbeat_async() SAUDE_MIN_ALERTA = 80 @@ -688,6 +771,16 @@ class ModuloIPBribge(ModuloDiagnosticoBase): bw_util_pct = bw_util_pct_s bw_state = bw_state_s + bw_pressure_pct = self._bandwidth_pressure_pct( + bw_util_pct=bw_util_pct, + ls=ls, + tmout=tmout, + los=los, + js=js, + ns=ns, + hbs=hbs, + ) + # 1) Latência cond_lat = [] if ls_f < SAUDE_MIN_ALERTA: @@ -915,6 +1008,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase): "saude_individual": saude_individual, "condicoes_operacionais": condicoes, "detalhes": { + "d_base": distancia_base, "ls": ls, "tmout": tmout, "los": los, @@ -936,6 +1030,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase): "bw_max_mbps": self.bw_max_mbps, "bw_safe_mbps": self.bw_safe_mbps, "bw_hard_mbps": self.bw_hard_mbps, + "bw_pressure_pct": bw_pressure_pct, } } diff --git a/AgroBase/OperationControl/Models/Variaveis.cs b/AgroBase/OperationControl/Models/Variaveis.cs index d7fd724c4..88c955974 100644 --- a/AgroBase/OperationControl/Models/Variaveis.cs +++ b/AgroBase/OperationControl/Models/Variaveis.cs @@ -41,6 +41,9 @@ namespace OperationControl.Models try { var p = message.Mensagem.Split(','); + if (string.IsNullOrEmpty(message.Mensagem) || p.Length < 2) + return; + string device_id = p[0]; string device_ip = p[1]; VariaveisControleOperacao.AdicionarNovoRoverNaRede(device_id, device_ip); diff --git a/AgroBase/OperationControl/Windows/MainWindow.xaml.cs b/AgroBase/OperationControl/Windows/MainWindow.xaml.cs index 9e3b7e49a..5cc5d5ef2 100644 --- a/AgroBase/OperationControl/Windows/MainWindow.xaml.cs +++ b/AgroBase/OperationControl/Windows/MainWindow.xaml.cs @@ -4,10 +4,7 @@ using OperationControl.Controls; using OperationControl.Models; using System.Windows.Controls; using System.Windows.Media; -using LibVLCSharp.Shared; using System.Windows.Input; -using System.IO; -using MediaPlayer = LibVLCSharp.Shared.MediaPlayer; using ComboBox = System.Windows.Controls.ComboBox; using Application = System.Windows.Application; using MessageBox = System.Windows.MessageBox; @@ -27,11 +24,6 @@ namespace OperationControl.Windows /// public partial class MainWindow : Window, INotifyPropertyChanged { - //private LibVLC _libVLC; - //private MediaPlayer _playerFront; - //private MediaPlayer _playerWeed; - //private Media _mediaFront; - //private Media _mediaWeed; private TcpVideoReceiver videoFront; private TcpVideoReceiver videoWeed; @@ -72,30 +64,6 @@ namespace OperationControl.Windows DataContext = this; - - //DataContext = this; - - // atualizar as caixas - //BBoxes3D.SetBboxes(new[] - //{ - // new AgroBase.Models.LivoxBboxModel{ cx=1.2, cy=0.5, cz=0.4, d=0.6, w=0.4, h=0.8 }, - // new AgroBase.Models.LivoxBboxModel{ cx=3.0, cy=-0.7, cz=0.6, d=0.8, w=0.5, h=1.2 }, - //}); - - - // Opções focadas em baixa latência - //_libVLC = new LibVLC( - // "--network-caching=1000", // ms (ajusta depois) - // "--clock-jitter=0", - // "--clock-synchro=0" - //); - - // Player da câmera frontal - //_playerFront = new MediaPlayer(_libVLC); - //VideoFront.MediaPlayer = _playerFront; - //_playerWeed = new MediaPlayer(_libVLC); - //VideoWeed.MediaPlayer = _playerWeed; - videoFront = new TcpVideoReceiver(5000, imgVideoFront); videoWeed = new TcpVideoReceiver(5002, imgVideoWeed); @@ -124,7 +92,6 @@ namespace OperationControl.Windows { PararStreamCameraFrontal(true); PararStreamCameraErvas(true); - //_libVLC?.Dispose(); } #region DISPOSITIVOS @@ -1052,12 +1019,6 @@ namespace OperationControl.Windows Valor = perda }, new GraficoModel.GraficoParametrosModel() - { - Label = "Erro", - Unidade = "%", - Valor = errRate - }, - new GraficoModel.GraficoParametrosModel() { Label = "Banda", Unidade = "Mbps", @@ -1101,6 +1062,30 @@ namespace OperationControl.Windows }, }); break; + case AgroBase.Models.Enums.T_Code.Bat: + var bat = VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Bateria; + graficoAcompanhamento.Atualizar(inicial, momento, new List() + { + new GraficoModel.GraficoParametrosModel() + { + Label = "Tensão", + Unidade = "V", + Valor = bat?.TensaoInstantanea + }, + new GraficoModel.GraficoParametrosModel() + { + Label = "Corrente", + Unidade = "A", + Valor = bat?.CorrenteInstantanea + }, + new GraficoModel.GraficoParametrosModel() + { + Label = "Temperatura", + Unidade = "°C", + Valor = bat?.Temperatura + }, + }); + break; } graficoAcompanhamento.Limpar(momento); @@ -1958,39 +1943,12 @@ namespace OperationControl.Windows private void IniciarStreamCameraFrontal() { - //var path = @"streams\camera_frontal.sdp"; - //if (!File.Exists(path)) - //{ - // string stream = - // "v=0\r\n" + - // "o=- 0 0 IN IP4 0.0.0.0\r\n" + - // "s=OAK H264\r\n" + - // "c=IN IP4 0.0.0.0\r\n" + - // "t=0 0\r\n" + - // "m=video 5000 RTP/AVP 96\r\n" + - // "a=rtpmap:96 H264/90000\r\n" + - // "a=fmtp:96 packetization-mode=1\r\n"; - // File.WriteAllText(path, stream); - //} - //_mediaFront?.Dispose(); // limpa antigo, se existir - //_mediaFront = new Media(_libVLC, path, FromType.FromPath); - //_playerFront.Play(_mediaFront); - videoFront.Iniciar(); VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Snr, true, ((AgroBase.Models.Enums.TipoFrameCamera)cmbCameraFrontalFrameTipo.SelectedIndex)); } private void PararStreamCameraFrontal(bool finalizar = false) { - //_playerFront?.Stop(); - //if (finalizar) - //{ - // _mediaFront?.Dispose(); - // _mediaFront = null; - // _playerFront?.Dispose(); - // _playerFront = null; - //} - videoFront.Parar(); VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Snr, false, ((AgroBase.Models.Enums.TipoFrameCamera)cmbCameraFrontalFrameTipo.SelectedIndex)); } @@ -2008,40 +1966,12 @@ namespace OperationControl.Windows private void IniciarStreamCameraErvas() { - //var path = @"streams\camera_ervas.sdp"; - //if (!File.Exists(path)) - //{ - // string stream = - // "v=0\r\n" + - // "o=- 0 0 IN IP4 0.0.0.0\r\n" + - // "s=OAK H264\r\n" + - // "c=IN IP4 0.0.0.0\r\n" + - // "t=0 0\r\n" + - // "m=video 5002 RTP/AVP 96\r\n" + // <- porta 5001 - // "a=rtpmap:96 H264/90000\r\n" + - // "a=fmtp:96 packetization-mode=1\r\n"; - // File.WriteAllText(path, stream); - //} - //_mediaWeed?.Dispose(); - //_mediaWeed = new Media(_libVLC, path, FromType.FromPath); - //_mediaWeed.AddOption(":network-caching=150"); - //_playerWeed.Play(_mediaWeed); - videoWeed.Iniciar(); VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Cam, true, ((AgroBase.Models.Enums.TipoFrameCamera)cmbCameraTraseiraFrameTipo.SelectedIndex)); } private void PararStreamCameraErvas(bool finalizar = false) { - //_playerWeed?.Stop(); - //if (finalizar) - //{ - // _mediaWeed?.Dispose(); - // _mediaWeed = null; - // _playerWeed?.Dispose(); - // _playerWeed = null; - //} - videoWeed.Parar(); VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Cam, false, ((AgroBase.Models.Enums.TipoFrameCamera)cmbCameraTraseiraFrameTipo.SelectedIndex)); }