Compare commits
2 Commits
fce03f5044
...
bb546da70f
| Author | SHA1 | Date |
|---|---|---|
|
|
bb546da70f | |
|
|
bfed826136 |
|
|
@ -30,7 +30,7 @@ def main():
|
||||||
T_Code.Sen: ModuloSensoriamento(),
|
T_Code.Sen: ModuloSensoriamento(),
|
||||||
T_Code.Atu: ModuloAtuador(),
|
T_Code.Atu: ModuloAtuador(),
|
||||||
#T_Code.Lra: ModuloLoRa(),
|
#T_Code.Lra: ModuloLoRa(),
|
||||||
T_Code.Imu: CameraIMU(modulos_imu=[T_Code.Snr, T_Code.Cam], max_disagreement_deg=4.0, max_skew_imu_s=0.12),
|
T_Code.Imu: CameraIMU(modulos_imu=[T_Code.Snr]), # CameraIMU(modulos_imu=[T_Code.Snr, T_Code.Cam], max_disagreement_deg=4.0, max_skew_imu_s=0.12),
|
||||||
T_Code.Npc: ModuloPC(),
|
T_Code.Npc: ModuloPC(),
|
||||||
T_Code.Lvx: ModuloLivox(),
|
T_Code.Lvx: ModuloLivox(),
|
||||||
T_Code.Ipb: ModuloIPBribge(),
|
T_Code.Ipb: ModuloIPBribge(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import time
|
import time
|
||||||
|
import math
|
||||||
|
from collections import deque
|
||||||
from shared.enums import T_Code, StatusModulo
|
from shared.enums import T_Code, StatusModulo
|
||||||
from health_worker.modulos.base import ModuloDiagnosticoBase
|
from health_worker.modulos.base import ModuloDiagnosticoBase
|
||||||
from health_worker.aprendizado.padrao_dinamico import PadraoDinamico
|
from health_worker.aprendizado.padrao_dinamico import PadraoDinamico
|
||||||
|
|
@ -75,7 +77,6 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
TEMP_AMOSTRAS_MIN_FALHA = 3
|
TEMP_AMOSTRAS_MIN_FALHA = 3
|
||||||
TEMP_AMOSTRAS_MIN_EMERGENCIA = 3
|
TEMP_AMOSTRAS_MIN_EMERGENCIA = 3
|
||||||
TEMP_AMOSTRA_MAX_IDADE_S = 3.0
|
TEMP_AMOSTRA_MAX_IDADE_S = 3.0
|
||||||
TEMP_SALTO_SUSPEITO_C = 35.0
|
|
||||||
|
|
||||||
# Elétricos
|
# Elétricos
|
||||||
TENSAO_FALHA_PERSISTENCIA_S = 2.0
|
TENSAO_FALHA_PERSISTENCIA_S = 2.0
|
||||||
|
|
@ -94,6 +95,21 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
CORRENTE_MOTOR_EXTREMA_A = 90.0
|
CORRENTE_MOTOR_EXTREMA_A = 90.0
|
||||||
CORRENTE_BARRAMENTO_EXTREMA_A = 55.0
|
CORRENTE_BARRAMENTO_EXTREMA_A = 55.0
|
||||||
|
|
||||||
|
TEMP_MIN_FISICA_C = 0.0
|
||||||
|
TEMP_MAX_FISICA_C = 150.0
|
||||||
|
|
||||||
|
# Uma elevação maior que isso entre amostras próximas entra em quarentena.
|
||||||
|
TEMP_SALTO_SUSPEITO_C = 12.0
|
||||||
|
|
||||||
|
# Velocidade térmica aceitável. O piso de 3 °C tolera ruído e quantização.
|
||||||
|
TEMP_VARIACAO_BASE_C = 3.0
|
||||||
|
TEMP_SUBIDA_MAX_C_S = 2.0
|
||||||
|
TEMP_DESCIDA_MAX_C_S = 4.0
|
||||||
|
|
||||||
|
# Para aceitar um novo patamar após salto.
|
||||||
|
TEMP_CONFIRMAR_PATAMAR_AMOSTRAS = 3
|
||||||
|
TEMP_CONFIRMAR_PATAMAR_TOL_C = 5.0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.t_code = T_Code.Mov
|
self.t_code = T_Code.Mov
|
||||||
self.nome = "Movimentacao"
|
self.nome = "Movimentacao"
|
||||||
|
|
@ -140,6 +156,7 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
self._estados_persistencia = {}
|
self._estados_persistencia = {}
|
||||||
self._historico_amostras = {}
|
self._historico_amostras = {}
|
||||||
|
self._estados_temperatura = {}
|
||||||
|
|
||||||
def atualizar_saude(self):
|
def atualizar_saude(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -979,8 +996,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
def _temperatura_valida(self, temperatura):
|
def _temperatura_valida(self, temperatura):
|
||||||
try:
|
try:
|
||||||
valor = float(temperatura)
|
valor = float(temperatura)
|
||||||
return 0.0 < valor < 200.0
|
|
||||||
except (TypeError, ValueError):
|
return (
|
||||||
|
math.isfinite(valor)
|
||||||
|
and self.TEMP_MIN_FISICA_C < valor < self.TEMP_MAX_FISICA_C
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError, OverflowError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _avaliar_temperatura(
|
def _avaliar_temperatura(
|
||||||
|
|
@ -1005,11 +1026,11 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
label = f"Temperatura do {nome_origem}"
|
label = f"Temperatura do {nome_origem}"
|
||||||
|
|
||||||
amostra = self._registrar_amostra(endereco, campo, temperatura, modulo)
|
amostra = self._registrar_amostra(endereco, campo, temperatura, modulo)
|
||||||
|
normalizada = self._normalizar_temperatura(endereco=endereco, campo=campo, temperatura=temperatura, amostra=amostra)
|
||||||
|
|
||||||
if not self._temperatura_valida(temperatura):
|
if normalizada["status"] == "invalida":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura do {nome_origem} inválida: "
|
f"Temperatura do {nome_origem} inválida: {temperatura}"
|
||||||
f"{temperatura} °C"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
|
|
@ -1017,49 +1038,64 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
"valor": temperatura,
|
"valor": temperatura,
|
||||||
"severidade": 50,
|
"severidade": 50,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Leitura de temperatura inválida"
|
"Amostra térmica inválida; ignorada pela proteção "
|
||||||
|
"de sobretemperatura"
|
||||||
),
|
),
|
||||||
"acoes": [
|
"acoes": [
|
||||||
"Verificar sensor de temperatura.",
|
"Verificar sensor, conversão, aterramento e cabeamento.",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
return (
|
|
||||||
penalidade,
|
self._resetar_candidatos_temperatura(endereco, regra_desc)
|
||||||
motivos,
|
|
||||||
condicoes,
|
return self._resultado_temperatura_inconclusiva(endereco, regra_desc, motivos, condicoes)
|
||||||
risco_eletrico_termico,
|
|
||||||
|
if normalizada["status"] == "desatualizada":
|
||||||
|
motivos.append(
|
||||||
|
f"Temperatura do {nome_origem} desatualizada; "
|
||||||
|
"sobretemperatura não confirmada"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not amostra["fresca"]:
|
return self._resultado_temperatura_inconclusiva(endereco, regra_desc, motivos, condicoes)
|
||||||
|
|
||||||
|
if normalizada["status"] == "suspeita":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura do {nome_origem} sem amostra fresca "
|
f"Amostra térmica suspeita ignorada no {nome_origem}: "
|
||||||
f"({amostra['idade_s']:.1f} s)"
|
f"{float(temperatura):.1f} °C; "
|
||||||
|
f"referência validada={normalizada['valor_validado']:.1f} °C; "
|
||||||
|
f"delta={normalizada['delta_c']:+.1f} °C; "
|
||||||
|
f"confirmações="
|
||||||
|
f"{normalizada['amostras_candidatas']}/"
|
||||||
|
f"{self.TEMP_CONFIRMAR_PATAMAR_AMOSTRAS}"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura,
|
||||||
"severidade": 55,
|
"severidade": 35,
|
||||||
"descricao": "Leitura térmica está congelada ou desatualizada; não confirma sobretemperatura",
|
"descricao": (
|
||||||
"acoes": ["Verificar publicação e timestamp do sensor térmico."],
|
"Salto térmico fisicamente implausível em quarentena; "
|
||||||
|
"não classificado como temperatura elevada"
|
||||||
|
),
|
||||||
|
"acoes": [
|
||||||
|
"Aguardar as próximas amostras.",
|
||||||
|
"Verificar sensor e aterramento se o ruído persistir.",
|
||||||
|
],
|
||||||
})
|
})
|
||||||
return (0, motivos, condicoes, False)
|
|
||||||
|
|
||||||
if amostra["salto_suspeito"]:
|
self._resetar_candidatos_temperatura(endereco, regra_desc)
|
||||||
motivos.append(
|
|
||||||
f"Salto térmico suspeito no {nome_origem}: {amostra['delta_c']:.1f} °C"
|
return self._resultado_temperatura_inconclusiva(endereco, regra_desc, motivos, condicoes)
|
||||||
)
|
|
||||||
condicoes.append({
|
if normalizada["status"] == "repetida":
|
||||||
"label": label,
|
return self._resultado_temperatura_inconclusiva(endereco, regra_desc, motivos, condicoes)
|
||||||
"valor": temperatura,
|
|
||||||
"severidade": 70,
|
temperatura_avaliada = normalizada["valor_validado"]
|
||||||
"descricao": "Variação térmica instantânea acima do fisicamente plausível; aguardando confirmação",
|
|
||||||
"acoes": ["Verificar sensor, divisor, aterramento e próximas amostras."],
|
|
||||||
})
|
|
||||||
|
|
||||||
estado_temp_emergencia = self._estado_limite_alto(
|
estado_temp_emergencia = self._estado_limite_alto(
|
||||||
driver=endereco,
|
driver=endereco,
|
||||||
regra=f"temperatura_emergencia_{regra_desc}",
|
regra=f"temperatura_emergencia_{regra_desc}",
|
||||||
valor=temperatura,
|
valor=temperatura_avaliada,
|
||||||
limite_entrada=self.TEMP_EMERGENCIA_C,
|
limite_entrada=self.TEMP_EMERGENCIA_C,
|
||||||
limite_saida=self.TEMP_RECUPERA_EMERGENCIA_C,
|
limite_saida=self.TEMP_RECUPERA_EMERGENCIA_C,
|
||||||
tempo_intermediario_s=0.0,
|
tempo_intermediario_s=0.0,
|
||||||
|
|
@ -1072,7 +1108,7 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
estado_temp_falha = self._estado_limite_alto(
|
estado_temp_falha = self._estado_limite_alto(
|
||||||
driver=endereco,
|
driver=endereco,
|
||||||
regra=f"temperatura_falha_{regra_desc}",
|
regra=f"temperatura_falha_{regra_desc}",
|
||||||
valor=temperatura,
|
valor=temperatura_avaliada,
|
||||||
limite_entrada=self.TEMP_FALHA_C,
|
limite_entrada=self.TEMP_FALHA_C,
|
||||||
limite_saida=self.TEMP_RECUPERA_FALHA_C,
|
limite_saida=self.TEMP_RECUPERA_FALHA_C,
|
||||||
tempo_intermediario_s=self.TEMP_FALHA_INTERMEDIARIO_S,
|
tempo_intermediario_s=self.TEMP_FALHA_INTERMEDIARIO_S,
|
||||||
|
|
@ -1085,7 +1121,7 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
estado_temp_alerta = self._estado_limite_alto(
|
estado_temp_alerta = self._estado_limite_alto(
|
||||||
driver=endereco,
|
driver=endereco,
|
||||||
regra=f"temperatura_alerta_{regra_desc}",
|
regra=f"temperatura_alerta_{regra_desc}",
|
||||||
valor=temperatura,
|
valor=temperatura_avaliada,
|
||||||
limite_entrada=self.TEMP_ALERTA_C,
|
limite_entrada=self.TEMP_ALERTA_C,
|
||||||
limite_saida=self.TEMP_RECUPERA_ALERTA_C,
|
limite_saida=self.TEMP_RECUPERA_ALERTA_C,
|
||||||
tempo_intermediario_s=self.TEMP_ALERTA_INTERMEDIARIO_S,
|
tempo_intermediario_s=self.TEMP_ALERTA_INTERMEDIARIO_S,
|
||||||
|
|
@ -1102,12 +1138,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
if estado_temp_emergencia["nivel"] == "confirmado":
|
if estado_temp_emergencia["nivel"] == "confirmado":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura do {nome_origem} em nível de emergência "
|
f"Temperatura do {nome_origem} em nível de emergência "
|
||||||
f"confirmado: {temperatura:.1f} °C"
|
f"confirmado: {temperatura_avaliada:.1f} °C"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 98,
|
"severidade": 98,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura de emergência confirmada após "
|
"Temperatura de emergência confirmada após "
|
||||||
|
|
@ -1129,12 +1165,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_emergencia["nivel"] == "recuperando":
|
elif estado_temp_emergencia["nivel"] == "recuperando":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura do {nome_origem} normalizando após emergência: "
|
f"Temperatura do {nome_origem} normalizando após emergência: "
|
||||||
f"{temperatura:.1f} °C. Aguardando estabilidade para liberação."
|
f"{temperatura_avaliada:.1f} °C. Aguardando estabilidade para liberação."
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 98,
|
"severidade": 98,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura caiu abaixo do limite de recuperação da "
|
"Temperatura caiu abaixo do limite de recuperação da "
|
||||||
|
|
@ -1160,13 +1196,13 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_emergencia["ativa"]:
|
elif estado_temp_emergencia["ativa"]:
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura de emergência do {nome_origem} detectada: "
|
f"Temperatura de emergência do {nome_origem} detectada: "
|
||||||
f"{temperatura:.1f} °C "
|
f"{temperatura_avaliada:.1f} °C "
|
||||||
f"({estado_temp_emergencia['duracao_s']:.1f} s)"
|
f"({estado_temp_emergencia['duracao_s']:.1f} s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 89,
|
"severidade": 89,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura em nível de emergência, aguardando "
|
"Temperatura em nível de emergência, aguardando "
|
||||||
|
|
@ -1188,12 +1224,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_falha["nivel"] == "confirmado":
|
elif estado_temp_falha["nivel"] == "confirmado":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura perigosa do {nome_origem} confirmada: "
|
f"Temperatura perigosa do {nome_origem} confirmada: "
|
||||||
f"{temperatura:.1f} °C"
|
f"{temperatura_avaliada:.1f} °C"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 95,
|
"severidade": 95,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura perigosa persistiu por "
|
"Temperatura perigosa persistiu por "
|
||||||
|
|
@ -1216,13 +1252,13 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_falha["nivel"] == "recuperando":
|
elif estado_temp_falha["nivel"] == "recuperando":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura do {nome_origem} normalizando após condição "
|
f"Temperatura do {nome_origem} normalizando após condição "
|
||||||
f"perigosa: {temperatura:.1f} °C. "
|
f"perigosa: {temperatura_avaliada:.1f} °C. "
|
||||||
"Aguardando estabilidade para liberação."
|
"Aguardando estabilidade para liberação."
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 95,
|
"severidade": 95,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura caiu abaixo do limite de recuperação da "
|
"Temperatura caiu abaixo do limite de recuperação da "
|
||||||
|
|
@ -1248,13 +1284,13 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_falha["nivel"] == "persistente":
|
elif estado_temp_falha["nivel"] == "persistente":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura perigosa do {nome_origem} persistindo: "
|
f"Temperatura perigosa do {nome_origem} persistindo: "
|
||||||
f"{temperatura:.1f} °C "
|
f"{temperatura_avaliada:.1f} °C "
|
||||||
f"({estado_temp_falha['duracao_s']:.1f} s)"
|
f"({estado_temp_falha['duracao_s']:.1f} s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 85,
|
"severidade": 85,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura perigosa persistindo, aguardando "
|
"Temperatura perigosa persistindo, aguardando "
|
||||||
|
|
@ -1275,12 +1311,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_falha["nivel"] == "detectado":
|
elif estado_temp_falha["nivel"] == "detectado":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Pico de temperatura perigosa do {nome_origem} detectado: "
|
f"Pico de temperatura perigosa do {nome_origem} detectado: "
|
||||||
f"{temperatura:.1f} °C"
|
f"{temperatura_avaliada:.1f} °C"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 70,
|
"severidade": 70,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura perigosa detectada, ainda sem persistência"
|
"Temperatura perigosa detectada, ainda sem persistência"
|
||||||
|
|
@ -1299,12 +1335,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_alerta["nivel"] == "confirmado":
|
elif estado_temp_alerta["nivel"] == "confirmado":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura elevada do {nome_origem} confirmada: "
|
f"Temperatura elevada do {nome_origem} confirmada: "
|
||||||
f"{temperatura:.1f} °C"
|
f"{temperatura_avaliada:.1f} °C"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 60,
|
"severidade": 60,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura elevada persistiu por "
|
"Temperatura elevada persistiu por "
|
||||||
|
|
@ -1325,12 +1361,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_alerta["nivel"] == "recuperando":
|
elif estado_temp_alerta["nivel"] == "recuperando":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Temperatura do {nome_origem} normalizando após alerta: "
|
f"Temperatura do {nome_origem} normalizando após alerta: "
|
||||||
f"{temperatura:.1f} °C"
|
f"{temperatura_avaliada:.1f} °C"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 60,
|
"severidade": 60,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura caiu abaixo do limite de recuperação do "
|
"Temperatura caiu abaixo do limite de recuperação do "
|
||||||
|
|
@ -1355,13 +1391,13 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_alerta["nivel"] == "persistente":
|
elif estado_temp_alerta["nivel"] == "persistente":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Aquecimento do {nome_origem} persistindo: "
|
f"Aquecimento do {nome_origem} persistindo: "
|
||||||
f"{temperatura:.1f} °C "
|
f"{temperatura_avaliada:.1f} °C "
|
||||||
f"({estado_temp_alerta['duracao_s']:.1f} s)"
|
f"({estado_temp_alerta['duracao_s']:.1f} s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 50,
|
"severidade": 50,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura elevada persistindo, aguardando confirmação"
|
"Temperatura elevada persistindo, aguardando confirmação"
|
||||||
|
|
@ -1381,12 +1417,12 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
elif estado_temp_alerta["nivel"] == "detectado":
|
elif estado_temp_alerta["nivel"] == "detectado":
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"Aquecimento do {nome_origem} detectado: "
|
f"Aquecimento do {nome_origem} detectado: "
|
||||||
f"{temperatura:.1f} °C"
|
f"{temperatura_avaliada:.1f} °C"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": label,
|
"label": label,
|
||||||
"valor": temperatura,
|
"valor": temperatura_avaliada,
|
||||||
"severidade": 40,
|
"severidade": 40,
|
||||||
"descricao": (
|
"descricao": (
|
||||||
"Temperatura acima da faixa ideal, aguardando persistência"
|
"Temperatura acima da faixa ideal, aguardando persistência"
|
||||||
|
|
@ -1909,6 +1945,183 @@ class ModuloMovimentacao(ModuloDiagnosticoBase):
|
||||||
if str(k).strip()
|
if str(k).strip()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _normalizar_temperatura(
|
||||||
|
self,
|
||||||
|
endereco,
|
||||||
|
campo,
|
||||||
|
temperatura,
|
||||||
|
amostra,
|
||||||
|
):
|
||||||
|
chave = (str(endereco), str(campo))
|
||||||
|
|
||||||
|
estado = self._estados_temperatura.setdefault(chave, {
|
||||||
|
"valor_validado": None,
|
||||||
|
"mono_validado": None,
|
||||||
|
"candidatas": deque(
|
||||||
|
maxlen=self.TEMP_CONFIRMAR_PATAMAR_AMOSTRAS
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
if not self._temperatura_valida(temperatura):
|
||||||
|
estado["candidatas"].clear()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "invalida",
|
||||||
|
"valor_validado": estado["valor_validado"],
|
||||||
|
"motivo": "valor fora da faixa física ou não finito",
|
||||||
|
}
|
||||||
|
|
||||||
|
valor = float(temperatura)
|
||||||
|
|
||||||
|
if not amostra["fresca"]:
|
||||||
|
return {
|
||||||
|
"status": "desatualizada",
|
||||||
|
"valor_validado": estado["valor_validado"],
|
||||||
|
"motivo": "amostra térmica sem frescor",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Polling repetido do mesmo snapshot não confirma tendência.
|
||||||
|
if not amostra["nova"]:
|
||||||
|
return {
|
||||||
|
"status": "repetida",
|
||||||
|
"valor_validado": estado["valor_validado"],
|
||||||
|
"motivo": "snapshot já avaliado",
|
||||||
|
}
|
||||||
|
|
||||||
|
agora = time.monotonic()
|
||||||
|
anterior = estado["valor_validado"]
|
||||||
|
mono_anterior = estado["mono_validado"]
|
||||||
|
|
||||||
|
# Primeira amostra fisicamente válida vira referência.
|
||||||
|
if anterior is None or mono_anterior is None:
|
||||||
|
estado["valor_validado"] = valor
|
||||||
|
estado["mono_validado"] = agora
|
||||||
|
estado["candidatas"].clear()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "validada",
|
||||||
|
"valor_validado": valor,
|
||||||
|
"motivo": "primeira referência térmica válida",
|
||||||
|
}
|
||||||
|
|
||||||
|
dt = max(0.001, agora - mono_anterior)
|
||||||
|
delta = valor - anterior
|
||||||
|
|
||||||
|
limite_delta = (
|
||||||
|
self.TEMP_VARIACAO_BASE_C
|
||||||
|
+ (
|
||||||
|
self.TEMP_SUBIDA_MAX_C_S
|
||||||
|
if delta >= 0
|
||||||
|
else self.TEMP_DESCIDA_MAX_C_S
|
||||||
|
) * dt
|
||||||
|
)
|
||||||
|
|
||||||
|
if abs(delta) <= limite_delta:
|
||||||
|
estado["valor_validado"] = valor
|
||||||
|
estado["mono_validado"] = agora
|
||||||
|
estado["candidatas"].clear()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "validada",
|
||||||
|
"valor_validado": valor,
|
||||||
|
"delta_c": delta,
|
||||||
|
"dt_s": dt,
|
||||||
|
"motivo": "variação térmica plausível",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Salto implausível: guarda o possível novo patamar,
|
||||||
|
# mas ainda não o entrega à proteção térmica.
|
||||||
|
estado["candidatas"].append({
|
||||||
|
"valor": valor,
|
||||||
|
"mono": agora,
|
||||||
|
})
|
||||||
|
|
||||||
|
candidatas = list(estado["candidatas"])
|
||||||
|
|
||||||
|
patamar_coerente = (
|
||||||
|
len(candidatas) >= self.TEMP_CONFIRMAR_PATAMAR_AMOSTRAS
|
||||||
|
and (
|
||||||
|
max(x["valor"] for x in candidatas)
|
||||||
|
- min(x["valor"] for x in candidatas)
|
||||||
|
) <= self.TEMP_CONFIRMAR_PATAMAR_TOL_C
|
||||||
|
)
|
||||||
|
|
||||||
|
if patamar_coerente:
|
||||||
|
valor_confirmado = sum(
|
||||||
|
x["valor"] for x in candidatas
|
||||||
|
) / len(candidatas)
|
||||||
|
|
||||||
|
estado["valor_validado"] = valor_confirmado
|
||||||
|
estado["mono_validado"] = agora
|
||||||
|
estado["candidatas"].clear()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "patamar_confirmado",
|
||||||
|
"valor_validado": valor_confirmado,
|
||||||
|
"delta_c": valor_confirmado - anterior,
|
||||||
|
"dt_s": dt,
|
||||||
|
"motivo": (
|
||||||
|
"novo patamar confirmado por "
|
||||||
|
f"{self.TEMP_CONFIRMAR_PATAMAR_AMOSTRAS} amostras"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "suspeita",
|
||||||
|
"valor_validado": anterior,
|
||||||
|
"delta_c": delta,
|
||||||
|
"dt_s": dt,
|
||||||
|
"amostras_candidatas": len(candidatas),
|
||||||
|
"motivo": "salto térmico em quarentena",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _resetar_candidatos_temperatura(self, endereco, regra_desc):
|
||||||
|
for nivel in ("alerta", "falha", "emergencia"):
|
||||||
|
chave = (
|
||||||
|
str(endereco),
|
||||||
|
f"temperatura_{nivel}_{regra_desc}",
|
||||||
|
)
|
||||||
|
|
||||||
|
estado = self._estados_persistencia.get(chave)
|
||||||
|
|
||||||
|
if estado and not estado.get("confirmada", False):
|
||||||
|
estado["ativa_desde"] = None
|
||||||
|
estado["normal_desde"] = None
|
||||||
|
estado["amostras_ativas"] = 0
|
||||||
|
|
||||||
|
def _resultado_temperatura_inconclusiva(
|
||||||
|
self,
|
||||||
|
endereco,
|
||||||
|
regra_desc,
|
||||||
|
motivos,
|
||||||
|
condicoes,
|
||||||
|
):
|
||||||
|
def obter_estado(nivel):
|
||||||
|
chave = (
|
||||||
|
str(endereco),
|
||||||
|
f"temperatura_{nivel}_{regra_desc}",
|
||||||
|
)
|
||||||
|
return self._estados_persistencia.get(chave, {})
|
||||||
|
|
||||||
|
emergencia = obter_estado("emergencia")
|
||||||
|
falha = obter_estado("falha")
|
||||||
|
alerta = obter_estado("alerta")
|
||||||
|
|
||||||
|
nivel_emergencia = emergencia.get("nivel")
|
||||||
|
nivel_falha = falha.get("nivel")
|
||||||
|
nivel_alerta = alerta.get("nivel")
|
||||||
|
|
||||||
|
if nivel_emergencia in ("confirmado", "recuperando"):
|
||||||
|
return (90, motivos, condicoes, True)
|
||||||
|
|
||||||
|
if nivel_falha in ("confirmado", "recuperando"):
|
||||||
|
return (80, motivos, condicoes, True)
|
||||||
|
|
||||||
|
if nivel_alerta in ("confirmado", "recuperando"):
|
||||||
|
return (25, motivos, condicoes, False)
|
||||||
|
|
||||||
|
return (0, motivos, condicoes, False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _bool(valor, default=False):
|
def _bool(valor, default=False):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,15 @@ class RegrasTaticas:
|
||||||
freio_necessario = freio_necessario or freio
|
freio_necessario = freio_necessario or freio
|
||||||
|
|
||||||
novos_motivos, freio = self._avaliar_oak_parada_por_bloqueio(
|
novos_motivos, freio = self._avaliar_oak_parada_por_bloqueio(
|
||||||
controle=controle
|
controle=controle,
|
||||||
|
operacao=operacao
|
||||||
)
|
)
|
||||||
motivos.extend(novos_motivos)
|
motivos.extend(novos_motivos)
|
||||||
freio_necessario = freio_necessario or freio
|
freio_necessario = freio_necessario or freio
|
||||||
|
|
||||||
novos_motivos, freio = self._avaliar_imu_parada_por_inclinacao(
|
novos_motivos, freio = self._avaliar_imu_parada_por_inclinacao(
|
||||||
controle=controle
|
controle=controle,
|
||||||
|
operacao=operacao
|
||||||
)
|
)
|
||||||
motivos.extend(novos_motivos)
|
motivos.extend(novos_motivos)
|
||||||
freio_necessario = freio_necessario or freio
|
freio_necessario = freio_necessario or freio
|
||||||
|
|
@ -124,7 +126,7 @@ class RegrasTaticas:
|
||||||
|
|
||||||
return motivos, freio_necessario
|
return motivos, freio_necessario
|
||||||
|
|
||||||
def _avaliar_oak_parada_por_bloqueio(self, controle: dict):
|
def _avaliar_oak_parada_por_bloqueio(self, controle: dict, operacao: dict):
|
||||||
"""
|
"""
|
||||||
Bloqueia somente se a função de parada por bloqueio estiver ativa.
|
Bloqueia somente se a função de parada por bloqueio estiver ativa.
|
||||||
|
|
||||||
|
|
@ -132,6 +134,12 @@ class RegrasTaticas:
|
||||||
Esses são auxílios, não garantias absolutas de segurança.
|
Esses são auxílios, não garantias absolutas de segurança.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
sonar_mandatorio = self._modulo_mandatorio(operacao, T_Code.Snr)
|
||||||
|
if not sonar_mandatorio:
|
||||||
|
self._resetar_persistencia("oak_indisponivel")
|
||||||
|
self._resetar_persistencia("oak_obstaculo")
|
||||||
|
return [], False
|
||||||
|
|
||||||
oak_parada_por_bloqueio = self._bool(
|
oak_parada_por_bloqueio = self._bool(
|
||||||
controle.get("oak_parada_por_bloqueio", False)
|
controle.get("oak_parada_por_bloqueio", False)
|
||||||
)
|
)
|
||||||
|
|
@ -234,12 +242,10 @@ class RegrasTaticas:
|
||||||
|
|
||||||
return [], False
|
return [], False
|
||||||
|
|
||||||
def _avaliar_imu_parada_por_inclinacao(self, controle: dict):
|
def _avaliar_imu_parada_por_inclinacao(self, controle: dict, operacao: dict):
|
||||||
imu_parada_por_inclinacao = self._bool(
|
protecao_ativa = self._bool(controle.get("imu_parada_por_inclinacao", False))
|
||||||
controle.get("imu_parada_por_inclinacao", False)
|
imu_mandatorio = self._modulo_mandatorio(operacao, T_Code.Imu)
|
||||||
)
|
if not protecao_ativa or not imu_mandatorio:
|
||||||
|
|
||||||
if not imu_parada_por_inclinacao:
|
|
||||||
self._resetar_persistencia("imu_indisponivel")
|
self._resetar_persistencia("imu_indisponivel")
|
||||||
self._resetar_persistencia("imu_perigo")
|
self._resetar_persistencia("imu_perigo")
|
||||||
return [], False
|
return [], False
|
||||||
|
|
@ -537,6 +543,30 @@ class RegrasTaticas:
|
||||||
|
|
||||||
return idade_s <= max_idade_s, idade_ms
|
return idade_s <= max_idade_s, idade_ms
|
||||||
|
|
||||||
|
def _modulo_mandatorio(self, operacao: dict, t_code: T_Code) -> bool:
|
||||||
|
# Se o campo ainda não chegou ao Python, preserva a proteção atual.
|
||||||
|
if "modulos_mandatorios" not in operacao:
|
||||||
|
return True
|
||||||
|
|
||||||
|
valores = operacao.get("modulos_mandatorios", []) or []
|
||||||
|
|
||||||
|
normalizados = set()
|
||||||
|
|
||||||
|
for item in valores:
|
||||||
|
try:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
item = item.get(
|
||||||
|
"modulo",
|
||||||
|
item.get("codigo", item.get("t_code"))
|
||||||
|
)
|
||||||
|
|
||||||
|
item = getattr(item, "value", item)
|
||||||
|
normalizados.add(int(item))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return int(t_code.value) in normalizados
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Helpers genéricos seguros
|
# Helpers genéricos seguros
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ WEED_DEFAULT_CONFIG = {
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Gate global: só libera bicos quando existe alvo suficiente no frame.
|
# Gate global: só libera bicos quando existe alvo suficiente no frame.
|
||||||
"usar_radar_global_gate": True,
|
"usar_radar_global_gate": True,
|
||||||
"inverter_ordem_bicos": True,
|
"inverter_ordem_bicos": False,
|
||||||
"inverter_sentido_vertical_bicos": True,
|
"inverter_sentido_vertical_bicos": True,
|
||||||
|
|
||||||
# Histerese global da fração de alvo no frame completo.
|
# Histerese global da fração de alvo no frame completo.
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,8 @@ class WeedDetector:
|
||||||
|
|
||||||
cfg = self.config
|
cfg = self.config
|
||||||
vel_mps = float(cfg.get("velocidade_robo", 0.0) or 0.0)
|
vel_mps = float(cfg.get("velocidade_robo", 0.0) or 0.0)
|
||||||
|
# A geometria de atuação foi definida para o avanço. A ré não é
|
||||||
|
# reinterpretada como um percurso espacial inverso dos bicos.
|
||||||
if vel_mps < 0:
|
if vel_mps < 0:
|
||||||
vel_mps = 0.0
|
vel_mps = 0.0
|
||||||
|
|
||||||
|
|
@ -463,9 +465,14 @@ class WeedDetector:
|
||||||
self._frac_grid_last = frac_grid
|
self._frac_grid_last = frac_grid
|
||||||
|
|
||||||
# 3) Atualiza score espacial.
|
# 3) Atualiza score espacial.
|
||||||
|
# Em movimento, acumula evidência e desloca a esteira normalmente.
|
||||||
|
# Parado, acompanha a evidência do frame atual por EMA: o mesmo ruído
|
||||||
|
# não pode ser somado indefinidamente, mas uma erva persistente continua
|
||||||
|
# podendo manter/ligar o bico.
|
||||||
score_update_info = self._atualizar_score_memoria(
|
score_update_info = self._atualizar_score_memoria(
|
||||||
frac_grid=frac_grid,
|
frac_grid=frac_grid,
|
||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
|
em_movimento=bool(shift_info["em_movimento"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4) Decide bicos pela zona física de atuação, com compensação de latência.
|
# 4) Decide bicos pela zona física de atuação, com compensação de latência.
|
||||||
|
|
@ -533,6 +540,7 @@ class WeedDetector:
|
||||||
return {
|
return {
|
||||||
"dt_s": 0.0,
|
"dt_s": 0.0,
|
||||||
"vel_mps": float(vel_mps),
|
"vel_mps": float(vel_mps),
|
||||||
|
"em_movimento": False,
|
||||||
"delta_frac": 0.0,
|
"delta_frac": 0.0,
|
||||||
"shift_cells": 0.0,
|
"shift_cells": 0.0,
|
||||||
"distancia_deslocada_total_cm": float(self._distancia_deslocada_total_cm),
|
"distancia_deslocada_total_cm": float(self._distancia_deslocada_total_cm),
|
||||||
|
|
@ -545,10 +553,17 @@ class WeedDetector:
|
||||||
if max_dt > 0:
|
if max_dt > 0:
|
||||||
dt = min(dt, max_dt)
|
dt = min(dt, max_dt)
|
||||||
|
|
||||||
if dt <= 0 or vel_mps <= 0:
|
vel_min = max(
|
||||||
|
0.0,
|
||||||
|
float(cfg.get("vel_min_movimento_memoria_mps", 0.03) or 0.03),
|
||||||
|
)
|
||||||
|
em_movimento = bool(abs(vel_mps) >= vel_min)
|
||||||
|
|
||||||
|
if dt <= 0 or not em_movimento:
|
||||||
return {
|
return {
|
||||||
"dt_s": float(max(0.0, dt)),
|
"dt_s": float(max(0.0, dt)),
|
||||||
"vel_mps": float(vel_mps),
|
"vel_mps": float(vel_mps),
|
||||||
|
"em_movimento": False,
|
||||||
"delta_frac": 0.0,
|
"delta_frac": 0.0,
|
||||||
"shift_cells": 0.0,
|
"shift_cells": 0.0,
|
||||||
"distancia_deslocada_total_cm": float(self._distancia_deslocada_total_cm),
|
"distancia_deslocada_total_cm": float(self._distancia_deslocada_total_cm),
|
||||||
|
|
@ -582,6 +597,7 @@ class WeedDetector:
|
||||||
return {
|
return {
|
||||||
"dt_s": float(dt),
|
"dt_s": float(dt),
|
||||||
"vel_mps": float(vel_mps),
|
"vel_mps": float(vel_mps),
|
||||||
|
"em_movimento": True,
|
||||||
"delta_frac": float(delta_frac),
|
"delta_frac": float(delta_frac),
|
||||||
"shift_cells": float(shift_cells),
|
"shift_cells": float(shift_cells),
|
||||||
"k_shift_frame_por_mps": float(k_shift),
|
"k_shift_frame_por_mps": float(k_shift),
|
||||||
|
|
@ -723,6 +739,7 @@ class WeedDetector:
|
||||||
self,
|
self,
|
||||||
frac_grid: np.ndarray,
|
frac_grid: np.ndarray,
|
||||||
cfg: dict,
|
cfg: dict,
|
||||||
|
em_movimento: bool = True,
|
||||||
):
|
):
|
||||||
min_frac_pred = float(
|
min_frac_pred = float(
|
||||||
cfg.get(
|
cfg.get(
|
||||||
|
|
@ -748,7 +765,9 @@ class WeedDetector:
|
||||||
evidencia = np.clip(frac_grid / percent_ref, 0.0, 1.0).astype(np.float32)
|
evidencia = np.clip(frac_grid / percent_ref, 0.0, 1.0).astype(np.float32)
|
||||||
cond_pos = frac_grid >= min_frac_pred
|
cond_pos = frac_grid >= min_frac_pred
|
||||||
|
|
||||||
# Decaimento geral, depois reforço positivo.
|
if em_movimento:
|
||||||
|
# Em movimento, cada frame traz evidência de uma nova faixa física
|
||||||
|
# do terreno; mantém a estratégia acumulativa original.
|
||||||
score = np.maximum(0.0, self._score_grid - down_decay)
|
score = np.maximum(0.0, self._score_grid - down_decay)
|
||||||
|
|
||||||
score = np.where(
|
score = np.where(
|
||||||
|
|
@ -756,6 +775,25 @@ class WeedDetector:
|
||||||
np.minimum(1.0, score + up_gain * evidencia),
|
np.minimum(1.0, score + up_gain * evidencia),
|
||||||
score,
|
score,
|
||||||
).astype(np.float32)
|
).astype(np.float32)
|
||||||
|
modo_atualizacao = "acumulativo_movimento"
|
||||||
|
else:
|
||||||
|
# Parado, vários frames observam essencialmente o mesmo terreno.
|
||||||
|
# Uma EMA faz o score convergir para a evidência atual, em vez de
|
||||||
|
# transformar repetição temporal em confiança espacial infinita.
|
||||||
|
alpha_parado = float(cfg.get("score_alpha_parado", 0.70) or 0.70)
|
||||||
|
alpha_parado = float(np.clip(alpha_parado, 0.0, 0.99))
|
||||||
|
|
||||||
|
evidencia_atual = np.where(
|
||||||
|
cond_pos,
|
||||||
|
evidencia,
|
||||||
|
0.0,
|
||||||
|
).astype(np.float32)
|
||||||
|
|
||||||
|
score = (
|
||||||
|
alpha_parado * self._score_grid
|
||||||
|
+ (1.0 - alpha_parado) * evidencia_atual
|
||||||
|
).astype(np.float32)
|
||||||
|
modo_atualizacao = "ema_parado"
|
||||||
|
|
||||||
self._score_grid = score
|
self._score_grid = score
|
||||||
|
|
||||||
|
|
@ -764,6 +802,11 @@ class WeedDetector:
|
||||||
"percent_ref_evidencia": float(percent_ref),
|
"percent_ref_evidencia": float(percent_ref),
|
||||||
"score_up_gain": float(up_gain),
|
"score_up_gain": float(up_gain),
|
||||||
"score_down_decay": float(down_decay),
|
"score_down_decay": float(down_decay),
|
||||||
|
"em_movimento": bool(em_movimento),
|
||||||
|
"modo_atualizacao": modo_atualizacao,
|
||||||
|
"score_alpha_parado": float(
|
||||||
|
cfg.get("score_alpha_parado", 0.70) or 0.70
|
||||||
|
),
|
||||||
"frac_grid_max": float(frac_grid.max()) if frac_grid.size else 0.0,
|
"frac_grid_max": float(frac_grid.max()) if frac_grid.size else 0.0,
|
||||||
"evidencia_max": float(evidencia.max()) if evidencia.size else 0.0,
|
"evidencia_max": float(evidencia.max()) if evidencia.size else 0.0,
|
||||||
}
|
}
|
||||||
|
|
@ -788,20 +831,32 @@ class WeedDetector:
|
||||||
|
|
||||||
k_shift = max(0.0, k_shift)
|
k_shift = max(0.0, k_shift)
|
||||||
|
|
||||||
# Antecipação em fração da imagem:
|
# A zona configurada representa a posição física fixa dos bicos.
|
||||||
# Se demora para atuar, avalia células um pouco acima da zona real,
|
# Com faixa=0,85, área=0,10 e inversão vertical, 85%..95% da imagem
|
||||||
# porque elas chegarão na zona quando o bico abrir de fato.
|
# original vira 5%..15% na convenção interna. Essa zona NÃO muda de
|
||||||
antecipacao_frac = max(0.0, vel_mps * lat_s * k_shift)
|
# parado até a velocidade de referência. Somente a parcela de
|
||||||
|
# velocidade ACIMA da referência antecipa a avaliação para compensar
|
||||||
|
# o tempo de resposta do sistema.
|
||||||
|
vel_referencia = max(
|
||||||
|
0.0,
|
||||||
|
float(cfg.get("velocidade_referencia_atuacao_mps", 0.65) or 0.65),
|
||||||
|
)
|
||||||
|
velocidade_abs = abs(float(vel_mps))
|
||||||
|
velocidade_excedente = max(0.0, velocidade_abs - vel_referencia)
|
||||||
|
antecipacao_extra_frac = velocidade_excedente * lat_s * k_shift
|
||||||
|
|
||||||
if getattr(self, "inverter_sentido_vertical_bicos", False):
|
# A posição dos bicos é fixa. Não tentamos reinterpretar essa zona na
|
||||||
# Se a erva vem do pé para o topo, antecipar significa olhar
|
# marcha a ré; a antecipação só indica uma região anterior no sentido
|
||||||
# um pouco abaixo da zona real, pois ela ainda vai subir até o bico.
|
# visual configurado para a operação normal de avanço.
|
||||||
zona_eval_ini_frac = zona_ini_frac + antecipacao_frac
|
sentido_vertical = (
|
||||||
zona_eval_fim_frac = zona_fim_frac + antecipacao_frac
|
1.0
|
||||||
else:
|
if getattr(self, "inverter_sentido_vertical_bicos", False)
|
||||||
# Lógica atual: erva vem do topo para o pé.
|
else -1.0
|
||||||
zona_eval_ini_frac = zona_ini_frac - antecipacao_frac
|
)
|
||||||
zona_eval_fim_frac = zona_fim_frac - antecipacao_frac
|
offset_assinado = antecipacao_extra_frac * sentido_vertical
|
||||||
|
|
||||||
|
zona_eval_ini_frac = zona_ini_frac + offset_assinado
|
||||||
|
zona_eval_fim_frac = zona_fim_frac + offset_assinado
|
||||||
|
|
||||||
zona_eval_ini_frac = max(0.0, min(1.0, zona_eval_ini_frac))
|
zona_eval_ini_frac = max(0.0, min(1.0, zona_eval_ini_frac))
|
||||||
zona_eval_fim_frac = max(0.0, min(1.0, zona_eval_fim_frac))
|
zona_eval_fim_frac = max(0.0, min(1.0, zona_eval_fim_frac))
|
||||||
|
|
@ -872,7 +927,12 @@ class WeedDetector:
|
||||||
|
|
||||||
# Zona avaliada com compensação de latência.
|
# Zona avaliada com compensação de latência.
|
||||||
"latencia_total_atuacao_ms": float(lat_ms),
|
"latencia_total_atuacao_ms": float(lat_ms),
|
||||||
"antecipacao_frac": float(antecipacao_frac),
|
"antecipacao_frac": float(antecipacao_extra_frac),
|
||||||
|
"velocidade_referencia_atuacao_mps": float(vel_referencia),
|
||||||
|
"velocidade_excedente_atuacao_mps": float(velocidade_excedente),
|
||||||
|
"offset_base_referencia_frac": 0.0,
|
||||||
|
"antecipacao_extra_frac": float(antecipacao_extra_frac),
|
||||||
|
"offset_assinado_frac": float(offset_assinado),
|
||||||
"zona_eval_inicio_frac": float(zona_eval_ini_frac),
|
"zona_eval_inicio_frac": float(zona_eval_ini_frac),
|
||||||
"zona_eval_fim_frac": float(zona_eval_fim_frac),
|
"zona_eval_fim_frac": float(zona_eval_fim_frac),
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue