ajustes nas cameras, imu, implementado heading e ajustes no mapa
This commit is contained in:
parent
7f39aaecad
commit
a3590bd224
|
|
@ -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<OAKImuModel>(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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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,
|
||||
)
|
||||
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
|
||||
|
||||
# Fase 2: criar pipeline e instanciar normalmente
|
||||
try:
|
||||
|
|
@ -134,128 +83,24 @@ 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
|
||||
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
|
||||
)
|
||||
|
||||
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.")
|
||||
|
||||
def atualizar_saude(self):
|
||||
# 1) Ver se “existe” no contexto e se o handle da GAL está aberto
|
||||
ctx_cam = ContextoGlobalRedis.get_cameras().get(self.mx_id) or {}
|
||||
|
|
@ -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 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 table.get(int(mode), table[1])
|
||||
return self.ultimo_frame_rgb, resultado
|
||||
|
||||
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()
|
||||
# 2) Se preciso atualizar o RGB agora
|
||||
start = time.time()
|
||||
|
||||
# cooldown mínimo entre trocas
|
||||
if (now - self._last_mode_change_ts) < 1.5:
|
||||
return
|
||||
# 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")
|
||||
|
||||
# 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
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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
|
||||
)
|
||||
except Exception as e:
|
||||
self.gst_proc = None
|
||||
self.mostrar_log(f"Erro ao criar script de transmissao de video: {e}")
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
|
@ -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"],
|
||||
)
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,7 +302,6 @@ 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)
|
||||
|
||||
from visual_worker.config import load_seg_config, load_det_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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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,7 +202,6 @@ 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)
|
||||
|
||||
from weed_worker.config import 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<double>() ?? 0.0;
|
||||
double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double>() ?? 0.0;
|
||||
double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double>() ?? 0;
|
||||
double conexao_latencia = conexao?.detalhes?["avg_rtt"]?.Value<double?>() ?? 0.0;
|
||||
double conexao_perda = conexao?.detalhes?["loss_pct"]?.Value<double?>() ?? 0.0;
|
||||
double bwTot = conexao?.detalhes?["bw_total_mbps"]?.Value<double?>() ?? 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();
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2.2*"/>
|
||||
<RowDefinition Height="1.3*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
|
|
@ -42,9 +42,11 @@
|
|||
</DockPanel>
|
||||
|
||||
<Grid Grid.Row="1" Background="Black">
|
||||
<TextBlock Text="SEM SINAL" Foreground="Gray" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding CameraFrontalSemSinal, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
|
||||
<Image x:Name="imgVideoFront" Stretch="Uniform" RenderOptions.BitmapScalingMode="LowQuality"/>
|
||||
|
||||
<Border Background="Black" Visibility="{Binding CameraFrontalSemSinal, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock Text="SEM SINAL" Foreground="Gray" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
|
@ -67,9 +69,11 @@
|
|||
</DockPanel>
|
||||
|
||||
<Grid Grid.Row="1" Background="Black">
|
||||
<TextBlock Text="SEM SINAL" Foreground="Gray" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding CameraErvasSemSinal, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
|
||||
<Image x:Name="imgVideoWeed" Stretch="Uniform" RenderOptions.BitmapScalingMode="LowQuality"/>
|
||||
|
||||
<Border Background="Black" Visibility="{Binding CameraErvasSemSinal, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock Text="SEM SINAL" Foreground="Gray" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
|
@ -85,15 +89,22 @@
|
|||
|
||||
<TextBlock Grid.Row="0" Text="Mapa e posição do rover" Margin="0,0,0,8" FontSize="12" FontWeight="SemiBold" Foreground="#E6E6E6"/>
|
||||
|
||||
<controls:MapViewControl x:Name="MapaMonitoramento" MarkerClicked="MAP_MarkerClicked" StreetMapClicked="MAP_StreetMapClicked" Grid.Row="1" Latitude="-22.1726572492617" Longitude="-47.3952163870556" Scale="5000" />
|
||||
<!-- Área do mapa com overlays -->
|
||||
<Grid Grid.Row="1">
|
||||
<controls:MapViewControl x:Name="MapaMonitoramento" MarkerClicked="MAP_MarkerClicked" StreetMapClicked="MAP_StreetMapClicked" Latitude="-22.1726572492617" Longitude="-47.3952163870556" Scale="5000" />
|
||||
|
||||
<controls:HeadingIndicator x:Name="areaHeading" Width="130" Height="130" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="2"/>
|
||||
|
||||
<controls:AttitudeIndicator x:Name="areaAttitude" Width="160" Height="180" CriticalLimit="20" WarningLimit="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="2"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ÁREA DIREITA INFERIOR -->
|
||||
<Grid Grid.Row="1" Grid.Column="1" Margin="6,6,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1.45*"/>
|
||||
<RowDefinition Height="0.55*"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
<RowDefinition Height="1*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- PULVERIZADOR -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -3,71 +3,80 @@
|
|||
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:local="clr-namespace:OperationControl.Views.Operacao"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="70" d:DesignWidth="1200">
|
||||
<Border Background="{Binding CorFundo}"
|
||||
CornerRadius="10"
|
||||
Padding="14,10"
|
||||
Margin="10,8,10,6">
|
||||
d:DesignWidth="1400"
|
||||
d:DesignHeight="92">
|
||||
|
||||
<Grid Height="92" Margin="10,0,10,0">
|
||||
<Border Background="{Binding CorFundo}" BorderBrush="{Binding CorBorda}" BorderThickness="1" CornerRadius="12" Padding="12,10">
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="260"/>
|
||||
<ColumnDefinition Width="240"/>
|
||||
<ColumnDefinition Width="10"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="180"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Contexto -->
|
||||
<Border Grid.Column="0"
|
||||
Background="#22000000"
|
||||
CornerRadius="8"
|
||||
Padding="12,8"
|
||||
Margin="0,0,12,0">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="Selecionado: "
|
||||
Foreground="#D8D8D8"
|
||||
FontSize="14"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding ContextoSelecionado}"
|
||||
Foreground="{Binding CorTexto}"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<!-- BLOCO ESQUERDO -->
|
||||
<Border Grid.Column="0" Background="#1AFFFFFF" CornerRadius="10" Padding="12,8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="18"/>
|
||||
<RowDefinition Height="28"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="ROVER / CONTEXTO" Foreground="#D8D8D8" FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
|
||||
<Viewbox Grid.Row="1" Stretch="Uniform" StretchDirection="DownOnly" HorizontalAlignment="Left" VerticalAlignment="Center" Height="24">
|
||||
<TextBlock Text="{Binding ContextoSelecionado}" Foreground="{Binding CorTexto}" FontSize="18" FontWeight="Bold" TextWrapping="NoWrap"/>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Mensagem principal -->
|
||||
<Border Grid.Column="1"
|
||||
Background="#16000000"
|
||||
CornerRadius="8"
|
||||
Padding="14,8"
|
||||
Margin="0,0,12,0">
|
||||
<TextBlock Text="{Binding MensagemStatus}"
|
||||
Foreground="{Binding CorTexto}"
|
||||
FontSize="17"
|
||||
FontWeight="SemiBold"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"/>
|
||||
<!-- BARRA VERTICAL -->
|
||||
<Border Grid.Column="1" Width="4" Margin="0,4" Background="{Binding CorAccent}" CornerRadius="2"/>
|
||||
|
||||
<!-- BLOCO CENTRAL -->
|
||||
<Border Grid.Column="2" Margin="10,0,10,0" Background="#14000000" CornerRadius="10" Padding="14,8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="30"/>
|
||||
<RowDefinition Height="24"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- STATUS PRINCIPAL -->
|
||||
<Viewbox Grid.Row="0" Stretch="Uniform" StretchDirection="DownOnly" HorizontalAlignment="Left" VerticalAlignment="Center" Height="26">
|
||||
<TextBlock Text="{Binding TituloStatus}" Foreground="{Binding CorTexto}" FontSize="24" FontWeight="Bold" TextWrapping="NoWrap"/>
|
||||
</Viewbox>
|
||||
|
||||
<!-- MOTIVO -->
|
||||
<Viewbox Grid.Row="1" Stretch="Uniform" StretchDirection="DownOnly" HorizontalAlignment="Left" VerticalAlignment="Center" Height="18">
|
||||
<TextBlock Text="{Binding MotivoPrincipal}" Foreground="#F2F2F2" FontSize="14" FontWeight="SemiBold" TextWrapping="NoWrap"/>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Badge -->
|
||||
<Border Grid.Column="2"
|
||||
Background="{Binding CorBadge}"
|
||||
CornerRadius="18"
|
||||
Padding="12,8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="120">
|
||||
<TextBlock Text="{Binding BadgeTexto}"
|
||||
Foreground="Black"
|
||||
FontWeight="Bold"
|
||||
FontSize="14"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"/>
|
||||
<!-- BLOCO DIREITO -->
|
||||
<Border Grid.Column="3" Background="#1AFFFFFF" CornerRadius="10" Padding="10,8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="24"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Background="{Binding CorBadge}" CornerRadius="14" Padding="10,4" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<Viewbox Stretch="Uniform" StretchDirection="DownOnly" Height="18">
|
||||
<TextBlock Text="{Binding BadgeTexto}" Foreground="{Binding CorTextoBadge}" FontWeight="Bold" FontSize="13" TextWrapping="NoWrap" HorizontalAlignment="Center"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
|
||||
<Viewbox Grid.Row="1" Stretch="Uniform" StretchDirection="DownOnly" Height="16" VerticalAlignment="Bottom">
|
||||
<TextBlock Text="{Binding ResumoCurto}" Foreground="{Binding CorTexto}" FontSize="11" FontWeight="SemiBold" TextWrapping="NoWrap" HorizontalAlignment="Center"/>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for OperacaoTopView.xaml
|
||||
/// </summary>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,11 +14,11 @@
|
|||
<Grid>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="100"/>
|
||||
<RowDefinition Height="110"/>
|
||||
<!-- Topo -->
|
||||
<RowDefinition Height="*"/>
|
||||
<!-- Centro -->
|
||||
<RowDefinition Height="120"/>
|
||||
<RowDefinition Height="90"/>
|
||||
<!-- Rodapé -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
|
@ -32,24 +32,17 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Topo (span em 3 colunas) -->
|
||||
<Border Grid.Row="0" Grid.ColumnSpan="3"
|
||||
Background="#222"
|
||||
Padding="8">
|
||||
<Border Grid.Row="0" Grid.ColumnSpan="3" Background="#222">
|
||||
<ContentControl Content="{Binding TopContent}"/>
|
||||
</Border>
|
||||
|
||||
<!-- Área central principal -->
|
||||
<Border Grid.Row="1" Grid.Column="1"
|
||||
Margin="8"
|
||||
BorderThickness="1"
|
||||
BorderBrush="Gray">
|
||||
<Border Grid.Row="1" Grid.Column="1" Margin="8" BorderThickness="1" BorderBrush="Gray">
|
||||
<ContentControl Content="{Binding CenterContent}"/>
|
||||
</Border>
|
||||
|
||||
<!-- Rodapé (span 3 colunas) -->
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="3"
|
||||
Background="#181818"
|
||||
Padding="8">
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="3" Background="#181818" Padding="5">
|
||||
<ContentControl Content="{Binding BottomContent}"/>
|
||||
</Border>
|
||||
|
||||
|
|
@ -85,11 +78,7 @@
|
|||
</Grid>
|
||||
|
||||
<!-- Lateral direita -->
|
||||
<Border Grid.Row="1" Grid.Column="2"
|
||||
Margin="8"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#404040"
|
||||
Background="#1C1C1C">
|
||||
<Border Grid.Row="1" Grid.Column="2" Margin="8" BorderThickness="1" BorderBrush="#404040" Background="#1C1C1C">
|
||||
<ContentControl Content="{Binding RightContent}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
|
|
|||
Loading…
Reference in New Issue