Compare commits

...

3 Commits

7 changed files with 139 additions and 52 deletions

View File

@ -774,8 +774,8 @@ namespace AgroBase.Models.Operacoes
Imu = new OperacaoSensoriamentoLogImuModel Imu = new OperacaoSensoriamentoLogImuModel
{ {
Iniciado = sen?.IMU?.Iniciado ?? false, Iniciado = sen?.IMU?.Iniciado ?? false,
InclinacaoFrontal = sen?.IMU?.PitchSeguro ?? 0, InclinacaoFrontal = sen?.IMU?.RollSeguro ?? 0,
InclinacaoLateral = sen?.IMU?.RollSeguro ?? 0, InclinacaoLateral = sen?.IMU?.PitchSeguro ?? 0,
Rotacao = sen?.IMU?.YawSeguro ?? 0 Rotacao = sen?.IMU?.YawSeguro ?? 0
}; };
} }

View File

@ -2881,8 +2881,16 @@ namespace AgroBase.Models
bool bateriaOk = BateriaSuficiente && (_Bateria?.Iniciado ?? false); bool bateriaOk = BateriaSuficiente && (_Bateria?.Iniciado ?? false);
bool herbOk = HerbicidaSuficiente && (_Reservatorio?.Iniciado ?? false); bool herbOk = HerbicidaSuficiente && (_Reservatorio?.Iniciado ?? false);
bool exigirPulverizacaoTotal = bool pulverizadorAutomatico = op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
op.Parametros?.ModulosMandatorios?.Any(x => x.Dispositivo == T_Code.Atu && x.Mandatorio && x.Utilizar) ?? false;
bool atuMandatorio =
op.Parametros?.ModulosMandatorios?.Any(x =>
x.Dispositivo == T_Code.Atu &&
x.Mandatorio &&
x.Utilizar
) ?? false;
bool exigirPulverizacaoTotal = pulverizadorAutomatico && atuMandatorio;
// ========================== // ==========================
// 1) Calcula status "base" (sem considerar trava) // 1) Calcula status "base" (sem considerar trava)
@ -3003,6 +3011,8 @@ namespace AgroBase.Models
} }
else else
{ {
motivosBloqueio.Clear();
// ❌ Ainda está crítico, mas operador insistiu -> OkSupervisionado (override) // ❌ Ainda está crítico, mas operador insistiu -> OkSupervisionado (override)
statusFinal = AutonomiaCorredorStatus.OkSupervisionado; statusFinal = AutonomiaCorredorStatus.OkSupervisionado;
liberadoFinal = true; liberadoFinal = true;
@ -3011,9 +3021,6 @@ namespace AgroBase.Models
(string.IsNullOrEmpty(motivoBase) ? "Autonomia insuficiente, mas liberado." : motivoBase); (string.IsNullOrEmpty(motivoBase) ? "Autonomia insuficiente, mas liberado." : motivoBase);
motivosBloqueio.Add($"Corredor {idxCorredor + 1}: LIBERAÇÃO MANUAL. Autonomia insuficiente, mas houve liberação supervisionada."); motivosBloqueio.Add($"Corredor {idxCorredor + 1}: LIBERAÇÃO MANUAL. Autonomia insuficiente, mas houve liberação supervisionada.");
motivosBloqueio.Clear();
// Destrava o corredor (o operador assumiu) // Destrava o corredor (o operador assumiu)
DestravarCorredoresAte(idxCorredor); DestravarCorredoresAte(idxCorredor);
} }

View File

@ -862,7 +862,7 @@ namespace AgroBase.Models
public static double ComprimentoFrente { get; } = 7.0; // 7 public static double ComprimentoFrente { get; } = 7.0; // 7
public static double ComprimentoTras { get; } = 107.0; // 107 public static double ComprimentoTras { get; } = 107.0; // 107
public static double DistanciaEntreEixos { get; } = 92.0; public static double DistanciaEntreEixos { get; } = 92.0;
public static double LeverArmFrontalCm { get; } = 100.0; // cm public static double LeverArmFrontalCm { get; } = 37.0; // cm
public static double LeverArmLateralCm { get; } = 0.0; // cm public static double LeverArmLateralCm { get; } = 0.0; // cm
public static double LarguraEquipamentoMm public static double LarguraEquipamentoMm
{ {

View File

@ -304,7 +304,10 @@ class ModuloAtuador(ModuloDiagnosticoBase):
) )
herbicida_ok_corredor = self._bool(corredor.get("herbicida_ok", True), True) herbicida_ok_corredor = self._bool(corredor.get("herbicida_ok", True), True)
motivo_herbicida_corredor = corredor.get("motivo_hrb", "Herbicida insuficiente para o corredor") motivo_herbicida_corredor = self._texto_ou_padrao(
corredor.get("motivo_hrb"),
padrao="Herbicida insuficiente para pulverizar o corredor completo."
)
bomba_principal = self._achar_por_label(bombas, "BMBLN") bomba_principal = self._achar_por_label(bombas, "BMBLN")
agitador = self._achar_por_label(bombas, "BMBAGT") agitador = self._achar_por_label(bombas, "BMBAGT")
@ -443,6 +446,17 @@ class ModuloAtuador(ModuloDiagnosticoBase):
if percent < percent_min: if percent < percent_min:
motivos.append(f"Nível de herbicida abaixo do mínimo ({percent:.1f}% < {percent_min:.1f}%)") motivos.append(f"Nível de herbicida abaixo do mínimo ({percent:.1f}% < {percent_min:.1f}%)")
herbicida_pode_bloquear = bool(
ctx["pulverizador_automatico"] and
ctx["operacao_iniciada"] and
not ctx["calibrando"] and
not ctx["finalizando"] and
not ctx["pausa"] and
not ctx["emergencia"]
)
if herbicida_pode_bloquear:
condicoes.append({ condicoes.append({
"label": "Reservatório", "label": "Reservatório",
"valor": percent, "valor": percent,
@ -455,6 +469,19 @@ class ModuloAtuador(ModuloDiagnosticoBase):
], ],
}) })
score = min(score, 65) score = min(score, 65)
else:
condicoes.append({
"label": "Reservatório",
"valor": percent,
"severidade": 35,
"classe": "recurso_operacional",
"descricao": "Nível do reservatório abaixo do mínimo, mas pulverização automática não está ativa; não bloqueia deslocamento.",
"acoes": [
"Reabastecer antes de habilitar pulverização.",
"Recalibrar sensor de massa se o valor estiver incoerente.",
],
})
score = min(score, 90)
elif percent < 20: elif percent < 20:
motivos.append(f"Nível de herbicida baixo ({percent:.1f}%)") motivos.append(f"Nível de herbicida baixo ({percent:.1f}%)")
condicoes.append({ condicoes.append({
@ -943,6 +970,15 @@ class ModuloAtuador(ModuloDiagnosticoBase):
calibrando = ctx["calibrando"] calibrando = ctx["calibrando"]
bomba_principal = ctx["bomba_principal"] bomba_principal = ctx["bomba_principal"]
herbicida_pode_bloquear = bool(
auto and
ctx["operacao_iniciada"] and
not calibrando and
not ctx["finalizando"] and
not ctx["pausa"] and
not ctx["emergencia"]
)
em_transicao = self._pulverizacao_em_transicao(ctx) em_transicao = self._pulverizacao_em_transicao(ctx)
bomba_principal_on = self._bomba_principal_ligada_fisica(ctx) bomba_principal_on = self._bomba_principal_ligada_fisica(ctx)
bomba_principal_cmd = self._bomba_principal_comandada(ctx) bomba_principal_cmd = self._bomba_principal_comandada(ctx)
@ -984,6 +1020,8 @@ class ModuloAtuador(ModuloDiagnosticoBase):
motivos.append( motivos.append(
f"Reservatório abaixo do mínimo ({ctx['percent_reservatorio']:.1f}% < {ctx['percent_min']:.1f}%)" f"Reservatório abaixo do mínimo ({ctx['percent_reservatorio']:.1f}% < {ctx['percent_min']:.1f}%)"
) )
if herbicida_pode_bloquear:
condicoes.append({ condicoes.append({
"label": "Reservatório", "label": "Reservatório",
"valor": ctx["percent_reservatorio"], "valor": ctx["percent_reservatorio"],
@ -996,6 +1034,18 @@ class ModuloAtuador(ModuloDiagnosticoBase):
], ],
}) })
score = min(score, 65) score = min(score, 65)
else:
condicoes.append({
"label": "Reservatório",
"valor": ctx["percent_reservatorio"],
"severidade": 35,
"classe": "recurso_operacional",
"descricao": "Nível de herbicida abaixo do mínimo, mas pulverização automática não está ativa; não bloqueia deslocamento.",
"acoes": [
"Reabastecer antes de pulverizar.",
],
})
score = min(score, 90)
elif ctx["percent_reservatorio"] < 20: elif ctx["percent_reservatorio"] < 20:
motivos.append(f"Reservatório baixo ({ctx['percent_reservatorio']:.1f}%)") motivos.append(f"Reservatório baixo ({ctx['percent_reservatorio']:.1f}%)")
@ -1010,18 +1060,37 @@ class ModuloAtuador(ModuloDiagnosticoBase):
score = min(score, 90) score = min(score, 90)
if not ctx["herbicida_ok_corredor"]: if not ctx["herbicida_ok_corredor"]:
motivos.append(ctx["motivo_herbicida_corredor"]) motivo = self._texto_ou_padrao(
ctx.get("motivo_herbicida_corredor"),
padrao="Herbicida insuficiente para pulverizar o corredor completo."
)
motivos.append(motivo)
if herbicida_pode_bloquear:
condicoes.append({ condicoes.append({
"label": "Autonomia de herbicida", "label": "Autonomia de herbicida",
"valor": ctx["percent_reservatorio"], "valor": ctx["percent_reservatorio"],
"severidade": 90, "severidade": 90,
"classe": "recurso_operacional", "classe": "recurso_operacional",
"descricao": ctx["motivo_herbicida_corredor"], "descricao": motivo,
"acoes": [ "acoes": [
"Reabastecer ou reduzir distância do corredor planejado.", "Reabastecer ou reduzir distância do corredor planejado.",
], ],
}) })
score = min(score, 80) score = min(score, 80)
else:
condicoes.append({
"label": "Autonomia de herbicida",
"valor": ctx["percent_reservatorio"],
"severidade": 35,
"classe": "recurso_operacional",
"descricao": f"{motivo} Pulverização automática desligada; condição informativa, sem bloqueio de deslocamento.",
"acoes": [
"Reabastecer antes de habilitar a pulverização automática.",
],
})
score = min(score, 90)
if ( if (
(bicos_cmd or bicos_on) and (bicos_cmd or bicos_on) and
@ -1567,6 +1636,17 @@ class ModuloAtuador(ModuloDiagnosticoBase):
not self._telemetria_depois_do_comando(dados) not self._telemetria_depois_do_comando(dados)
) )
def _texto_ou_padrao(self, *valores, padrao=""):
for valor in valores:
try:
texto = str(valor or "").strip()
if texto:
return texto
except Exception:
pass
return str(padrao or "").strip()
@staticmethod @staticmethod
def _bool(valor, default=False): def _bool(valor, default=False):

View File

@ -1260,7 +1260,7 @@ class ModuloIPBribge(ModuloDiagnosticoBase):
"degraded_restrictive_signal": degraded_restrictive_signal, "degraded_restrictive_signal": degraded_restrictive_signal,
} }
def _elapsed_since(now_mono: float, started_mono: float) -> float: def _elapsed_since(self, now_mono: float, started_mono: float) -> float:
return max(0.0, now_mono - started_mono) if started_mono > 0 else 0.0 return max(0.0, now_mono - started_mono) if started_mono > 0 else 0.0
def _update_persistence_counters(self, new_ping_sample: bool, instant): def _update_persistence_counters(self, new_ping_sample: bool, instant):

