diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 6af63eaa2..4f8e5d79c 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -2359,13 +2359,14 @@ namespace AgroBase.Models //var sIMU = LivoxManagerProcess.DadosLeitura.imu; string imu = RedisService.Get(RedisService.ModKey(T_Code.Imu)) ?? ""; var sIMU = JsonConvert.DeserializeObject(imu); + bool imu_iniciado = sIMU.timestamp > 0 && HealthWorkerService.ModulosSaude.FirstOrDefault(x => x.modulo == T_Code.Imu)?.status != StatusModulo.Desconectado; var dadosIMU = sIMU != null ? new OperacaoSensoriamentoLogImuModel() { - Iniciado = sIMU.timestamp > 0 && HealthWorkerService.ModulosSaude.FirstOrDefault(x => x.modulo == T_Code.Imu)?.status != StatusModulo.Desconectado, - InclinacaoLateral = sIMU.roll, - InclinacaoFrontal = sIMU.pitch, - Rotacao = sIMU.yaw, - RotacaoCorrigida = sIMU.yaw_fixed, + Iniciado = imu_iniciado, + InclinacaoLateral = imu_iniciado ? sIMU.pitch : 0, + InclinacaoFrontal = imu_iniciado ? sIMU.roll : 0, + Rotacao = imu_iniciado ? sIMU.yaw : 0, + RotacaoCorrigida = imu_iniciado ? sIMU.yaw_fixed : 0, } : new OperacaoSensoriamentoLogImuModel(); var dadosAtuador = _DispAtu != null ? new OperacaoSensoriamentoLogAtuModel() diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs index 711b41ba3..6a5e2007b 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs @@ -768,6 +768,7 @@ namespace AgroBase.Models.Operacoes }; PerformanceNcp = sen?.DadosPerformance?.Clone(); LivoxLidar = sen?.LivoxLidar?.Clone(); + CameraWorkerService.GetListaCameras(); Cameras = CameraWorkerService.ListaCameras; OperadorVisual = VisualWorkerService.DadosLeitura?.Resumo?.UpdateClone(); var l = sen?.Logs; diff --git a/AgroBase/AgroBase/Models/Operadores/CameraWorkerModel.cs b/AgroBase/AgroBase/Models/Operadores/CameraWorkerModel.cs index 9e7f974ff..9df43421d 100644 --- a/AgroBase/AgroBase/Models/Operadores/CameraWorkerModel.cs +++ b/AgroBase/AgroBase/Models/Operadores/CameraWorkerModel.cs @@ -47,7 +47,8 @@ namespace AgroBase.Models.Operadores public double fps { get; set; } public int stream_w { get; set; } public int stream_h { get; set; } - public int stream_kbps { get; set; } + public double stream_kbps { get; set; } + public double stream_last_frame { get; set; } public SaudeWorkerModel saude { get; set; } public CameraWorkerItemPerformanceModel performance { get; set; } diff --git a/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs b/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs index 51da07295..d0456d299 100644 --- a/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs +++ b/AgroBase/AgroBase/Models/TrajetoriaMapaOperacaoModel.cs @@ -2409,7 +2409,7 @@ namespace AgroBase.Models public double DistanciaPercorridaTotal { get; private set; } = 0; public void AtualizarDistancias(double distancia) { - DistanciaTotal += distancia; + DistanciaPercorridaTotal += distancia; if (Dentro) { DistanciaPercorridaCorredor += distancia; diff --git a/AgroBase/AgroBase/Models/Variaveis.cs b/AgroBase/AgroBase/Models/Variaveis.cs index b80783174..d7ed697e1 100644 --- a/AgroBase/AgroBase/Models/Variaveis.cs +++ b/AgroBase/AgroBase/Models/Variaveis.cs @@ -732,6 +732,9 @@ namespace AgroBase.Models double dx = velocidadeMs * tempoDelta * Math.Sin(heading); // Leste(+) double dy = velocidadeMs * tempoDelta * Math.Cos(heading); // Norte(+) + GPSModel ultimaPosicao = GPSService.historicoPosicao.Peek(); + if (pos == null) return ultimaPosicao; + // geo double R_earth = GPSUtils.RaioDaTerra; double dLat = (dy / R_earth) * 180.0 / Math.PI; @@ -740,7 +743,6 @@ namespace AgroBase.Models double latitude = pos.Latitude + dLat; double longitude = pos.Longitude + dLon; - GPSModel ultimaPosicao = GPSService.historicoPosicao.Peek(); GPSModel novaPosicao = new GPSModel { Momento = DateTime.Now, 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 8e9e518b2..2bff11072 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 @@ -1,29 +1,17 @@ import numpy as np import time -import socket -import struct +from camera_worker.tcp_streamer import CameraTcpStreamer from camera_worker.raw_segformer_service import make_bgr_preview_from_raw -from shared.utils import resize_frame from shared.enums import StatusModulo, T_Code from shared.contexto_global_redis import ContextoGlobalRedis from camera_worker.gal_service import GalService -import subprocess -import cv2 -GST_LAUNCH = r"C:\Program Files\gstreamer\1.0\msvc_x86_64\bin\gst-launch-1.0.exe" - class CameraGal: def __init__(self, mostrar_log, mx_id, raw_w=None, raw_h=None): self.mx_id = mx_id self.mostrar_log = mostrar_log - - self.gst_proc = None - self.gst_FPS = 5 - self.gst_WIDTH = 480 - self.gst_HEIGHT = 270 - self.gst_BIT_RATE = 300 - self._ultimo_envio_gst = 0.0 + self.stream = None self.dispositivo = T_Code.Vzo self.modelo = "Desconhecido" @@ -56,58 +44,19 @@ class CameraGal: self.modelo = info.get("model") self.versao = info.get("version_word") - _camera = ContextoGlobalRedis.get_camera(self.mx_id) or {} - _camera["mx_id"] = self.mx_id - _camera["versao"] = self.versao - _camera["iniciando"] = True - _camera["iniciado"] = False - _camera["rodando"] = False - _camera["parametros"] = {} - - # Criar processo para transmissao de video - try: - self._sock = None - self._sock_conectado = False - self._sock_ultima_tentativa_conexao = 0.0 - self._sock_intervalo_reconexao = 5.0 # segundos entre tentativas - self._op_mode = 3 - self._op_fps = 5 - self._last_frame_sent_ts = 0.0 - self._last_mode_change_ts = 0.0 - if False and _ip is not None and _porta is not None: - self.mostrar_log(f"Iniciando GStreamer para {_ip}:{_porta}...") - gst_cmd = [ - GST_LAUNCH, - "fdsrc", "fd=0", - #"!", "queue", "leaky=2", "max-size-buffers=1", # dropa frames antigos - "!", "videoparse", - f"width={self.gst_WIDTH}", f"height={self.gst_HEIGHT}", - "format=bgr", - f"framerate={self.gst_FPS}/1", - "!", "videoconvert", - "!", "videoscale", - "!", f"video/x-raw,width={self.gst_WIDTH},height={self.gst_HEIGHT}", - "!", "x264enc", "tune=zerolatency", "speed-preset=ultrafast", - f"bitrate={self.gst_BIT_RATE}", "key-int-max=20", - #"byte-stream=true", "key-int-max=5", "bframes=0", "aud=true", f"bitrate={self.gst_BIT_RATE}", - "!", "rtph264pay", "config-interval=-1", "pt=96", - #"!", "h264parse", "!", "mpegtsmux", - "!", "udpsink", - f"host={_ip}", f"port={_porta}", - "sync=false", "async=false", - ] - self.gst_proc = subprocess.Popen( - gst_cmd, - stdin=subprocess.PIPE, - ) - except Exception as e: - self.gst_proc = None - self.mostrar_log(f"Erro ao criar script de transmissao de video: {e}") - - _camera["modelo"] = self.modelo - _camera["dispositivo"] = self.dispositivo.value - _camera["tem_depht"] = False - _camera["tem_imu"] = False + ContextoGlobalRedis.atualizar_ctx_dict( + ContextoGlobalRedis.CamKey(self.mx_id), + mx_id=self.mx_id, + versao=self.versao, + iniciando=True, + iniciado=False, + rodando=False, + parametros={}, + modelo=self.modelo, + dispositivo=self.dispositivo.value, + tem_depth=False, + tem_imu=False, + ) # Fase 2: criar pipeline e instanciar normalmente try: @@ -134,127 +83,23 @@ class CameraGal: "cm_por_px_y": cm_por_px_y, } - _camera["parametros"] = self.parametros - self.ultima_saude["timestamp"] = time.time() self.ultima_saude["status"] = StatusModulo.OPERANTE.value self.iniciado = True - _camera["iniciado"] = True - _camera["iniciado_em"] = time.time() + _porta = (ContextoGlobalRedis.get_equipamento() or {}).get("base_porta_ervas") + self.stream = CameraTcpStreamer(porta=_porta, mx_id=self.mx_id, mostrar_log=self.mostrar_log) except Exception as e: self.mostrar_log(f"Erro ao iniciar camera: {e}") pass - _camera["iniciando"] = False - ContextoGlobalRedis.set(ContextoGlobalRedis.CamKey(self.mx_id), _camera) - - def requisitar_frame_raw(self, force: bool = False, max_age_s: float = None): - """ - Se force=False, reaproveita o último frame se ainda for "novo". - Se force=True, sempre busca um frame novo na câmera. - """ - try: - agora = time.time() - if max_age_s is None: - max_age_s = self._raw_cache_max_age - - # Se NÃO for forçado e tem frame recente em cache, reaproveita - if (not force - and self.ultimo_frame_raw is not None - and self.timestamp_ultimo_frame_raw is not None - and (agora - self.timestamp_ultimo_frame_raw) < max_age_s): - return self.ultimo_frame_raw - - # Caso contrário, busca frame novo - raw4_base, dbg = self.cam.grab_raw4( - out_h=self.parametros["raw_h"], - out_w=self.parametros["raw_w"], - timeout_ms=500 - ) - - self.ultimo_frame_raw = raw4_base - self.timestamp_ultimo_frame_raw = agora - - return raw4_base - - except Exception as e: - self.mostrar_log(f"Erro ao requisitar frame raw: {e}") - return None - - def requisitar_frame_rgb(self, force: bool = False, max_age_s: float = None): - """ - - Se force=False: - * Se existir RGB recente em cache, só devolve ele (barato). - * Se estiver "velho" demais, recalcula a partir do raw (sem necessariamente ir na câmera). - - Se force=True: - * Recalcula RGB agora, usando o raw mais novo possível. - """ - try: - agora = time.time() - if max_age_s is None: - max_age_s = self._rgb_cache_max_age - - # 1) Se NÃO for forçado e tenho frame RGB "fresco", só devolvo ele - if (not force - and self.ultimo_frame_rgb is not None - and self.timestamp_ultimo_frame_rgb is not None - and (agora - self.timestamp_ultimo_frame_rgb) < max_age_s): - - dur = 0.0 # basicamente só acesso memória - resultado = { - "erro": None, - "duracao": dur, - "frame_valido": True - } - return self.ultimo_frame_rgb, resultado - - # 2) Se preciso atualizar o RGB agora - start = time.time() - - # Aqui a sacada: NÃO precisa obrigatoriamente ir na câmera. - # Usa o raw em cache (que a segmentação acabou de usar) se possível. - raw4_base = self.requisitar_frame_raw(force=False) - if raw4_base is None: - raise RuntimeError("raw4_base veio None em requisitar_frame_rgb") - - frame = make_bgr_preview_from_raw(raw4_base, rgirb=True, preview_fast=True) - - self.ultimo_frame_rgb = frame - self.timestamp_ultimo_frame_rgb = time.time() - dur = self.timestamp_ultimo_frame_rgb - start - - self.enviar_frame_stream(frame) - - resultado = { - "erro": None, - "duracao": dur, - "frame_valido": frame is not None and frame.size > 0 - } - - return frame, resultado - - except Exception as e: - self.mostrar_log(f"Erro ao requisitar frame RGB: {e}") - return None, { - "erro": str(e), - "duracao": 0, - "frame_valido": False - } - - def enviar_frame_stream(self, frame): - _stream_on = (ContextoGlobalRedis.get_camera(self.mx_id) or {}).get("streaming", False) - if self.gst_proc is not None and _stream_on: - self._ultimo_envio_gst = time.time() - if frame is not None and frame.size > 0: - if frame.shape[1] != self.gst_WIDTH or frame.shape[0] != self.gst_HEIGHT: - frame_stream = cv2.resize(frame, (self.gst_WIDTH, self.gst_HEIGHT), interpolation=cv2.INTER_AREA) - else: - frame_stream = frame - try: - self.gst_proc.stdin.write(frame_stream.tobytes()) - except BrokenPipeError: - self.mostrar_log("GStreamer fechou o pipe. Encerrando.") + ContextoGlobalRedis.atualizar_ctx_dict( + ContextoGlobalRedis.CamKey(self.mx_id), + parametros=self.parametros, + iniciado=self.iniciado, + iniciando=False, + iniciado_em=time.time() if self.iniciado else None + ) def atualizar_saude(self): # 1) Ver se “existe” no contexto e se o handle da GAL está aberto @@ -377,151 +222,97 @@ class CameraGal: from camera_worker.manager import definir_saude_camera definir_saude_camera(self.mx_id, status, saude, motivos, self.rodando, performance, conectado, agora, self.dispositivo) - def _sock_tentar_conectar(self): - _cfg = ContextoGlobalRedis.get_equipamento() or {} - _ip = _cfg.get("base_ip") - _porta = _cfg.get("base_porta_ervas") - # Sem IP/porta configurado, nem tenta - if not _ip or not _porta: - return - - agora = time.time() - if (agora - self._sock_ultima_tentativa_conexao) < self._sock_intervalo_reconexao: - # ainda não deu o tempo mínimo, evita flood - return - - self._sock_ultima_tentativa_conexao = agora - - # Se já tem socket antigo, fecha - if self._sock is not None: - try: - self._sock.close() - except: - pass - self._sock = None - - try: - #self.mostrar_log(f"Tentando conectar em {_ip}:{_porta}...") - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - s.settimeout(1.0) # timeout curto pra não travar - - s.connect((_ip, _porta)) - s.settimeout(None) # depois de conectado, volta p/ blocking normal - - self._sock = s - self._sock_conectado = True - self.mostrar_log("Socket de vídeo conectado.") - except Exception as e: - self._sock_conectado = False - self._sock = None - # log mais leve pra não spammar - #self.mostrar_log(f"Falha ao conectar socket de vídeo: {e}") - def enviar_frame_tcp(self, frame_bgr): - _camera = ContextoGlobalRedis.get_camera(self.mx_id) or {} - _stream_on = _camera.get("streaming", False) - if not _stream_on: - return - - # 1) Atualiza modo (não custa caro) - self._maybe_update_mode() - params = self._mode_params(self._op_mode) - self._op_fps = params["fps"] - - # 2) Throttle por FPS (só tenta enviar quando "bate o relógio") - now = time.time() - min_dt = 1.0 / max(0.1, self._op_fps) - if (now - self._last_frame_sent_ts) < min_dt: - return - prev_sent_ts = self._last_frame_sent_ts - self._last_frame_sent_ts = now - - # 4) Conexão socket (igual você já faz) - if not self._sock_conectado or self._sock is None: - self._sock_tentar_conectar() - if not self._sock_conectado or self._sock is None: - return - - # 5) Resize adaptativo - frame_bgr = resize_frame(frame_bgr, max_width=params["w"], max_height=params["h"]) - - # 6) JPEG quality adaptativa - encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), int(params["q"])] - ok, buf = cv2.imencode(".jpg", frame_bgr, encode_param) - if not ok: - return - - data = buf.tobytes() - size = len(data) - header = struct.pack("!I", size) - - # 6) Calcula bitrate real - dt_real = max(0.001, now - prev_sent_ts) - kbps_inst = (size * 8) / dt_real / 1000.0 - kbps_ant = float(_camera.get("stream_kbps", 0.0) or 0.0) - kbps_suave = kbps_inst if kbps_ant <= 0 else (kbps_ant * 0.8 + kbps_inst * 0.2) + self.stream.enviar_frame_tcp(frame_bgr) + def requisitar_frame_raw(self, force: bool = False, max_age_s: float = None): + """ + Se force=False, reaproveita o último frame se ainda for "novo". + Se force=True, sempre busca um frame novo na câmera. + """ try: - self._sock.sendall(header + data) - except (BrokenPipeError, ConnectionResetError, OSError) as e: - self._sock_conectado = False - kbps_suave = 0.0 - try: - self._sock.close() - except: - pass - self._sock = None - finally: - ContextoGlobalRedis.atualizar_ctx_dict( - ContextoGlobalRedis.CamKey(self.mx_id), - stream_kbps=round(kbps_suave, 1), - fps=params["fps"], - stream_w=params["w"], - stream_h=params["h"], + agora = time.time() + if max_age_s is None: + max_age_s = self._raw_cache_max_age + + # Se NÃO for forçado e tem frame recente em cache, reaproveita + if (not force + and self.ultimo_frame_raw is not None + and self.timestamp_ultimo_frame_raw is not None + and (agora - self.timestamp_ultimo_frame_raw) < max_age_s): + return self.ultimo_frame_raw + + # Caso contrário, busca frame novo + raw4_base, dbg = self.cam.grab_raw4( + out_h=self.parametros["raw_h"], + out_w=self.parametros["raw_w"], + timeout_ms=500 ) - def _get_ipb_bw_util_pct(self) -> float: - 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_pressure_pct") or detalhes.get("bw_util_pct") or 0.0) + self.ultimo_frame_raw = raw4_base + self.timestamp_ultimo_frame_raw = agora - def _mode_params(self, mode: int): - # mode 0..3 - table = { - 3: dict(fps=5.0, q=55, w=640, h=360), - 2: dict(fps=3.0, q=45, w=640, h=360), - 1: dict(fps=2.0, q=40, w=480, h=270), - 0: dict(fps=1.0, q=35, w=320, h=180), - } - return table.get(int(mode), table[1]) + return raw4_base - def _maybe_update_mode(self): + except Exception as e: + self.mostrar_log(f"Erro ao requisitar frame raw: {e}") + return None + + def requisitar_frame_rgb(self, force: bool = False, max_age_s: float = None): """ - Decide modo baseado no uso de banda. Histerese com cooldown pra não oscilar. + - Se force=False: + * Se existir RGB recente em cache, só devolve ele (barato). + * Se estiver "velho" demais, recalcula a partir do raw (sem necessariamente ir na câmera). + - Se force=True: + * Recalcula RGB agora, usando o raw mais novo possível. """ - util = self._get_ipb_bw_util_pct() # 0..150 (pode passar 100) - now = time.time() + try: + agora = time.time() + if max_age_s is None: + max_age_s = self._rgb_cache_max_age - # cooldown mínimo entre trocas - if (now - self._last_mode_change_ts) < 1.5: - return + # 1) Se NÃO for forçado e tenho frame RGB "fresco", só devolvo ele + if (not force + and self.ultimo_frame_rgb is not None + and self.timestamp_ultimo_frame_rgb is not None + and (agora - self.timestamp_ultimo_frame_rgb) < max_age_s): + + dur = 0.0 # basicamente só acesso memória + resultado = { + "erro": None, + "duracao": dur, + "frame_valido": True + } + return self.ultimo_frame_rgb, resultado - # thresholds (ajuste fino depois) - # sobe só se estiver bem folgado - if util < 55 and self._op_mode < 3: - self._op_mode += 1 - self._last_mode_change_ts = now - # desce se está pesado - elif util > 85 and self._op_mode > 0: - self._op_mode -= 1 - self._last_mode_change_ts = now - # emergência: muito alto, cai mais - if util > 90 and self._op_mode > 0: - self._op_mode = max(0, self._op_mode - 1) - self._last_mode_change_ts = now - - + # 2) Se preciso atualizar o RGB agora + start = time.time() + # Aqui a sacada: NÃO precisa obrigatoriamente ir na câmera. + # Usa o raw em cache (que a segmentação acabou de usar) se possível. + raw4_base = self.requisitar_frame_raw(force=False) + if raw4_base is None: + raise RuntimeError("raw4_base veio None em requisitar_frame_rgb") + frame = make_bgr_preview_from_raw(raw4_base, rgirb=True, preview_fast=True) + + self.ultimo_frame_rgb = frame + self.timestamp_ultimo_frame_rgb = time.time() + dur = self.timestamp_ultimo_frame_rgb - start + + resultado = { + "erro": None, + "duracao": dur, + "frame_valido": frame is not None and frame.size > 0 + } + + return frame, resultado + + except Exception as e: + self.mostrar_log(f"Erro ao requisitar frame RGB: {e}") + return None, { + "erro": str(e), + "duracao": 0, + "frame_valido": False + } + 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 522096e2b..dce9ff4e1 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 @@ -1,30 +1,18 @@ import numpy as np import depthai as dai import time -import socket -import struct +from camera_worker.tcp_streamer import CameraTcpStreamer from health_worker.modulos.imu import IMUCamera -from shared.utils import resize_frame from shared.enums import StatusModulo, T_Code from shared.contexto_global_redis import ContextoGlobalRedis -import subprocess -import cv2 -GST_LAUNCH = r"C:\Program Files\gstreamer\1.0\msvc_x86_64\bin\gst-launch-1.0.exe" - class CameraOak: def __init__(self, mostrar_log, mx_id, modelo_ia_seg=None, modelo_ia_det=None, iniciar_imu=False): self.mostrar_log = mostrar_log self.modelo_ia_seg = modelo_ia_seg self.modelo_ia_det = modelo_ia_det - - self.gst_proc = None - self.gst_FPS = 5 - self.gst_WIDTH = 480 - self.gst_HEIGHT = 270 - self.gst_BIT_RATE = 300 - self._ultimo_envio_gst = 0.0 + self.stream = None self.dispositivo = T_Code.Vzo self.modelo = "Desconhecido" @@ -52,13 +40,15 @@ class CameraOak: self.mx_id = self.dev_info.getMxId() self.versao = self.dev_info.name - _camera = ContextoGlobalRedis.get_camera(self.mx_id) or {} - _camera["mx_id"] = self.mx_id - _camera["versao"] = self.versao - _camera["iniciando"] = True - _camera["iniciado"] = False - _camera["rodando"] = False - _camera["parametros"] = {} + ContextoGlobalRedis.atualizar_ctx_dict( + ContextoGlobalRedis.CamKey(self.mx_id), + mx_id=self.mx_id, + versao=self.versao, + iniciando=True, + iniciado=False, + rodando=False, + parametros={} + ) # Fase 1: detectar sensores sem pipeline try: @@ -81,49 +71,13 @@ class CameraOak: pass self.mostrar_log(f"Falha ao detectar sensores: {e}") - _camera["modelo"] = self.modelo - _camera["dispositivo"] = self.dispositivo.value - _camera["tem_depht"] = self.tem_depth - _camera["tem_imu"] = self.tem_imu - - # Criar processo para transmissao de video - try: - self._sock = None - self._sock_conectado = False - self._sock_ultima_tentativa_conexao = 0.0 - self._sock_intervalo_reconexao = 5.0 # segundos entre tentativas - self._op_mode = 3 - self._op_fps = 5 - self._last_frame_sent_ts = 0.0 - self._last_mode_change_ts = 0.0 - if False and _ip is not None and _porta is not None: - self.mostrar_log(f"Iniciando GStreamer para {_ip}:{_porta}...") - gst_cmd = [ - GST_LAUNCH, - "fdsrc", "fd=0", - "!", "videoparse", - f"width={self.gst_WIDTH}", f"height={self.gst_HEIGHT}", - "format=bgr", - f"framerate={self.gst_FPS}/1", - "!", "videoconvert", - "!", "videoscale", - "!", f"video/x-raw,width={self.gst_WIDTH},height={self.gst_HEIGHT}", - "!", "x264enc", "tune=zerolatency", "speed-preset=ultrafast", - f"bitrate={self.gst_BIT_RATE}", "key-int-max=20", - #"byte-stream=true", "key-int-max=5", "bframes=0", "aud=true", f"bitrate={self.gst_BIT_RATE}", - "!", "rtph264pay", "config-interval=-1", "pt=96", - #"!", "h264parse", "!", "mpegtsmux", - "!", "udpsink", - f"host={_ip}", f"port={_porta}", - "sync=false", "async=false", - ] - self.gst_proc = subprocess.Popen( - gst_cmd, - stdin=subprocess.PIPE, - ) - except Exception as e: - self.gst_proc = None - self.mostrar_log(f"Erro ao criar script de transmissao de video: {e}") + ContextoGlobalRedis.atualizar_ctx_dict( + ContextoGlobalRedis.CamKey(self.mx_id), + modelo=self.modelo, + dispositivo=self.dispositivo.value, + tem_depth=self.tem_depth, + tem_imu=self.tem_imu + ) # Fase 2: criar pipeline e instanciar normalmente try: @@ -161,7 +115,6 @@ class CameraOak: "percentual_altura_solo": dadosSnr.get("percentual_altura_solo", 30), } self._set_calib() - _camera["parametros"] = self.parametros elif self.dispositivo == T_Code.Cam: rgb_width = 1920 rgb_height = 1080 @@ -183,19 +136,148 @@ class CameraOak: "cm_por_px_x": cm_por_px_x, "cm_por_px_y": cm_por_px_y, } - _camera["parametros"] = self.parametros self.ultima_saude["timestamp"] = time.time() self.iniciado = True - _camera["iniciado"] = True - _camera["iniciado_em"] = time.time() + _porta = (ContextoGlobalRedis.get_equipamento() or {}).get("base_porta_caminho") + self.stream = CameraTcpStreamer(porta=_porta, mx_id=self.mx_id, mostrar_log=self.mostrar_log) + except Exception as e: self.mostrar_log(f"Erro ao iniciar camera: {e}") pass - _camera["iniciando"] = False - ContextoGlobalRedis.set(ContextoGlobalRedis.CamKey(self.mx_id), _camera) + ContextoGlobalRedis.atualizar_ctx_dict( + ContextoGlobalRedis.CamKey(self.mx_id), + parametros=self.parametros, + iniciado=self.iniciado, + iniciando=False, + iniciado_em=time.time() if self.iniciado else None + ) + + def atualizar_saude(self): + #self.mostrar_log(f"[{self.mx_id}] Atualizando saude {self.dispositivo.name}...") + + if self.imu is not None: + self.imu.atualizar_saude() + + conectado = ContextoGlobalRedis.get_cameras().get(self.mx_id) is not None + + saude = 50 + motivos = [] + + performance = { + "temperatura": 0, + "memoria_usada": 0, + "executando": False, + "velocidade": "" + } + if conectado: + try: + dev = self.device + temp = dev.getChipTemperature().average + ddr = dev.getDdrMemoryUsage().used / 1024.0 / 1024.0 + running = dev.isPipelineRunning() + speed = dev.getUsbSpeed().name + performance = { + "temperatura": temp, + "memoria_usada": ddr, + "executando": running, + "velocidade": speed + } + # 🔸 Temperatura (> 60 °C começa a penalizar) + if temp >= 80: + motivos.append(f"Temperatura crítica: {temp:.1f} °C") + saude -= 30 + elif temp >= 70: + motivos.append(f"Temperatura elevada: {temp:.1f} °C") + saude -= 15 + elif temp >= 60: + motivos.append(f"Temperatura acima do ideal: {temp:.1f} °C") + saude -= 5 + + # 🔸 Memória DDR (> 300 MB começa a penalizar) + if ddr >= 500: + motivos.append(f"Memória DDR crítica: {ddr:.1f} MB") + saude -= 30 + elif ddr >= 400: + motivos.append(f"Memória DDR elevada: {ddr:.1f} MB") + saude -= 15 + elif ddr >= 300: + motivos.append(f"Memória DDR acima do ideal: {ddr:.1f} MB") + saude -= 5 + + # 🔸 Pipeline inativa + if not running: + motivos.append("Pipeline parada") + saude -= 20 + + # 🔸 USB Speed + if speed.lower() not in ["super", "superplus"]: # USB 3.x + motivos.append(f"USB lenta: {speed}") + saude -= 15 + + frame, resultado = self.requisitar_frame_rgb() + except Exception as e: + self.mostrar_log(f"Erro ao requisitar dados da camera para atualizar saude: {e}") + resultado = { + "erro": str(e), + "frame_valido": False, + "duracao": 0 + } + if "Communication exception" in str(e) or "X_LINK_ERROR" in str(e): + conectado = False + + if not conectado: + motivos.append("desconectado") + saude = 0 + elif resultado["erro"]: + motivos.append(resultado["erro"]) + saude = 0 + if "Communication exception" in resultado["erro"] or "X_LINK_ERROR" in resultado["erro"]: + conectado = False + elif not resultado["frame_valido"]: + motivos.append("Frame inválido ou vazio") + saude = 0 + else: + saude += 50 + if resultado["duracao"] > 1.0: + saude -= 20 + motivos.append(f"Tempo elevado para captura: {resultado['duracao']:.2f}s") + + saude = min(max(saude, 0), 100) + + status = StatusModulo.OPERANTE + if not conectado: + status = StatusModulo.DESCONECTADO + elif saude <= 0: + status = StatusModulo.FALHA + elif saude < 80: + status = StatusModulo.ALERTA + + agora = time.time() + + saude_geral = { + "timestamp": agora, + "conectado": conectado, + "status": status.value, + "saude": saude, + "motivos": motivos, + "saude_idividual": [] + } + + self.ultima_saude = saude_geral + + timeout = 2.0 + ts_depth = self.timestamp_ultimo_frame_depth or 0 + ts_rgb = self.timestamp_ultimo_frame_rgb or 0 + self.rodando = ((agora - ts_depth) <= timeout or (agora - ts_rgb) <= timeout) + + from camera_worker.manager import definir_saude_camera + definir_saude_camera(self.mx_id, status, saude, motivos, self.rodando, performance, conectado, agora, self.dispositivo) + + def enviar_frame_tcp(self, frame_bgr): + self.stream.enviar_frame_tcp(frame_bgr) def _criar_pipeline(self): pipeline = dai.Pipeline() @@ -401,8 +483,6 @@ class CameraOak: #self.timestamp_ultimo_frame_rgb = time.time() dur = time.time() - start - self.enviar_frame_stream(frame) - resultado = { "erro": None, "duracao": dur, @@ -419,20 +499,6 @@ class CameraOak: "frame_valido": False } - def enviar_frame_stream(self, frame): - _stream_on = (ContextoGlobalRedis.get_camera(self.mx_id) or {}).get("streaming", False) - if self.gst_proc is not None and _stream_on: - self._ultimo_envio_gst = time.time() - if frame is not None and frame.size > 0: - if frame.shape[1] != self.gst_WIDTH or frame.shape[0] != self.gst_HEIGHT: - frame_stream = cv2.resize(frame, (self.gst_WIDTH, self.gst_HEIGHT), interpolation=cv2.INTER_AREA) - else: - frame_stream = frame - try: - self.gst_proc.stdin.write(frame_stream.tobytes()) - except BrokenPipeError: - self.mostrar_log("GStreamer fechou o pipe. Encerrando.") - def requisitar_frame_depth(self): if not self.tem_depth: return None, { @@ -583,271 +649,4 @@ class CameraOak: dur = time.time() - start return [], {"erro": str(e), "duracao": dur, "frame_valido": False} - def atualizar_saude(self): - #self.mostrar_log(f"[{self.mx_id}] Atualizando saude {self.dispositivo.name}...") - - if self.imu is not None: - self.imu.atualizar_saude() - - conectado = ContextoGlobalRedis.get_cameras().get(self.mx_id) is not None - - saude = 50 - motivos = [] - - performance = { - "temperatura": 0, - "memoria_usada": 0, - "executando": False, - "velocidade": "" - } - if conectado: - try: - dev = self.device - temp = dev.getChipTemperature().average - ddr = dev.getDdrMemoryUsage().used / 1024.0 / 1024.0 - running = dev.isPipelineRunning() - speed = dev.getUsbSpeed().name - performance = { - "temperatura": temp, - "memoria_usada": ddr, - "executando": running, - "velocidade": speed - } - # 🔸 Temperatura (> 60 °C começa a penalizar) - if temp >= 80: - motivos.append(f"Temperatura crítica: {temp:.1f} °C") - saude -= 30 - elif temp >= 70: - motivos.append(f"Temperatura elevada: {temp:.1f} °C") - saude -= 15 - elif temp >= 60: - motivos.append(f"Temperatura acima do ideal: {temp:.1f} °C") - saude -= 5 - - # 🔸 Memória DDR (> 300 MB começa a penalizar) - if ddr >= 500: - motivos.append(f"Memória DDR crítica: {ddr:.1f} MB") - saude -= 30 - elif ddr >= 400: - motivos.append(f"Memória DDR elevada: {ddr:.1f} MB") - saude -= 15 - elif ddr >= 300: - motivos.append(f"Memória DDR acima do ideal: {ddr:.1f} MB") - saude -= 5 - - # 🔸 Pipeline inativa - if not running: - motivos.append("Pipeline parada") - saude -= 20 - - # 🔸 USB Speed - if speed.lower() not in ["super", "superplus"]: # USB 3.x - motivos.append(f"USB lenta: {speed}") - saude -= 15 - - frame, resultado = self.requisitar_frame_rgb() - except Exception as e: - self.mostrar_log(f"Erro ao requisitar dados da camera para atualizar saude: {e}") - resultado = { - "erro": str(e), - "frame_valido": False, - "duracao": 0 - } - if "Communication exception" in str(e) or "X_LINK_ERROR" in str(e): - conectado = False - - if not conectado: - motivos.append("desconectado") - saude = 0 - elif resultado["erro"]: - motivos.append(resultado["erro"]) - saude = 0 - if "Communication exception" in resultado["erro"] or "X_LINK_ERROR" in resultado["erro"]: - conectado = False - elif not resultado["frame_valido"]: - motivos.append("Frame inválido ou vazio") - saude = 0 - else: - saude += 50 - if resultado["duracao"] > 1.0: - saude -= 20 - motivos.append(f"Tempo elevado para captura: {resultado['duracao']:.2f}s") - - saude = min(max(saude, 0), 100) - - status = StatusModulo.OPERANTE - if not conectado: - status = StatusModulo.DESCONECTADO - elif saude <= 0: - status = StatusModulo.FALHA - elif saude < 80: - status = StatusModulo.ALERTA - - agora = time.time() - - saude_geral = { - "timestamp": agora, - "conectado": conectado, - "status": status.value, - "saude": saude, - "motivos": motivos, - "saude_idividual": [] - } - - self.ultima_saude = saude_geral - - timeout = 2.0 - ts_depth = self.timestamp_ultimo_frame_depth or 0 - ts_rgb = self.timestamp_ultimo_frame_rgb or 0 - self.rodando = ((agora - ts_depth) <= timeout or (agora - ts_rgb) <= timeout) - - from camera_worker.manager import definir_saude_camera - definir_saude_camera(self.mx_id, status, saude, motivos, self.rodando, performance, conectado, agora, self.dispositivo) - - - def _sock_tentar_conectar(self): - _cfg = ContextoGlobalRedis.get_equipamento() or {} - _ip = _cfg.get("base_ip") - _porta = _cfg.get("base_porta_caminho") - # Sem IP/porta configurado, nem tenta - if not _ip or not _porta: - return - - agora = time.time() - if (agora - self._sock_ultima_tentativa_conexao) < self._sock_intervalo_reconexao: - # ainda não deu o tempo mínimo, evita flood - return - - self._sock_ultima_tentativa_conexao = agora - - # Se já tem socket antigo, fecha - if self._sock is not None: - try: - self._sock.close() - except: - pass - self._sock = None - - try: - #self.mostrar_log(f"Tentando conectar em {_ip}:{_porta}...") - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - s.settimeout(1.0) # timeout curto pra não travar - - s.connect((_ip, _porta)) - s.settimeout(None) # depois de conectado, volta p/ blocking normal - - self._sock = s - self._sock_conectado = True - self.mostrar_log("Socket de vídeo conectado.") - except Exception as e: - self._sock_conectado = False - self._sock = None - # log mais leve pra não spammar - #self.mostrar_log(f"Falha ao conectar socket de vídeo: {e}") - - def enviar_frame_tcp(self, frame_bgr): - _camera = ContextoGlobalRedis.get_camera(self.mx_id) or {} - _stream_on = _camera.get("streaming", False) - if not _stream_on: - return - - # 1) Atualiza modo (não custa caro) - self._maybe_update_mode() - params = self._mode_params(self._op_mode) - self._op_fps = params["fps"] - - # 2) Throttle por FPS (só tenta enviar quando "bate o relógio") - now = time.time() - min_dt = 1.0 / max(0.1, self._op_fps) - if (now - self._last_frame_sent_ts) < min_dt: - return - prev_sent_ts = self._last_frame_sent_ts - self._last_frame_sent_ts = now - - # 4) Conexão socket (igual você já faz) - if not self._sock_conectado or self._sock is None: - self._sock_tentar_conectar() - if not self._sock_conectado or self._sock is None: - return - - # 5) Resize adaptativo - frame_bgr = resize_frame(frame_bgr, max_width=params["w"], max_height=params["h"]) - - # 6) JPEG quality adaptativa - encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), int(params["q"])] - ok, buf = cv2.imencode(".jpg", frame_bgr, encode_param) - if not ok: - return - - data = buf.tobytes() - size = len(data) - header = struct.pack("!I", size) - - # 6) Calcula bitrate real - dt_real = max(0.001, now - prev_sent_ts) - kbps_inst = (size * 8) / dt_real / 1000.0 - kbps_ant = float(_camera.get("stream_kbps", 0.0) or 0.0) - kbps_suave = kbps_inst if kbps_ant <= 0 else (kbps_ant * 0.8 + kbps_inst * 0.2) - - try: - self._sock.sendall(header + data) - except (BrokenPipeError, ConnectionResetError, OSError) as e: - self._sock_conectado = False - kbps_suave = 0.0 - try: - self._sock.close() - except: - pass - self._sock = None - finally: - ContextoGlobalRedis.atualizar_ctx_dict( - ContextoGlobalRedis.CamKey(self.mx_id), - stream_kbps=round(kbps_suave, 1), - fps=params["fps"], - stream_w=params["w"], - stream_h=params["h"], - ) - - def _get_ipb_bw_util_pct(self) -> float: - 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_pressure_pct") or detalhes.get("bw_util_pct") or 0.0) - - def _mode_params(self, mode: int): - # mode 0..3 - table = { - 3: dict(fps=5.0, q=55, w=640, h=360), - 2: dict(fps=3.0, q=45, w=640, h=360), - 1: dict(fps=2.0, q=40, w=480, h=270), - 0: dict(fps=1.0, q=35, w=320, h=180), - } - return table.get(int(mode), table[1]) - - def _maybe_update_mode(self): - """ - Decide modo baseado no uso de banda. Histerese com cooldown pra não oscilar. - """ - util = self._get_ipb_bw_util_pct() # 0..150 (pode passar 100) - now = time.time() - - # cooldown mínimo entre trocas - if (now - self._last_mode_change_ts) < 1.5: - return - - # thresholds (ajuste fino depois) - # sobe só se estiver bem folgado - if util < 55 and self._op_mode < 3: - self._op_mode += 1 - self._last_mode_change_ts = now - # desce se está pesado - elif util > 75 and self._op_mode > 0: - self._op_mode -= 1 - self._last_mode_change_ts = now - # emergência: muito alto, cai mais - if util > 85 and self._op_mode > 0: - self._op_mode = max(0, self._op_mode - 1) - self._last_mode_change_ts = now - - + \ No newline at end of file diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/tcp_streamer.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/tcp_streamer.py new file mode 100644 index 000000000..932726768 --- /dev/null +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/camera_worker/tcp_streamer.py @@ -0,0 +1,155 @@ +import socket +import struct +import time +import cv2 +import numpy as np + +from shared.enums import T_Code +from shared.utils import resize_frame +from shared.contexto_global_redis import ContextoGlobalRedis + +class CameraTcpStreamer: + def __init__(self, porta: int, mx_id: str, mostrar_log=None): + self.mx_id = mx_id + self.mostrar_log = mostrar_log or (lambda msg: None) + + self._sock = None + self.sock_port = porta + self._sock_conectado = False + self._sock_ultima_tentativa_conexao = 0.0 + self._sock_intervalo_reconexao = 1.0 + + self._last_frame_sent_ts = 0.0 + self._last_mode_change_ts = 0.0 + self._op_mode = 1 + self._op_fps = 2.0 + + def fechar(self): + self._sock_conectado = False + if self._sock is not None: + try: + self._sock.close() + except: + pass + self._sock = None + + def _sock_tentar_conectar(self): + _cfg = ContextoGlobalRedis.get_equipamento() or {} + _ip = _cfg.get("base_ip") + + if not _ip or not self.sock_port: + return + + agora = time.time() + if (agora - self._sock_ultima_tentativa_conexao) < self._sock_intervalo_reconexao: + return + + self._sock_ultima_tentativa_conexao = agora + + self.fechar() + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + s.settimeout(1.0) + s.connect((_ip, self.sock_port)) + s.settimeout(None) + + self._sock = s + self._sock_conectado = True + self.mostrar_log(f"[{self.mx_id}] Socket de vídeo conectado.") + except Exception: + self._sock_conectado = False + self._sock = None + + def _get_ipb_bw_util_pct(self) -> float: + ipb = ContextoGlobalRedis.get_modulo(T_Code.Ipb) or {} + saude = (ipb.get("saude") or {}) + detalhes = (saude.get("detalhes") or {}) + return float(detalhes.get("bw_pressure_pct") or detalhes.get("bw_util_pct") or 0.0) + + def _mode_params(self, mode: int): + table = { + 3: dict(fps=5.0, q=55, w=640, h=360), + 2: dict(fps=3.0, q=45, w=640, h=360), + 1: dict(fps=2.0, q=40, w=480, h=270), + 0: dict(fps=1.0, q=35, w=320, h=180), + } + return table.get(int(mode), table[1]) + + def _maybe_update_mode(self): + util = self._get_ipb_bw_util_pct() + now = time.time() + + if (now - self._last_mode_change_ts) < 1.5: + return + + if util < 55 and self._op_mode < 3: + self._op_mode += 1 + self._last_mode_change_ts = now + elif util > 75 and self._op_mode > 0: + self._op_mode -= 1 + self._last_mode_change_ts = now + + if util > 85 and self._op_mode > 0: + self._op_mode = max(0, self._op_mode - 1) + self._last_mode_change_ts = now + + def enviar_frame_tcp(self, frame_bgr): + _camera = ContextoGlobalRedis.get_camera(self.mx_id) or {} + _stream_on = _camera.get("streaming", False) + if not _stream_on: + return + + self._maybe_update_mode() + params = self._mode_params(self._op_mode) + self._op_fps = params["fps"] + + now = time.time() + min_dt = 1.0 / max(0.1, self._op_fps) + if (now - self._last_frame_sent_ts) < min_dt: + return + + prev_sent_ts = self._last_frame_sent_ts + self._last_frame_sent_ts = now + + if not self._sock_conectado or self._sock is None: + self._sock_tentar_conectar() + if not self._sock_conectado or self._sock is None: + return + + if frame_bgr is None: + frame_bgr = np.zeros((params["h"], params["w"], 3), dtype=np.uint8) + else: + frame_bgr = resize_frame(frame_bgr, max_width=params["w"], max_height=params["h"]) + + encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), int(params["q"])] + ok, buf = cv2.imencode(".jpg", frame_bgr, encode_param) + if not ok: + return + data = buf.tobytes() + size = len(data) + header = struct.pack("!I", size) + + dt_real = max(0.001, now - prev_sent_ts) + fps_real = 1.0 / dt_real + kbps_inst = (size * 8) / dt_real / 1000.0 + + kbps_ant = float(_camera.get("stream_kbps", 0.0) or 0.0) + kbps_suave = kbps_inst if kbps_ant <= 0 else (kbps_ant * 0.8 + kbps_inst * 0.2) + + try: + self._sock.sendall(header + data) + except (BrokenPipeError, ConnectionResetError, OSError): + self._sock_conectado = False + kbps_suave = 0.0 + self.fechar() + finally: + ContextoGlobalRedis.atualizar_ctx_dict( + ContextoGlobalRedis.CamKey(self.mx_id), + stream_last_frame=time.time(), + stream_kbps=round(kbps_suave, 1), + fps=round(fps_real, 1), + stream_w=params["w"], + stream_h=params["h"], + ) diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/imu.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/imu.py index e5468e8c7..b005ca738 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/imu.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/health_worker/modulos/imu.py @@ -124,8 +124,8 @@ class IMUCamera(ModuloDiagnosticoBase): f_exec = 1.0 / (latencia + 1e-9) ContextoGlobalRedis.atualizar_ctx_dict( ContextoGlobalRedis.ModKey(T_Code.Imu), - roll=round(roll, 2), # lateral - pitch=round(-pitch, 2), # frontal + roll=round(-roll, 2), # frontal + pitch=round(pitch, 2), # lateral yaw=round(yaw, 2), vel_mps=round(velocidade_mps, 3), timestamp=(t1 * 1000), diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/regras_taticas.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/regras_taticas.py index 2dbda8e52..92c45e21b 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/regras_taticas.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/modulos/regras_taticas.py @@ -3,14 +3,14 @@ from shared.enums import StatusModulo, StatusOperacao, T_Code from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey from manager_worker.config import mostrar_log -def verifica_controle_liberado(): +def verifica_controle_liberado(ignorar_parada=False): try: motivos = [] _operacao = ContextoGlobalRedis.get_operacao() status_operacao = StatusOperacao(_operacao.get("status", StatusOperacao.NaoIniciado.value)) finalizando = _operacao.get("finalizando", False) - if (status_operacao != StatusOperacao.EmAndamento) or finalizando: + if not ignorar_parada and ((status_operacao != StatusOperacao.EmAndamento) or finalizando): motivos.append(f"Operação não está em andamento ou finalizando: {status_operacao.name}") _controle = ContextoGlobalRedis.get_controle() @@ -20,7 +20,7 @@ def verifica_controle_liberado(): _dados_vw = ContextoGlobalRedis.get(CtxKey.DadosVisualWorker, {}) _snr = ContextoGlobalRedis.get_modulo(T_Code.Snr) _snr_saude = StatusModulo(_snr.get("saude", {}).get("status", StatusModulo.DESCONECTADO.value)) - visual_worker_operante = _snr_saude == StatusModulo.OPERANTE + visual_worker_operante = _snr_saude in [StatusModulo.OPERANTE, StatusModulo.ALERTA] analise_deteccao_atualizada = (time.perf_counter() - _dados_vw.get("matriz_confianca", {}).get("ts", 10.0) <= 1.0) obstaculo_detectado = _dados_vw.get("matriz_confianca", {}).get("block", {}).get("decision", {}).get("parar", False) if analise_deteccao_atualizada else False @@ -33,7 +33,7 @@ def verifica_controle_liberado(): if imu_parada_por_inclinacao: _imu = ContextoGlobalRedis.get_modulo(T_Code.Imu) _imu_saude = StatusModulo(_imu.get("saude", {}).get("status", StatusModulo.DESCONECTADO.value)) - imu_operante = _imu_saude == StatusModulo.OPERANTE + imu_operante = _imu_saude in [StatusModulo.OPERANTE, StatusModulo.ALERTA] _equipamento = ContextoGlobalRedis.get_equipamento() ang_roll_max = _equipamento.get("angulo_roll_max", 15.0) ang_pitch_max = _equipamento.get("angulo_pitch_max", 30.0) 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 d51dbd57f..3dd6357fa 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 @@ -44,7 +44,8 @@ class ProcessadorEmAndamento(ProcessadorBase): erro=False, latencia=0, erro_lateral=0, - debug_custo={} + debug_custo={}, + motivos=motivos_parada ) _controle = ContextoGlobalRedis.get_controle() @@ -71,7 +72,8 @@ class ProcessadorEmAndamento(ProcessadorBase): erro=comando_dir.get("erro", False), latencia=comando_dir.get("latencia", 0.0), erro_lateral=comando_dir.get("erro_lateral", 0.0), - debug_custo=comando_dir.get("debug_custo", {}) + debug_custo=comando_dir.get("debug_custo", {}), + motivos=[] ) 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/_5_parado.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_5_parado.py index 08bf0deea..dee4cb7d6 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_5_parado.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/manager_worker/processadores/_5_parado.py @@ -1,10 +1,12 @@ from .base import ProcessadorBase from manager_worker.processadores.padroes import comando_parado +from manager_worker.modulos.regras_taticas import verifica_controle_liberado class ProcessadorParado(ProcessadorBase): def processar(self): try: - return comando_parado() + motivos_parada = verifica_controle_liberado(ignorar_parada=True) + return comando_parado(motivos=motivos_parada) except Exception as e: from manager_worker.config import mostrar_log mostrar_log(f"Erro no processador Parado: {e}") 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 9983333fd..d48529d6c 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 @@ -3,7 +3,7 @@ from shared.enums import ModoOperacao, StatusOperacao from manager_worker.config import mostrar_log from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey -def comando_controle(percentual_velocidade: float, frear: bool, angulo: float, tipo_movimento: int, simulacao = [], erro: bool = False, latencia: float = 0.0, erro_lateral: float = 0.0, debug_custo: dict = {}): +def comando_controle(percentual_velocidade: float, frear: bool, angulo: float, tipo_movimento: int, simulacao = [], erro: bool = False, latencia: float = 0.0, erro_lateral: float = 0.0, debug_custo: dict = {}, motivos = None): _controle = ContextoGlobalRedis.get_controle() hb_atual = _controle.get("heartbeat", 0) hb_novo = hb_atual @@ -14,6 +14,10 @@ def comando_controle(percentual_velocidade: float, frear: bool, angulo: float, t hb_novo = 0 hb_novo = (hb_novo + 1) % 10 + _motivos_anterior = _controle.get("motivos", []) + if motivos is None: + motivos = _motivos_anterior + ContextoGlobalRedis.atualizar_ctx_dict( CtxKey.DadosControle, angulo_sp=angulo, @@ -24,7 +28,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 ) return { "velocidade_sp": percentual_velocidade, @@ -38,8 +43,8 @@ def comando_controle(percentual_velocidade: float, frear: bool, angulo: float, t "debug_custo": debug_custo } -def comando_parado(): - return comando_controle(0, False, 0, TipoMovimentoDirecional.RodasDianteiras) +def comando_parado(motivos=None): + return comando_controle(0, False, 0, TipoMovimentoDirecional.RodasDianteiras, motivos=motivos) def log_status_operacao(): modo = ModoOperacao(ContextoGlobalRedis.get_operacao().get("modo")) 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 19a56e1b7..dc310e078 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 @@ -386,7 +386,13 @@ class ContextoGlobalRedis: if _motivo_traj: motivos_dos_modulos_mandatorios.append(_motivo_traj) - liberou = ((base_ok and parametros_ok and emergencia_ok and pausa_ok) or debug_mode) and trajetoria_ok + controle_ok = True + _motivos_controle = cls.get_controle().get("motivos", []) + if len(_motivos_controle) > 0: + controle_ok = False + motivos_dos_modulos_mandatorios.extend(_motivos_controle) + + liberou = ((base_ok and parametros_ok) or debug_mode) and emergencia_ok and pausa_ok and trajetoria_ok and controle_ok cls.atualizar_ctx_dict( CtxKey.DadosOperacao, diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/visual_worker/camera_manager.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/visual_worker/camera_manager.py index 5aaa413f0..7a31d6b43 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/visual_worker/camera_manager.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/visual_worker/camera_manager.py @@ -115,7 +115,7 @@ class CameraManager: self._analisando_deteccao = False self._iniciar_loop_analise_continua(15.0) - self._iniciar_loop_frame_stream(self.camera._op_fps) + self._iniciar_loop_frame_stream(self.camera.stream._op_fps) self.iniciando = False self.atualizar_saude_camera() @@ -302,8 +302,7 @@ class CameraManager: if (_stream_on): _frame_type = TipoFrameCamera(_camera.get("frame_type", TipoFrameCamera.Rgb.value)) _frame = self.get_selected_frame(_frame_type) - if _frame is not None: - self.camera.enviar_frame_tcp(_frame) + self.camera.enviar_frame_tcp(_frame) from visual_worker.config import load_seg_config, load_det_config seg_config = load_seg_config() @@ -318,7 +317,7 @@ class CameraManager: self.mostrar_log(f"Erro no loop de stream: {e}") finally: latencia = time.time() - t0 - freq = self.camera._op_fps + freq = self.camera.stream._op_fps time.sleep(max(0, (1.0 / freq) - latencia)) threading.Thread(target=loop, daemon=True).start() diff --git a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/camera_manager.py b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/camera_manager.py index 0d70bca9a..1f94de7c2 100644 --- a/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/camera_manager.py +++ b/AgroBase/AgroBase/bin/x64/Debug/Python/Scripts/workers/weed_worker/camera_manager.py @@ -79,7 +79,7 @@ class CameraManager: self.weed_detector = WeedDetector(color_map=colormap_rgb, classes=classes) self._iniciar_loop_analise_continua(20.0) - self._iniciar_loop_frame_stream(self.camera._op_fps) + self._iniciar_loop_frame_stream(self.camera.stream._op_fps) self.iniciando = False self.atualizar_saude_camera() @@ -202,8 +202,7 @@ class CameraManager: if (_stream_on): _frame_type = TipoFrameCamera(_camera.get("frame_type", TipoFrameCamera.Rgb.value)) _frame = self.get_selected_frame(_frame_type) - if _frame is not None: - self.camera.enviar_frame_tcp(_frame) + self.camera.enviar_frame_tcp(_frame) from weed_worker.config import load_seg_config config = load_seg_config() @@ -215,7 +214,7 @@ class CameraManager: self.mostrar_log(f"Erro no loop de stream: {e}") finally: latencia = time.time() - t0 - freq = self.camera._op_fps + freq = self.camera.stream._op_fps time.sleep(max(0, (1.0 / freq) - latencia)) threading.Thread(target=loop, daemon=True).start() diff --git a/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs b/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs index 8e689c709..6aeed9358 100644 --- a/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs +++ b/AgroBase/OperationControl/Controls/MapViewControl.xaml.cs @@ -496,9 +496,9 @@ namespace OperationControl.Controls Mapa.Map = new Mapsui.Map(); Mapa.Map.BackColor = Color.FromString("#111111"); } + Mapa.Map.Widgets.Clear(); InitBaseMapOptions(); - EnsureBaseLayer(); NavigateToView(); } diff --git a/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/MonitoramentoViewModel.cs b/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/MonitoramentoViewModel.cs index f0fd254c0..851ab7a0c 100644 --- a/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/MonitoramentoViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Views/Operacao/Monitoramento/MonitoramentoViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using AgroBase.Models.Operadores; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using static AgroBase.Models.Enums; @@ -115,12 +116,22 @@ namespace OperationControl.ViewModels.Views.Operacao.Monitoramento CameraErvasFrameTipoSelecionado = CameraErvasFrameTipos.FirstOrDefault(); } - public void AtualizarDadosCameras(double? cf_fps, double? cf_kbps, double? ct_fps, double? ct_kbps, StatusCarroMapa? status_carro, double? pct_ervas) + public void AtualizarDadosCameras(CameraWorkerItemModel? snr, CameraWorkerItemModel? cam, StatusCarroMapa? status_carro, double? pct_ervas) { - CameraFrontalStatus = $"{cf_fps ?? 0} fps • {cf_kbps ?? 0} kbps • {status_carro ?? StatusCarroMapa.Indefinido}"; - CameraErvasStatus = $"{ct_fps ?? 0} fps • {ct_kbps ?? 0} kbps • {pct_ervas ?? 0:F2}% ervas"; - CameraFrontalSemSinal = (cf_kbps ?? 0) == 0; - CameraErvasSemSinal = (ct_kbps ?? 0) == 0; + var snr_dt = DateTimeOffset.FromUnixTimeMilliseconds((long)((snr?.stream_last_frame ?? 0) * 1000)).UtcDateTime; + bool snr_sem_sinal = (DateTime.UtcNow - snr_dt).TotalSeconds > 10; + CameraFrontalSemSinal = snr_sem_sinal; + double snr_fps = snr_sem_sinal ? 0 : snr?.fps ?? 0; + double snr_kbps = snr_sem_sinal ? 0 : snr?.stream_kbps ?? 0; + CameraFrontalStatus = $"{snr_fps:F1} fps • {snr_kbps:F1} kbps • {status_carro ?? StatusCarroMapa.Indefinido}"; + + var cam_dt = DateTimeOffset.FromUnixTimeMilliseconds((long)((cam?.stream_last_frame ?? 0) * 1000)).UtcDateTime; + bool cam_sem_sinal = (DateTime.UtcNow - cam_dt).TotalSeconds > 10; + CameraErvasSemSinal = cam_sem_sinal; + double cam_fps = cam_sem_sinal ? 0 : cam?.fps ?? 0; + double cam_kbps = cam_sem_sinal ? 0 : cam?.stream_kbps ?? 0; + CameraErvasStatus = $"{cam_fps:F1} fps • {cam_kbps:F1} kbps • {pct_ervas ?? 0:F2}% ervas"; + } public event PropertyChangedEventHandler? PropertyChanged; diff --git a/AgroBase/OperationControl/ViewModels/Views/Operacao/OperacaoTopViewModel.cs b/AgroBase/OperationControl/ViewModels/Views/Operacao/OperacaoTopViewModel.cs index 49eef8135..329976120 100644 --- a/AgroBase/OperationControl/ViewModels/Views/Operacao/OperacaoTopViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Views/Operacao/OperacaoTopViewModel.cs @@ -2,135 +2,136 @@ using System.Windows.Media; using Brush = System.Windows.Media.Brush; using Brushes = System.Windows.Media.Brushes; -using Color = System.Windows.Media.Color; using ColorConverter = System.Windows.Media.ColorConverter; +using Color = System.Windows.Media.Color; namespace OperationControl.ViewModels.Views.Operacao { public class OperacaoTopViewModel : INotifyPropertyChanged { - private string _contextoSelecionado = "BASE"; + private string _contextoSelecionado = "ROVER 01"; public string ContextoSelecionado { get => _contextoSelecionado; - set - { - if (_contextoSelecionado != value) - { - _contextoSelecionado = value; - OnPropertyChanged(nameof(ContextoSelecionado)); - } - } + set { _contextoSelecionado = value; OnPropertyChanged(nameof(ContextoSelecionado)); } } - private string _mensagemStatus = "Sistema inicializado."; - public string MensagemStatus + private string _tituloStatus = "ROVER OPERANDO NORMALMENTE"; + public string TituloStatus { - get => _mensagemStatus; - set - { - if (_mensagemStatus != value) - { - _mensagemStatus = value; - OnPropertyChanged(nameof(MensagemStatus)); - } - } + get => _tituloStatus; + set { _tituloStatus = value; OnPropertyChanged(nameof(TituloStatus)); } } - private string _badgeTexto = "PRONTO"; + private string _motivoPrincipal = "Todos os módulos obrigatórios e regras de controle estão OK."; + public string MotivoPrincipal + { + get => _motivoPrincipal; + set { _motivoPrincipal = value; OnPropertyChanged(nameof(MotivoPrincipal)); } + } + + private string _badgeTexto = "NORMAL"; public string BadgeTexto { get => _badgeTexto; - set - { - if (_badgeTexto != value) - { - _badgeTexto = value; - OnPropertyChanged(nameof(BadgeTexto)); - } - } + set { _badgeTexto = value; OnPropertyChanged(nameof(BadgeTexto)); } } - private Brush _corFundo = Brushes.DimGray; + private string _resumoCurto = "Sistema saudável"; + public string ResumoCurto + { + get => _resumoCurto; + set { _resumoCurto = value; OnPropertyChanged(nameof(ResumoCurto)); } + } + + private Brush _corFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#153D2C")); public Brush CorFundo { get => _corFundo; - set - { - if (_corFundo != value) - { - _corFundo = value; - OnPropertyChanged(nameof(CorFundo)); - } - } + set { _corFundo = value; OnPropertyChanged(nameof(CorFundo)); } } - private Brush _corBadge = Brushes.SteelBlue; + private Brush _corBorda = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2C7A57")); + public Brush CorBorda + { + get => _corBorda; + set { _corBorda = value; OnPropertyChanged(nameof(CorBorda)); } + } + + private Brush _corAccent = Brushes.LightGreen; + public Brush CorAccent + { + get => _corAccent; + set { _corAccent = value; OnPropertyChanged(nameof(CorAccent)); } + } + + private Brush _corBadge = Brushes.LightGreen; public Brush CorBadge { get => _corBadge; - set - { - if (_corBadge != value) - { - _corBadge = value; - OnPropertyChanged(nameof(CorBadge)); - } - } + set { _corBadge = value; OnPropertyChanged(nameof(CorBadge)); } } private Brush _corTexto = Brushes.White; public Brush CorTexto { get => _corTexto; - set - { - if (_corTexto != value) - { - _corTexto = value; - OnPropertyChanged(nameof(CorTexto)); - } - } + set { _corTexto = value; OnPropertyChanged(nameof(CorTexto)); } } - public void DefinirStatusNormal(string contexto, string mensagem) + private Brush _corTextoBadge = Brushes.Black; + public Brush CorTextoBadge { - ContextoSelecionado = contexto; - MensagemStatus = mensagem; + get => _corTextoBadge; + set { _corTextoBadge = value; OnPropertyChanged(nameof(CorTextoBadge)); } + } + + public void MockarNormal() + { + ContextoSelecionado = "ROVER 01"; + TituloStatus = "ROVER OPERANDO NORMALMENTE"; + MotivoPrincipal = "Todos os módulos obrigatórios e regras de controle estão OK."; BadgeTexto = "NORMAL"; - CorFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1F3A2D")); + ResumoCurto = "Sistema saudável"; + + CorFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#153D2C")); + CorBorda = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2C7A57")); + CorAccent = Brushes.LightGreen; CorBadge = Brushes.LightGreen; CorTexto = Brushes.White; + CorTextoBadge = Brushes.Black; } - public void DefinirStatusManual(string contexto, string mensagem) + public void MockarParadoPorInclinacao() { - ContextoSelecionado = contexto; - MensagemStatus = mensagem; - BadgeTexto = "MANUAL"; - CorFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2D3440")); - CorBadge = Brushes.Gold; - CorTexto = Brushes.White; - } + ContextoSelecionado = "ROVER 01"; + TituloStatus = "ROVER PARADO POR SEGURANÇA"; + MotivoPrincipal = "Inclinação lateral acima do limite permitido."; + BadgeTexto = "PARADO"; + ResumoCurto = "Verificar IMU"; - public void DefinirStatusAtencao(string contexto, string mensagem) - { - ContextoSelecionado = contexto; - MensagemStatus = mensagem; - BadgeTexto = "ATENÇÃO"; - CorFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A3315")); - CorBadge = Brushes.Orange; - CorTexto = Brushes.White; - } - - public void DefinirStatusCritico(string contexto, string mensagem) - { - ContextoSelecionado = contexto; - MensagemStatus = mensagem; - BadgeTexto = "CRÍTICO"; CorFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A1F1F")); + CorBorda = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9B3A3A")); + CorAccent = Brushes.OrangeRed; CorBadge = Brushes.OrangeRed; CorTexto = Brushes.White; + CorTextoBadge = Brushes.Black; + } + + public void MockarObstaculo() + { + ContextoSelecionado = "ROVER 01"; + TituloStatus = "MOVIMENTO BLOQUEADO PREVENTIVAMENTE"; + MotivoPrincipal = "Obstáculo detectado no corredor à frente."; + BadgeTexto = "ATENÇÃO"; + ResumoCurto = "Inspecionar caminho"; + + CorFundo = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A3315")); + CorBorda = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#B7791F")); + CorAccent = Brushes.Orange; + CorBadge = Brushes.Orange; + CorTexto = Brushes.White; + CorTextoBadge = Brushes.Black; } public event PropertyChangedEventHandler? PropertyChanged; @@ -139,4 +140,4 @@ namespace OperationControl.ViewModels.Views.Operacao PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nome)); } } -} +} \ No newline at end of file diff --git a/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs b/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs index 77f0fbe98..71883f604 100644 --- a/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs +++ b/AgroBase/OperationControl/ViewModels/Windows/DockWindowViewModel.cs @@ -540,9 +540,9 @@ namespace OperationControl.ViewModels // ESQUERDA var conexao = obj.ModulosSaude?.FirstOrDefault(x => x.modulo == AgroBase.Models.Enums.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; - double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value() ?? 0; + double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value() ?? 0.0; + double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value() ?? 0.0; + double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value() ?? 0.0; string comunicacao = $"{conexao_latencia:0.00} ms {bwTot:0.00} Mbps"; string comunicacao_extended = $"{conexao_latencia:0.00} ms {conexao_perda:F2}% {bwTot:0.00} Mbps"; @@ -565,8 +565,8 @@ namespace OperationControl.ViewModels r.Modo = obj.Operacao == null ? "-" : obj.Operacao.Modo.ToString(); r.Status = obj.Operacao == null ? "-" : $"{obj.Operacao.Status}" + (obj.Operacao.Status == AgroBase.Models.Enums.StatusOperacao.Aguardando ? $" ({obj.Operacao.TempoAguardandoSegs:0}s)" : ""); r.Carro = obj.Trajetoria == null ? "-" : obj.Trajetoria.StatusCarro.ToString(); - r.Temperatura = $"{(obj.PerformanceNcp?.CPU_temp ?? 0):0.00} °C"; - r.RuaAtual = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.CorredorAtualDistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.CorredorAtualDistanciaTotal:0.00} m " + $"({obj.Trajetoria.CorredorAtualIdx + 1})"; + r.Temperatura = $"{(obj.Refrigeracao?.Temperatura ?? 0):F2} °C"; + r.RuaAtual = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.CorredorAtualDistanciaPercorrida:F2} m / " + $"{obj.Trajetoria.CorredorAtualDistanciaTotal:0.00} m " + $"({obj.Trajetoria.CorredorAtualIdx + 1})"; r.AreaTotal = obj.Trajetoria == null ? "-" : $"{obj.Trajetoria.DistanciaPercorrida:0.00} m / " + $"{obj.Trajetoria.DistanciaTotal:0.00} m"; r.Bateria = obj.Bateria == null ? "-" : $"{obj.Bateria.TensaoInstantanea:0.00} V " + $"({TimeSpan.FromMinutes(obj.Bateria.TempoEstimadoRestanteMinutos):dd\\.hh\\:mm\\:ss} " + $"{obj.Bateria.DistanciaEstimadaRestanteMetros:0.00} m)"; r.Herbicida = obj.Atuador == null ? "-" : $"{obj.Atuador.VolumeReservatorioL:0.00} L " + $"({TimeSpan.FromMinutes(obj.Atuador.TempoEstimadoRestanteMinutos):hh\\:mm\\:ss} " + $"{obj.Atuador.DistanciaEstimadaRestanteMetros:0.00} m)"; @@ -620,45 +620,30 @@ namespace OperationControl.ViewModels bico.TempoAtuado = _bc.TempoAtuado / 1000.0; } + // HEADING + var HDG = _viewOperacaoCenter.Monitoramento.Heading; + HDG.Heading = obj?.Gnss?.OrientacaoReal ?? double.NaN; + HDG.CourseHeading = obj?.Trajetoria?.AnguloCaminho ?? double.NaN; + + // ATTITUDE + var IMU = _viewOperacaoCenter.Monitoramento.Attitude; + if (obj?.Imu?.InclinacaoFrontal != null) IMU.PitchDeg = (double)obj.Imu.InclinacaoFrontal; + if (obj?.Imu?.InclinacaoLateral != null) IMU.RollDeg = (double)obj.Imu.InclinacaoLateral; + IMU.LateralError = erroLateral; + // CAMERAS - var camera_snr = obj?.Cameras?.FirstOrDefault(x => x.dispositivo == AgroBase.Models.Enums.T_Code.Snr); - var camera_cam = obj?.Cameras?.FirstOrDefault(x => x.dispositivo == AgroBase.Models.Enums.T_Code.Cam); - _viewOperacaoCenter?.Monitoramento?._vm?.AtualizarDadosCameras(camera_snr?.fps, camera_snr?.stream_kbps, camera_cam?.fps, camera_cam?.stream_kbps, obj?.OperadorVisual?.StatusCarro, obj?.Atuador?.PercentualErvasNoRadar); + var snr = obj?.Cameras?.FirstOrDefault(x => x.dispositivo == AgroBase.Models.Enums.T_Code.Snr); + var cam = obj?.Cameras?.FirstOrDefault(x => x.dispositivo == AgroBase.Models.Enums.T_Code.Cam); + + _viewOperacaoCenter?.Monitoramento?._vm?.AtualizarDadosCameras(snr, cam, obj?.OperadorVisual?.StatusCarro, obj?.Atuador?.PercentualErvasNoRadar); } - public void AtualizarTopOperacaoBase() - { - System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - _viewOperacaoTop?._vm.DefinirStatusNormal("BASE", "Base selecionada. Sistema pronto."); - })); - } - public void AtualizarTopOperacaoRover(string nomeRover, bool modoManual) { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { - if (modoManual) - _viewOperacaoTop?._vm.DefinirStatusManual($"{nomeRover}", "Rover em modo manual. Comandos locais liberados."); - else - _viewOperacaoTop?._vm.DefinirStatusNormal($"{nomeRover}", "Rover selecionado. Operação pronta."); - })); - } - - public void AtualizarTopOperacaoAlerta(string contexto, string mensagem) - { - System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - _viewOperacaoTop?._vm.DefinirStatusAtencao(contexto, mensagem); - })); - } - - public void AtualizarTopOperacaoCritico(string contexto, string mensagem) - { - System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - _viewOperacaoTop?._vm.DefinirStatusCritico(contexto, mensagem); + _viewOperacaoTop?._vm.MockarNormal(); })); } diff --git a/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml b/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml index ea5d84d18..c535f70e7 100644 --- a/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml +++ b/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml @@ -15,8 +15,8 @@ - - + + @@ -42,9 +42,11 @@ - - + + + + @@ -67,9 +69,11 @@ - - + + + + @@ -85,15 +89,22 @@ - + + + + + + + + - - + + diff --git a/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml.cs b/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml.cs index bd3a5df14..1fcf785e9 100644 --- a/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml.cs +++ b/AgroBase/OperationControl/Views/Operacao/MonitoramentoView.xaml.cs @@ -14,6 +14,8 @@ namespace OperationControl.Views.Operacao private TcpVideoReceiver videoFront; private TcpVideoReceiver videoWeed; public PulverizadorIndicator Pulverizador => areaPulverizador; + public HeadingIndicator Heading => areaHeading; + public AttitudeIndicator Attitude => areaAttitude; public MonitoramentoView() { @@ -58,7 +60,6 @@ namespace OperationControl.Views.Operacao videoFront.Iniciar(); var tipo = _vm.CameraFrontalFrameTipoSelecionado?.Valor ?? AgroBase.Models.Enums.TipoFrameCamera.Rgb; VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Snr, true, tipo); - //_vm.CameraFrontalSemSinal = false; } public void PararStreamCameraFrontal(bool finalizar = false) @@ -66,7 +67,6 @@ namespace OperationControl.Views.Operacao videoFront.Parar(); var tipo = _vm.CameraFrontalFrameTipoSelecionado?.Valor ?? AgroBase.Models.Enums.TipoFrameCamera.Rgb; VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Snr, false, tipo); - //_vm.CameraFrontalSemSinal = true; } #endregion @@ -87,7 +87,6 @@ namespace OperationControl.Views.Operacao videoWeed.Iniciar(); var tipo = _vm.CameraErvasFrameTipoSelecionado?.Valor ?? AgroBase.Models.Enums.TipoFrameCamera.Rgb; VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Cam, true, tipo); - //_vm.CameraErvasSemSinal = false; } public void PararStreamCameraErvas(bool finalizar = false) @@ -95,7 +94,6 @@ namespace OperationControl.Views.Operacao videoWeed.Parar(); var tipo = _vm.CameraErvasFrameTipoSelecionado?.Valor ?? AgroBase.Models.Enums.TipoFrameCamera.Rgb; VariaveisControleOperacao.EnviarComandoTransmissaoVideo(AgroBase.Models.Enums.T_Code.Cam, false, tipo); - //_vm.CameraErvasSemSinal = true; } #endregion diff --git a/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml b/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml index 40071d857..779d8a09e 100644 --- a/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml +++ b/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml @@ -1,73 +1,82 @@  - - - - - - - + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + mc:Ignorable="d" + d:DesignWidth="1400" + d:DesignHeight="92"> - - - - - - - + + - - - - + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml.cs b/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml.cs index d82edce1f..de3ace1b1 100644 --- a/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml.cs +++ b/AgroBase/OperationControl/Views/Operacao/OperacaoTopView.xaml.cs @@ -1,24 +1,7 @@ using OperationControl.ViewModels.Views.Operacao; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace OperationControl.Views.Operacao { - /// - /// Interaction logic for OperacaoTopView.xaml - /// public partial class OperacaoTopView : System.Windows.Controls.UserControl { public OperacaoTopViewModel _vm; @@ -28,8 +11,6 @@ namespace OperationControl.Views.Operacao InitializeComponent(); _vm = new OperacaoTopViewModel(); DataContext = _vm; - - _vm.DefinirStatusNormal("BASE", "Sistema pronto para operação."); } } -} +} \ No newline at end of file diff --git a/AgroBase/OperationControl/Windows/DockWindow.xaml b/AgroBase/OperationControl/Windows/DockWindow.xaml index 6e7d1f4ed..0efec5568 100644 --- a/AgroBase/OperationControl/Windows/DockWindow.xaml +++ b/AgroBase/OperationControl/Windows/DockWindow.xaml @@ -14,11 +14,11 @@ - + - + @@ -32,24 +32,17 @@ - + - + - + @@ -85,11 +78,7 @@ - +