View File

@ -131,8 +131,8 @@ namespace OperationControl.Controls
// Derivados // Derivados
public double MaxAbsAngle => Math.Max(Math.Abs(RollDeg), Math.Abs(PitchDeg)); public double MaxAbsAngle => Math.Max(Math.Abs(RollDeg), Math.Abs(PitchDeg));
public string RollTexto => $"LAT {FormatSignedDeg(RollDeg)}"; public string RollTexto => $"FRT {FormatSignedDeg(RollDeg)}";
public string PitchTexto => $"FRT {FormatSignedDeg(PitchDeg)}"; public string PitchTexto => $"LAT {FormatSignedDeg(PitchDeg)}";
private static string FormatSignedDeg(double value) private static string FormatSignedDeg(double value)
{ {

View File

@ -692,7 +692,7 @@ namespace OperationControl.ViewModels
// ============================ // ============================
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ {
_viewOperacaoTop?._vm.AtualizarStatus( _viewOperacaoTop?._vm?.AtualizarStatus(
statusVisual, statusVisual,
rover.RoverId, rover.RoverId,
obj?.Operacao?.Status ?? StatusOperacao.NaoIniciado, obj?.Operacao?.Status ?? StatusOperacao.NaoIniciado,
@ -708,7 +708,7 @@ namespace OperationControl.ViewModels
{ {
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ {
_viewOperacaoTop?._vm.AtualizarStatus(status, titulo, StatusOperacao.Parametrizando, linha1, linha2.Replace("\n\n", " • "), status_str, resumo); _viewOperacaoTop?._vm?.AtualizarStatus(status, titulo, StatusOperacao.Parametrizando, linha1, linha2.Replace("\n\n", " • "), status_str, resumo);
})); }));
} }
@ -1048,8 +1048,8 @@ namespace OperationControl.ViewModels
// ATTITUDE // ATTITUDE
var IMU = _viewOperacaoCenter.Monitoramento.Attitude; var IMU = _viewOperacaoCenter.Monitoramento.Attitude;
if (obj?.Imu?.InclinacaoFrontal != null) IMU.PitchDeg = (double)obj.Imu.InclinacaoFrontal; if (obj?.Imu?.InclinacaoLateral != null) IMU.PitchDeg = (double)obj.Imu.InclinacaoLateral;
if (obj?.Imu?.InclinacaoLateral != null) IMU.RollDeg = (double)obj.Imu.InclinacaoLateral; if (obj?.Imu?.InclinacaoFrontal != null) IMU.RollDeg = (double)obj.Imu.InclinacaoFrontal;
IMU.LateralError = erroLateral; IMU.LateralError = erroLateral;