1937 lines
92 KiB
C#
1937 lines
92 KiB
C#
using AgroBase.Models;
|
|
using AgroBase.Models.Modules;
|
|
using AgroBase.Models.Operadores;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using static AgroBase.Models.Enums;
|
|
using static AgroBase.Models.Operadores.OperadoresModels;
|
|
|
|
namespace AgroBase.Services.Operadores
|
|
{
|
|
public class HealthWorkerService : IWorkerModel
|
|
{
|
|
public static HealthWorkerModel DadosLeitura = new HealthWorkerModel();
|
|
public SaudeWorkerModel Saude { get; set; } = new SaudeWorkerModel();
|
|
|
|
private bool DebugMode = false;
|
|
//private static bool AtualizandoRedis = false;
|
|
|
|
public void MostrarLog(string message, int limite = 500)
|
|
{
|
|
if (DebugMode)
|
|
{
|
|
Console.WriteLine($"[HealthWorker] {message.Substring(0, Math.Min(limite, message.Length))}{(message.Length > limite ? "..." : "")}");
|
|
}
|
|
}
|
|
|
|
public async Task<bool> IniciarProcessamento()
|
|
{
|
|
RedisService.Subscribe(CmdKey.HealthWorkerTx, RedisCallback);
|
|
|
|
DadosLeitura.IniciadoEm = DateTime.Now;
|
|
DadosLeitura.Iniciado = true;
|
|
|
|
bool pronto() => DadosLeitura.Pronto;
|
|
await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
|
|
if (pronto())
|
|
{
|
|
MostrarLog("Processamento iniciado com sucesso");
|
|
return true;
|
|
}
|
|
|
|
MostrarLog("Timeout ao iniciar script");
|
|
DadosLeitura.ReiniciarEstado();
|
|
return false;
|
|
}
|
|
|
|
public void RedisCallback(string mensagem)
|
|
{
|
|
try
|
|
{
|
|
var comando = JsonConvert.DeserializeObject<ComandoRedis>(mensagem);
|
|
|
|
var cmd = (HealthWorkerCommandType)comando.cmd;
|
|
|
|
//MostrarLog($"Comando recebido: cmd = {cmd.ToString()}");
|
|
|
|
switch (cmd)
|
|
{
|
|
case HealthWorkerCommandType.ScriptCarregado:
|
|
DadosLeitura.Pronto = true;
|
|
DadosLeitura.ProntoEm = DateTime.Now;
|
|
MostrarLog($"Comando recebido: cmd = {cmd.ToString()}");
|
|
break;
|
|
|
|
default:
|
|
MostrarLog($"Comando não reconhecido: {comando.cmd}");
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MostrarLog($"Erro ao deserializar resposta: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
DadosLeitura.UltimaMensagem = DateTime.Now;
|
|
}
|
|
}
|
|
|
|
public void AtualizarSaude()
|
|
{
|
|
try
|
|
{
|
|
Saude = OperadoresModels.AtualizarSaude(CtxKey.DadosHealthWorker);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarSaude: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
|
|
private static void AtualizarDadosContextoLocal()
|
|
{
|
|
var op = Variaveis.OperacaoEmAndamento;
|
|
if (op?.Sensoriamento == null) return;
|
|
|
|
try
|
|
{
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("debug_mode", Variaveis.DebugMode),
|
|
("iniciado", op.Sensoriamento?.Operacao?.OperacaoIniciada ?? false),
|
|
("finalizando", op.Sensoriamento?.Operacao?.Finalizando ?? false),
|
|
("calibrando", op.CalibrandoRuntime),
|
|
("simulando", op.Simulando),
|
|
("emergencia", op.Sensoriamento?.Operacao?.Emergencia ?? false),
|
|
("pausa", op.Sensoriamento?.Operacao?.Pausa ?? false)
|
|
);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarDadosContextoLocal: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public static void AtualizarDadosContexto()
|
|
{
|
|
var op = Variaveis.OperacaoEmAndamento;
|
|
|
|
try
|
|
{
|
|
//if (AtualizandoRedis)
|
|
// return;
|
|
//AtualizandoRedis = true;
|
|
|
|
AtualizarDadosContextoLocal();
|
|
AtualizarDadosModulos();
|
|
AtualizarDadosControleOperacao();
|
|
|
|
var _Sensoriamento = op?.Sensoriamento;
|
|
|
|
if (_Sensoriamento == null)
|
|
{
|
|
//AtualizandoRedis = false;
|
|
return;
|
|
}
|
|
|
|
var _t = _Sensoriamento.Trajetoria;
|
|
PontoTrajetoriaModel pontoComparar = _t != null ? (_t.PontoAtual.Aproximando ? _t.PontoAtual : _t.ProximoPonto != null ? _t.ProximoPonto : _t.PontoAtual) : null;
|
|
int idx_p_alvo = _t != null ?
|
|
(pontoComparar.DistanciaAtual > 1.0 ? pontoComparar.idxPonto :
|
|
pontoComparar.idxPonto < _t.QtdPontos - 1 ? pontoComparar.idxPonto + 1 :
|
|
pontoComparar.idxPonto) : 0;
|
|
|
|
double velocidadeMs = op.Simulando
|
|
? FuncoesMatematicas.CalculaVelocidadeMsPercentual(op.Controle.RPM_SP)
|
|
: (_Sensoriamento.Movimentacao?.VelocidadeMediaMs ?? 0.0);
|
|
|
|
double percentualVelSp = op.Controle?.PercentualVelocidadeSP ?? 0.0;
|
|
double rpmSp = op.Controle?.RPM_SP ?? 0.0;
|
|
|
|
bool comandoMovimentoAtivo = percentualVelSp > 0.5 || rpmSp > 1.0;
|
|
|
|
bool velocidadeFisicaAtiva = Math.Abs(velocidadeMs) > 0.03; // 3 cm/s, ajustável
|
|
|
|
bool carroEmMovimento = comandoMovimentoAtivo && velocidadeFisicaAtiva;
|
|
|
|
var bombaLinha = op?.DispAtu?.Dados?.BombaPressurizadora;
|
|
double pressaoAlvo = bombaLinha?._PressaoSPControle > 0 ? bombaLinha._PressaoSPControle : op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
|
double pressaoLinha = bombaLinha?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
|
double toleranciaPressao = Math.Max(2.0, pressaoAlvo * 0.15);
|
|
bool pressaoLinhaRegular = pressaoAlvo <= 0 || pressaoLinha >= Math.Max(0, pressaoAlvo - toleranciaPressao);
|
|
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosContexto,
|
|
("Trajetoria", _t != null ? new
|
|
{
|
|
status = (int)_t.StatusCarro,
|
|
concluido = _t.TrajetoriaConcluida,
|
|
manobrando = _t.ManobrandoEntreRuas,
|
|
erro_angular = _t.ErroOrientacaoAngularCombinado,
|
|
erro_angular_caminho = _t.ErroOrientacaoAngularCaminho,
|
|
erro_angular_proximo_ponto = _t.ErroOrientacaoAngularProximoPonto,
|
|
idx_proximo_ponto = _t.ProximoPonto.idxPonto,
|
|
idx_ponto_alvo = idx_p_alvo,
|
|
CorredorAtual = new
|
|
{
|
|
concluido = _t.CorredorAtual.Concluido,
|
|
dentro = _t.CorredorAtual.Dentro,
|
|
ultimo = _t.CorredorAtual.Ultimo,
|
|
largura = _t.CorredorAtual.Largura,
|
|
progresso = _t.CorredorAtual.Progresso,
|
|
dist_esq = _t.DistanciaEsquerda,
|
|
dist_dir = _t.DistanciaDireita,
|
|
angulo = _t.AnguloMedioCorredor,
|
|
pontos_restantes = _t.CorredorAtual.QtdPontos - pontoComparar.idxPontoCorredor,
|
|
liberado = _t.AutonomiaCorredor?.Liberado ?? false,
|
|
motivo_bat = _t.AutonomiaCorredor?.Motivos?.FirstOrDefault(x => x.Contains("Bateria")) ?? "",
|
|
bateria_ok = _t.AutonomiaCorredor?.BateriaSuficiente ?? false,
|
|
motivo_hrb = _t.AutonomiaCorredor?.Motivos?.FirstOrDefault(x => x.Contains("Herbicida")) ?? "",
|
|
herbicida_ok = _t.AutonomiaCorredor?.HerbicidaSuficiente ?? false,
|
|
autonomia_iniciada = _t.AutonomiaCorredor?.Iniciado ?? false,
|
|
autonomia_liberada = _t.AutonomiaCorredor?.Liberado ?? false,
|
|
autonomia_status = (int)(_t.AutonomiaCorredor?.Status ?? AutonomiaCorredorStatus.Desconhecido),
|
|
autonomia_motivo = _t.AutonomiaCorredor?.Motivo ?? "",
|
|
autonomia_ok_supervisionado = _t.AutonomiaCorredor?.Status == AutonomiaCorredorStatus.OkSupervisionado,
|
|
},
|
|
} : null
|
|
),
|
|
("Gerais", new
|
|
{
|
|
velocidade_ms = velocidadeMs,
|
|
|
|
percentual_velocidade_sp = percentualVelSp,
|
|
rpm_sp = rpmSp,
|
|
|
|
comando_movimento_ativo = comandoMovimentoAtivo,
|
|
velocidade_fisica_ativa = velocidadeFisicaAtiva,
|
|
carro_em_movimento = carroEmMovimento,
|
|
|
|
ervas_no_radar = _Sensoriamento.Atuador?.ErvasNoRadar ?? false,
|
|
percentual_ervas_no_radar = _Sensoriamento.Atuador?.PercentualErvasNoRadar ?? 0,
|
|
percentual_reservatorio = _Sensoriamento.Atuador?.PercentualReservatorio ?? 0,
|
|
percentual_bateria = _Sensoriamento.Bateria?.PercentualBateria ?? 0,
|
|
}
|
|
),
|
|
("Atuador", new
|
|
{
|
|
pressao_linha_regular = pressaoLinhaRegular,
|
|
pressao_linha = pressaoLinha,
|
|
potencia_bomba = _Sensoriamento.Atuador?.LeituraPotenciaBomba ?? 0,
|
|
estado_bomba = _Sensoriamento.Atuador?.LeituraComandoBomba ?? false,
|
|
tempo_bomba_atuada = _Sensoriamento.Atuador?.TempoBombaAtuada ?? 0,
|
|
bomba_cavitada = _Sensoriamento.Atuador?.BombaCavitada ?? false,
|
|
}
|
|
)
|
|
);
|
|
|
|
//AtualizandoRedis = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarDadosContexto: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static void AtualizarDadosEquipamento()
|
|
{
|
|
try
|
|
{
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosEquipamento,
|
|
("pronto", VariaveisEquipamento.Pronto),
|
|
("base_ip", VariaveisEquipamento.Parametros.base_ip),
|
|
("base_porta_caminho", VariaveisEquipamento.Parametros.base_porta_caminho),
|
|
("base_porta_ervas", VariaveisEquipamento.Parametros.base_porta_ervas),
|
|
("serial_number", VariaveisEquipamento.Parametros.serial_number),
|
|
("pct_vel_min", VariaveisEquipamento.PercentualVelMin),
|
|
("largura", VariaveisEquipamento.LarguraEquipamentoMm / 1000.0),
|
|
("distancia_entre_eixos", VariaveisEquipamento.DistanciaEntreEixosCm / 100.0),
|
|
("qtd_bicos", VariaveisEquipamento.QuantidadeBicosPulverizadores),
|
|
("percentual_reservatorio_min", VariaveisEquipamento.PercentualReservatorioMin),
|
|
("percentual_tensao_bateria_min", VariaveisEquipamento.PercentualTensaoBateriaMin),
|
|
("camera_caminho_id", VariaveisEquipamento.Parametros.camera_caminhho_id ?? ""),
|
|
("camera_solo_id", VariaveisEquipamento.Parametros.camera_ervas_id?.FirstOrDefault() ?? ""),
|
|
("path_ia_model_ruas_seg", VersionamentoService.ArquivoModeloSegStreetDetector.CaminhoCompleto),
|
|
("path_ia_labelmap_ruas_seg", VersionamentoService.ArquivoLabelmapSegStreetDetector.CaminhoCompleto),
|
|
("path_ia_norm_stats_ruas_seg", VersionamentoService.ArquivoModeloNormstatsStreetDetector.CaminhoCompleto),
|
|
("ia_backbone_ruas_seg", "C:\\AgroBaseModels\\Backbones\\nvidia_mit_b0"),
|
|
("ia_mode_ruas", "dual_label"),
|
|
("path_ia_model_ruas_det", VersionamentoService.ArquivoModeloDetStreetDetector.CaminhoCompleto),
|
|
("path_ia_model_ervas", VersionamentoService.ArquivoModeloWeedDetector.CaminhoCompleto),
|
|
("path_ia_labelmap_ervas", VersionamentoService.ArquivoModeloLabelmapWeedDetector.CaminhoCompleto),
|
|
("path_ia_norm_stats_ervas", VersionamentoService.ArquivoModeloNormstatsWeedDetector.CaminhoCompleto),
|
|
("path_ia_module_params_ervas", VersionamentoService.ArquivoModeloModuleParamsWeedDetector.CaminhoCompleto),
|
|
("ia_backbone_ervas", "C:\\AgroBaseModels\\Backbones\\nvidia_mit_b1"),
|
|
("angulo_roll_max", VariaveisEquipamento.AnguloInclinacaoRollMax),
|
|
("angulo_pitch_max", VariaveisEquipamento.AnguloInclinacaoPitchMax),
|
|
("imu_roll_direita_sinal", 1),
|
|
("dir_angulo_direita_sinal", 1),
|
|
("imu_dir_roll_min_aux", 6.0),
|
|
("imu_dir_bias_attention_deg", 4.0),
|
|
("imu_dir_bias_risk_deg", 9.0)
|
|
);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarDadosEquipamento: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public static void AtualizarDadosOperacao(bool forcar = false, bool sincronizarMapa = true, bool configurado = true)
|
|
{
|
|
var op = Variaveis.OperacaoEmAndamento;
|
|
|
|
try
|
|
{
|
|
//if (AtualizandoRedis && !forcar)
|
|
// return;
|
|
|
|
//while (AtualizandoRedis && forcar) {}
|
|
|
|
//AtualizandoRedis = true;
|
|
|
|
AtualizarDadosEquipamento();
|
|
|
|
if (op.DispMvd == null)
|
|
{
|
|
//AtualizandoRedis = false;
|
|
return;
|
|
}
|
|
|
|
var _Parametros = op.Parametros;
|
|
var pControle = _Parametros.Controle;
|
|
var _Controle = op.Controle;
|
|
|
|
bool exigeConfirmacaoMapaMpc =
|
|
(op.Parametros?.ControleAutomatico ?? false) &&
|
|
pControle != null &&
|
|
pControle.DirTipoMovimento == TiposControladorDirecional.MPC &&
|
|
(op.Trajetoria?._TrajetoriaFixa?.Any() ?? false);
|
|
|
|
// Sempre que esta chamada solicita sincronizacao de um mapa MPC,
|
|
// a operacao fica estruturalmente NAO configurada ate o ACK exato
|
|
// da mesma revisao voltar do Manager/Python. Isto elimina a janela
|
|
// em que uma trajetoria nova ja foi publicada, mas o MPC ainda
|
|
// executa a revisao anterior.
|
|
bool configuradoPublicado =
|
|
configurado && !(sincronizarMapa && exigeConfirmacaoMapaMpc);
|
|
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("id", op.ID ?? ""),
|
|
("configurado", configuradoPublicado),
|
|
("modo", op.Parametros?.Modo ?? ModoOperacao.NaoDefinido),
|
|
("tempo_aguardar_inicio_operacao", op.TempoIniciarOperacao),
|
|
("tempo_aguardar_retomada", op.TempoAguardarRetomada),
|
|
("modulos_mandatorios", _Parametros.ModulosMandatorios.Where(x => x.Mandatorio && x.Utilizar).Select(x => (int)x.Dispositivo).ToArray()),
|
|
("modulos_opcionais", _Parametros.ModulosMandatorios.Where(x => !x.Mandatorio && x.Utilizar).Select(x => (int)x.Dispositivo).ToArray()),
|
|
("parametros_mandatorios", _Parametros.ParametrosMandatorios.Where(x => x.Mandatorio && x.Utilizar).Select(x => (int)x.Parametro).ToArray()),
|
|
("ponto_mapa_ref", op.Trajetoria?._TrajetoriaFixa?.Select(p => new double[] { p.Posicao.Latitude, p.Posicao.Longitude }).FirstOrDefault()),
|
|
("Mov", new
|
|
{
|
|
percent_vel_max = pControle.MovVelocidadeSErvasPercent,
|
|
percent_vel_min = pControle.MovVelocidadeCErvasPercent,
|
|
percent_vel_curva = 15.0,
|
|
rampa_partida_pct = VariaveisEquipamento.PercentualVelMin,
|
|
rampa_aceleracao_pct_s = 12.0,
|
|
rampa_desaceleracao_pct_s = 18.0,
|
|
rampa_desaceleracao_seguranca_pct_s = 35.0,
|
|
}
|
|
),
|
|
("Dir", new
|
|
{
|
|
angulo_max = pControle.DirAnguloMaximo,
|
|
vel_movimento = pControle.DirVelocidadeMovimento,
|
|
tipo_controle = (int)pControle.DirTipoMovimento,
|
|
mpc = new
|
|
{
|
|
pontos = op.Trajetoria?._TrajetoriaFixa?
|
|
.Select(p => new
|
|
{
|
|
lat = p.Posicao.Latitude,
|
|
lon = p.Posicao.Longitude,
|
|
tipo = (int)p.Tipo,
|
|
distanciaMargem = Math.Max(
|
|
0.25,
|
|
p.LarguraCorredor * 0.8
|
|
),
|
|
|
|
/*
|
|
* Identidade global preservada até o MPC.
|
|
* Não renumerar após recortes/conversões.
|
|
*/
|
|
idxPonto = p.idxPonto,
|
|
idxCorredor = p.idxCorredor,
|
|
|
|
/*
|
|
* O MPC usa um limiar de heading mais
|
|
* conservador para bordas e ligações.
|
|
*/
|
|
estrutural =
|
|
p.PontoBorda ||
|
|
p.PontoLigacao ||
|
|
p.Tipo == TipoPontoRua.BordaEntrada ||
|
|
p.Tipo == TipoPontoRua.BordaSaida ||
|
|
p.Tipo == TipoPontoRua.LigacaoEntrada ||
|
|
p.Tipo == TipoPontoRua.LigacaoSaida
|
|
})
|
|
.ToList(),
|
|
angulo_max_graus = pControle.DirAnguloMaximo,
|
|
velocidade_min = Math.Round(FuncoesMatematicas.CalculaVelocidadeMsPercentual(pControle.MovVelocidadeCErvasPercent), 4),
|
|
velocidade_max = Math.Round(FuncoesMatematicas.CalculaVelocidadeMsPercentual(pControle.MovVelocidadeSErvasPercent), 4),
|
|
tempo_entre_comandos = (op.Controle.TiposControle.FirstOrDefault(x => x.Tipo == Enums.T_Code.Dir)?.DelayEnvioComando ?? 500) / 1000.0,
|
|
horizonte = pControle.MpcHorizonte,
|
|
passos_atraso = 1,
|
|
}
|
|
}
|
|
),
|
|
("Atu", new
|
|
{
|
|
qtd_bicos = _Parametros.QtdBicos,
|
|
tempo_atuacao = pControle.AtuDuracaoAtuacao,
|
|
capacidade_reservatorio = _Parametros.CapacidadeReservatorio,
|
|
percent_vertical_deteccao = pControle.AtuPercentualInicioPulverizacao / 100.0,
|
|
height_area_deteccao = pControle.AtuAlturaAreaPulverizacao / 100.0,
|
|
pct_erva_bico_on = pControle.AtuPercentualErvasBicoOn / 100.0,
|
|
pct_erva_bico_off = pControle.AtuPercentualErvasBicoOff / 100.0,
|
|
modelo_cabeca = pControle.AtuModeloCabeca.ToString().ToLower(),
|
|
modelo_classe_alvo = pControle.AtuClasseAlvo,
|
|
}
|
|
),
|
|
("Snr", new
|
|
{
|
|
percentual_altura_solo = CameraWorkerService.CameraCaminho?.PercentualAlturaSolo ?? 0.1,
|
|
distancia_maxima = CameraWorkerService.CameraCaminho?.DistanciaMaximaVisao ?? 5000,
|
|
distancia_minima = CameraWorkerService.CameraCaminho?.DistanciaMinimaVisao ?? 300
|
|
}
|
|
)
|
|
);
|
|
|
|
if (sincronizarMapa)
|
|
{
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
bool sucesso = await SolicitarAtualizacaoMapaAsync(
|
|
op,
|
|
aguardarGate: false
|
|
).ConfigureAwait(false);
|
|
|
|
if (!sucesso)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MAPSYNC] Sincronização não crítica não foi confirmada. Operação permanece bloqueada."
|
|
);
|
|
}
|
|
else if (
|
|
configurado &&
|
|
ReferenceEquals(op, Variaveis.OperacaoEmAndamento)
|
|
)
|
|
{
|
|
// Só publica configurado=true DEPOIS que o mapa foi
|
|
// confirmado pelo Manager/MPC.
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("configurado", true)
|
|
);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] Erro em sincronização não crítica: {ex.Message}"
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
//Console.WriteLine("Dados de operacao atualizados com sucesso!");
|
|
|
|
//AtualizandoRedis = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarDadosOperacao: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static readonly SemaphoreSlim _mapaSync = new SemaphoreSlim(1, 1);
|
|
private static long _mapaRevisionSeq = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
private static long ProximaRevisaoMapa()
|
|
{
|
|
return Interlocked.Increment(ref _mapaRevisionSeq);
|
|
}
|
|
|
|
private static string ObterIdOperacaoMapa(OperacaoModel op)
|
|
{
|
|
if (op == null)
|
|
return "";
|
|
|
|
if (!string.IsNullOrWhiteSpace(op.ID))
|
|
return op.ID;
|
|
|
|
long ticks = op.Sensoriamento?.Operacao?.DataInicio.Ticks ?? 0L;
|
|
return $"local-{ticks}-{op.GetHashCode()}";
|
|
}
|
|
|
|
public static async Task<bool> SincronizarMapaCriticoAsync(OperacaoModel op)
|
|
{
|
|
int ciclo = 0;
|
|
|
|
// Intertravamento publicado ANTES de esperar o gate. Mesmo que uma
|
|
// sincronizacao antiga termine nesse intervalo, ela nao pode liberar
|
|
// a operacao enquanto esta barreira estiver ativa.
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("mapa_barreira_critica", true),
|
|
("mapa_confirmado", false),
|
|
("configurado", false)
|
|
);
|
|
|
|
/*
|
|
* Barreira critica de partida:
|
|
* enquanto esta mesma operacao existir, nao desistimos de sincronizar.
|
|
* A operacao permanece configurado=false ate o ACK exato voltar.
|
|
*/
|
|
while (ReferenceEquals(op, Variaveis.OperacaoEmAndamento))
|
|
{
|
|
ciclo++;
|
|
|
|
// Publica no Redis o snapshot mais recente da trajetoria, sem
|
|
// disparar outra sincronizacao em paralelo e sem liberar operacao.
|
|
AtualizarDadosOperacao(
|
|
forcar: true,
|
|
sincronizarMapa: false,
|
|
configurado: false
|
|
);
|
|
|
|
bool sucesso = await SolicitarAtualizacaoMapaAsync(
|
|
op,
|
|
aguardarGate: true
|
|
).ConfigureAwait(false);
|
|
|
|
if (sucesso)
|
|
{
|
|
// Somente agora a configuracao pode ser considerada pronta.
|
|
AtualizarDadosOperacao(
|
|
forcar: true,
|
|
sincronizarMapa: false,
|
|
configurado: true
|
|
);
|
|
|
|
// A barreira e a ultima coisa a cair. Nesse instante a
|
|
// revisao, a quantidade e o ACK do Manager ja foram validados.
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("mapa_barreira_critica", false)
|
|
);
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC/CRITICO] Mapa confirmado. ciclo={ciclo}. Operacao liberavel."
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!ReferenceEquals(op, Variaveis.OperacaoEmAndamento))
|
|
return false;
|
|
|
|
int atrasoMs = Math.Min(2000, 250 + ciclo * 250);
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC/CRITICO] Mapa ainda nao confirmado. " +
|
|
$"retry em {atrasoMs}ms | ciclo={ciclo}."
|
|
);
|
|
|
|
await Task.Delay(atrasoMs).ConfigureAwait(false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static async Task<bool> SolicitarAtualizacaoMapaAsync(OperacaoModel op, bool aguardarGate = false)
|
|
{
|
|
if (
|
|
op == null ||
|
|
!(op.Parametros?.ControleAutomatico ?? false) ||
|
|
!(op.Trajetoria?._TrajetoriaFixa?.Any() ?? false)
|
|
)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MAPSYNC] Solicitação recusada: operação/mapa inválido."
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool gateObtido = false;
|
|
|
|
try
|
|
{
|
|
if (aguardarGate)
|
|
{
|
|
/*
|
|
* Sincronização CRÍTICA:
|
|
* jamais descartar porque outra sincronização
|
|
* está em andamento.
|
|
*/
|
|
await _mapaSync.WaitAsync().ConfigureAwait(false);
|
|
gateObtido = true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Sincronização normal:
|
|
* pode ser coalescida.
|
|
*/
|
|
gateObtido =
|
|
await _mapaSync.WaitAsync(0).ConfigureAwait(false);
|
|
|
|
if (!gateObtido)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MAPSYNC] Sincronização comum ignorada: " +
|
|
"outra atualização já está em andamento."
|
|
);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!ReferenceEquals(op, Variaveis.OperacaoEmAndamento))
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MAPSYNC] Operação mudou durante solicitação."
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
var pontosSnapshot = op.Trajetoria?._TrajetoriaFixa?.ToList();
|
|
int quantidadeEsperada = pontosSnapshot?.Count ?? 0;
|
|
|
|
if (quantidadeEsperada <= 0)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MAPSYNC] Snapshot vazio. Operacao continua bloqueada."
|
|
);
|
|
return false;
|
|
}
|
|
|
|
int primeiroIdxEsperado = pontosSnapshot.First().idxPonto;
|
|
int ultimoIdxEsperado = pontosSnapshot.Last().idxPonto;
|
|
|
|
/*
|
|
* A REVISAO identifica o snapshot logico do mapa.
|
|
* commandId identifica apenas cada tentativa de transporte.
|
|
*/
|
|
long mapRevision = ProximaRevisaoMapa();
|
|
string operationId = ObterIdOperacaoMapa(op);
|
|
|
|
// Invalida qualquer confirmacao anterior ANTES de publicar a
|
|
// nova tentativa. Assim uma revisao velha jamais libera a atual.
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("configurado", false),
|
|
("mapa_revisao_solicitada", mapRevision),
|
|
("mapa_operacao_id", operationId),
|
|
("mapa_quantidade_pontos_esperada", quantidadeEsperada),
|
|
("mapa_primeiro_idx_esperado", primeiroIdxEsperado),
|
|
("mapa_ultimo_idx_esperado", ultimoIdxEsperado),
|
|
("mapa_revisao_processada", 0L),
|
|
("mapa_quantidade_pontos_processada", 0),
|
|
("mapa_revisao_mpc_aplicada", 0L),
|
|
("mapa_quantidade_pontos_mpc_aplicada", 0),
|
|
("mapa_command_id_mpc_aplicado", ""),
|
|
("mapa_status_mpc", "pendente"),
|
|
("mapa_confirmado", false),
|
|
("mapa_status", "pendente")
|
|
);
|
|
|
|
for (int tentativa = 1; tentativa <= 3; tentativa++)
|
|
{
|
|
if (!ReferenceEquals(op, Variaveis.OperacaoEmAndamento))
|
|
return false;
|
|
|
|
var atual = op.Trajetoria?._TrajetoriaFixa;
|
|
int qtdAtual = atual?.Count ?? 0;
|
|
int primeiroAtual = qtdAtual > 0 ? atual.First().idxPonto : int.MinValue;
|
|
int ultimoAtual = qtdAtual > 0 ? atual.Last().idxPonto : int.MinValue;
|
|
|
|
// Se a trajetoria mudou enquanto esta transacao estava viva,
|
|
// nao tentamos "encaixar" a revisao antiga no mapa novo.
|
|
if (
|
|
qtdAtual != quantidadeEsperada ||
|
|
primeiroAtual != primeiroIdxEsperado ||
|
|
ultimoAtual != ultimoIdxEsperado
|
|
)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] Trajetoria mudou durante sincronizacao | " +
|
|
$"esperado={quantidadeEsperada}({primeiroIdxEsperado}->{ultimoIdxEsperado}) | " +
|
|
$"atual={qtdAtual}({primeiroAtual}->{ultimoAtual}). Nova revisao sera criada."
|
|
);
|
|
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("mapa_status", "alterado_durante_sync"),
|
|
("mapa_confirmado", false)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
string commandId = Guid.NewGuid().ToString("N");
|
|
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("mapa_command_id", commandId)
|
|
);
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] Tentativa={tentativa} | " +
|
|
$"cmd={commandId} | " +
|
|
$"rev={mapRevision} | " +
|
|
$"pontos={quantidadeEsperada} | " +
|
|
$"idx={primeiroIdxEsperado}->{ultimoIdxEsperado}"
|
|
);
|
|
|
|
bool atualizado = await AtualizarPontosMapa(
|
|
op,
|
|
commandId,
|
|
operationId,
|
|
mapRevision,
|
|
quantidadeEsperada
|
|
).ConfigureAwait(false);
|
|
|
|
if (atualizado)
|
|
{
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("mapa_status", "aplicado"),
|
|
("mapa_confirmado", true),
|
|
("mapa_quantidade_pontos_confirmada", quantidadeEsperada),
|
|
("mapa_revisao_confirmada", mapRevision)
|
|
);
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] SUCESSO EXATO | " +
|
|
$"rev={mapRevision} | " +
|
|
$"pontos={quantidadeEsperada} | " +
|
|
$"tentativa={tentativa}"
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] Falha/timeout | " +
|
|
$"tentativa={tentativa} | " +
|
|
$"cmd={commandId} | " +
|
|
$"rev={mapRevision} | " +
|
|
$"pontos={quantidadeEsperada}"
|
|
);
|
|
|
|
if (tentativa < 3)
|
|
await Task.Delay(250).ConfigureAwait(false);
|
|
}
|
|
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosOperacao,
|
|
("mapa_status", "erro"),
|
|
("mapa_confirmado", false)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
if (gateObtido)
|
|
_mapaSync.Release();
|
|
}
|
|
}
|
|
|
|
private static async Task<bool> AtualizarPontosMapa(
|
|
OperacaoModel op,
|
|
string commandId,
|
|
string operationId,
|
|
long mapRevision,
|
|
int quantidadeEsperada
|
|
)
|
|
{
|
|
if (
|
|
!(op?.Parametros?.ControleAutomatico ?? false) ||
|
|
!(op?.Trajetoria?._TrajetoriaFixa?.Any() ?? false)
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var comando = new
|
|
{
|
|
cmd = HealthWorkerCommandType.AtualizarPontosMapa,
|
|
command_id = commandId,
|
|
operation_id = operationId,
|
|
map_revision = mapRevision,
|
|
expected_point_count = quantidadeEsperada
|
|
};
|
|
|
|
long assinantes = RedisService.Publish(
|
|
CmdKey.HealthWorkerRx,
|
|
JsonConvert.SerializeObject(comando)
|
|
);
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC/TX] cmd={commandId} | " +
|
|
$"rev={mapRevision} | " +
|
|
$"subs={assinantes} | " +
|
|
$"pontos={quantidadeEsperada}"
|
|
);
|
|
|
|
if (assinantes <= 0)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] Nenhum assinante HealthWorker | " +
|
|
$"cmd={commandId}"
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool sucesso = await AguardarMapaAplicadoAsync(
|
|
commandId,
|
|
mapRevision,
|
|
TimeSpan.FromSeconds(4)
|
|
).ConfigureAwait(false);
|
|
|
|
if (!sucesso)
|
|
{
|
|
string status = RedisService.GetField(
|
|
CtxKey.DadosHealthWorker,
|
|
"mapa_status",
|
|
""
|
|
);
|
|
|
|
string erro = RedisService.GetField(
|
|
CtxKey.DadosHealthWorker,
|
|
"mapa_erro",
|
|
""
|
|
);
|
|
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] ACK não confirmado | " +
|
|
$"cmd={commandId} | " +
|
|
$"rev={mapRevision} | " +
|
|
$"status={status} | " +
|
|
$"erro={erro}"
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Segunda confirmacao, diretamente no estado transacional usado
|
|
// pelo Manager. O ACK so vale se a MESMA revisao e a MESMA
|
|
// quantidade de pontos realmente chegaram ao MPC.
|
|
long revisaoProcessada = RedisService.GetField<long>(
|
|
CtxKey.DadosOperacao,
|
|
"mapa_revisao_processada",
|
|
0L
|
|
);
|
|
|
|
int qtdProcessada = RedisService.GetField<int>(
|
|
CtxKey.DadosOperacao,
|
|
"mapa_quantidade_pontos_processada",
|
|
0
|
|
);
|
|
|
|
long revisaoMpc = RedisService.GetField<long>(
|
|
CtxKey.DadosOperacao,
|
|
"mapa_revisao_mpc_aplicada",
|
|
0L
|
|
);
|
|
|
|
int qtdMpc = RedisService.GetField<int>(
|
|
CtxKey.DadosOperacao,
|
|
"mapa_quantidade_pontos_mpc_aplicada",
|
|
0
|
|
);
|
|
|
|
string statusMpc = RedisService.GetField(
|
|
CtxKey.DadosOperacao,
|
|
"mapa_status_mpc",
|
|
""
|
|
);
|
|
|
|
bool confirmacaoExata =
|
|
revisaoProcessada == mapRevision &&
|
|
qtdProcessada == quantidadeEsperada &&
|
|
revisaoMpc == mapRevision &&
|
|
qtdMpc == quantidadeEsperada &&
|
|
statusMpc == "aplicado";
|
|
|
|
if (!confirmacaoExata)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
$"[MAPSYNC] ACK rejeitado por divergencia de snapshot | " +
|
|
$"revEsperada={mapRevision} | revProcessada={revisaoProcessada} | revMpc={revisaoMpc} | " +
|
|
$"qtdEsperada={quantidadeEsperada} | qtdProcessada={qtdProcessada} | qtdMpc={qtdMpc} | " +
|
|
$"statusMpc={statusMpc}"
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static async Task<bool> AguardarMapaAplicadoAsync(string commandId, long revision, TimeSpan timeout)
|
|
{
|
|
var limite = DateTime.UtcNow + timeout;
|
|
|
|
while (DateTime.UtcNow < limite)
|
|
{
|
|
string ultimoCommandId = RedisService.GetField(CtxKey.DadosHealthWorker, "ultimo_comando_mapa", "");
|
|
long revisaoAplicada = RedisService.GetField(CtxKey.DadosHealthWorker, "mapa_revisao_aplicada", 0L);
|
|
string status = RedisService.GetField(CtxKey.DadosHealthWorker, "mapa_status", "");
|
|
|
|
if (ultimoCommandId == commandId && revisaoAplicada == revision && status == "aplicado")
|
|
return true;
|
|
|
|
if (ultimoCommandId == commandId && status == "erro")
|
|
return false;
|
|
|
|
await Task.Delay(100).ConfigureAwait(false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void AtualizarDadosControleOperacao()
|
|
{
|
|
try
|
|
{
|
|
var pControle = Variaveis.OperacaoEmAndamento?.Parametros?.Controle;
|
|
if (pControle == null)
|
|
return;
|
|
RedisService.AtualizarCampos(
|
|
CtxKey.DadosControle,
|
|
("pulverizador_automatico", pControle.PulverizadorAutomatico),
|
|
("movimento_automatico", pControle.MovimentoAutomatico),
|
|
("direcional_automatico", pControle.DirecionalAutomatico),
|
|
("frenagem_automatica", pControle.FrenagemAutomaticaAoParar),
|
|
("oak_parada_por_bloqueio", pControle.OakParadaPorObstaculo),
|
|
("imu_parada_por_inclinacao", pControle.ImuParadaPorInclinacao),
|
|
("imu_auxilio_movimento", pControle.ImuAuxilioMovimento),
|
|
("imu_auxilio_direcional", pControle.ImuAuxilioDirecional),
|
|
("imu_direcional_preferir_arco", pControle.ImuDirecionalPreferirArco),
|
|
("dir_auxilio_sonar", pControle.DirAuxilioSonar),
|
|
("mov_auxilio_sonar", pControle.MovAuxilioSonar),
|
|
|
|
("pulverizador_manual_permitir_parado", true),
|
|
("pulverizador_auto_permitir_sem_movimento", false),
|
|
|
|
("pressao_linha", pControle.AtuPressaoLinha),
|
|
|
|
("registrar_dados_pos_processamento", pControle.RegistrarDadosPosProcessamento)
|
|
);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarDadosControleOperacao: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static void AtualizarDadosModulos()
|
|
{
|
|
var op = Variaveis.OperacaoEmAndamento;
|
|
try
|
|
{
|
|
if (op?.DispMvd == null) return;
|
|
if (op?.DispAtu == null) return;
|
|
if (op?.DispSen == null) return;
|
|
|
|
var _Sensoriamento = op.Sensoriamento;
|
|
var agora_dt = DateTime.Now;
|
|
var agora = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
|
|
double CalcularIdadeMs(DateTime instante)
|
|
{
|
|
if (instante == DateTime.MinValue)
|
|
return 999999;
|
|
|
|
return Math.Max(
|
|
0,
|
|
(agora_dt - instante).TotalMilliseconds
|
|
);
|
|
}
|
|
|
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Gps)));
|
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Dir)));
|
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Mov)));
|
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Sen)));
|
|
|
|
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Gps), string.Empty);
|
|
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Dir), string.Empty);
|
|
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Mov), string.Empty);
|
|
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Sen), string.Empty);
|
|
|
|
#region MOV e DIR
|
|
|
|
var dirAtivos = new List<int>();
|
|
var movAtivos = new List<int>();
|
|
|
|
foreach (var mod in op.DispMvd.Dados.Modulos)
|
|
{
|
|
double erroAnguloDir = Math.Abs(mod.DirMotor.AnguloLeitura - mod.DirMotor.Angulo_SP);
|
|
|
|
double idadeUltimoComandoMs =
|
|
mod.DirMotor.UltimoComandoEnviado == DateTime.MinValue
|
|
? 999999
|
|
: (agora_dt - mod.DirMotor.UltimoComandoEnviado).TotalMilliseconds;
|
|
|
|
double idadeUltimaRespostaMs =
|
|
mod.DirMotor.Leitura?.UltimoComandoRecebido == DateTime.MinValue
|
|
? 999999
|
|
: (agora_dt - (mod.DirMotor.Leitura?.UltimoComandoRecebido ?? DateTime.MinValue)).TotalMilliseconds;
|
|
|
|
int endereco_dir = mod.DirMotor._EnderecoCAN_Tx;
|
|
var camposDir = new List<(string campo, object valor)>
|
|
{
|
|
($"{endereco_dir}.timestamp", agora),
|
|
($"{endereco_dir}.conectado", mod.DirMotor.Inicializado),
|
|
|
|
// legado
|
|
($"{endereco_dir}.freq_base", op.DispMvd.Dados.tmrRegistradorDIR.IntervalHz),
|
|
|
|
($"{endereco_dir}.endereco", mod.DirMotor._EnderecoCAN_Tx),
|
|
($"{endereco_dir}.mod_id", mod.Modulo_ID),
|
|
|
|
($"{endereco_dir}.angulo", mod.DirMotor.AnguloLeitura),
|
|
($"{endereco_dir}.angulo_sp", mod.DirMotor.Angulo_SP),
|
|
($"{endereco_dir}.comandar", mod.DirMotor.Comandar),
|
|
($"{endereco_dir}.referenciado", mod.DirMotor.Referenciado),
|
|
|
|
($"{endereco_dir}.in1_atuado", mod.DirMotor.IN1_Atuado),
|
|
($"{endereco_dir}.sentido", mod.DirMotor.Sentido),
|
|
($"{endereco_dir}.sentido_sp", mod.DirMotor.Sentido_SP),
|
|
($"{endereco_dir}.sentido_real", mod.DirMotor.SentidoReal),
|
|
|
|
($"{endereco_dir}.erro_angulo", erroAnguloDir),
|
|
($"{endereco_dir}.atingiu_angulo_sp", mod.DirMotor.AtingiuAnguloSP),
|
|
($"{endereco_dir}.ultimo_comando_ms", idadeUltimoComandoMs),
|
|
($"{endereco_dir}.ultima_resposta_ms", idadeUltimaRespostaMs),
|
|
($"{endereco_dir}.ultima_direcao", mod.DirMotor.UltimaDirecao),
|
|
($"{endereco_dir}.status_movimento", mod.DirMotor.Leitura?.StatusMovimento),
|
|
($"{endereco_dir}.rpm", mod.DirMotor.RPM),
|
|
($"{endereco_dir}.em_movimento", mod.DirMotor.EmMovimento),
|
|
($"{endereco_dir}.retornando_a_zero", mod.DirMotor.RetornandoAzero),
|
|
|
|
// frequências reais medidas
|
|
($"{endereco_dir}.freq.pulsosrecebidos", mod.DirMotor.Leitura?.NumeroPulsos?.frequencia ?? 0),
|
|
($"{endereco_dir}.freq.io", mod.DirMotor.Leitura?.EntradaIN1?.frequencia ?? 0),
|
|
($"{endereco_dir}.freq.rpm", mod.DirMotor.Leitura?.RPM?.frequencia ?? 0),
|
|
|
|
// mantém disponíveis para diagnóstico futuro, mesmo que não estejam no polling normal
|
|
($"{endereco_dir}.freq.erro", 0),
|
|
($"{endereco_dir}.freq.encoder_carregado", 0),
|
|
($"{endereco_dir}.freq.encoder_acumulado", 0),
|
|
($"{endereco_dir}.freq.versao", 0),
|
|
};
|
|
|
|
camposDir.AddRange(MKS057DCanService.GerarCamposRedisHealthBase(endereco_dir));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), camposDir.ToArray());
|
|
|
|
|
|
double erroRpmMov = Math.Abs(mod.MovMotor.RPM_Roda - mod.MovMotor.RPM_SP_Leitura);
|
|
|
|
double erroRpmPctMov =
|
|
Math.Abs(mod.MovMotor.RPM_SP_Leitura) > 1
|
|
? erroRpmMov / Math.Abs(mod.MovMotor.RPM_SP_Leitura)
|
|
: 0;
|
|
|
|
bool atingiuRpmSp =
|
|
Math.Abs(mod.MovMotor.RPM_SP_Leitura) < 25
|
|
? Math.Abs(mod.MovMotor.RPM_Roda) < 35
|
|
: erroRpmPctMov <= 0.45 || erroRpmMov <= 45;
|
|
|
|
double idadeUltimoComandoMovMs =
|
|
mod.MovMotor.UltimoComandoEnviado == DateTime.MinValue
|
|
? 999999
|
|
: (agora_dt - mod.MovMotor.UltimoComandoEnviado).TotalMilliseconds;
|
|
|
|
double idadeUltimaRespostaMovMs =
|
|
mod.MovMotor.Leitura?.UltimoComandoRecebido == DateTime.MinValue
|
|
? 999999
|
|
: (agora_dt - (mod.MovMotor.Leitura?.UltimoComandoRecebido ?? DateTime.MinValue)).TotalMilliseconds;
|
|
|
|
int endereco_mov = mod.MovMotor._EnderecoCAN_Tx;
|
|
var camposMov = new List<(string campo, object valor)>
|
|
{
|
|
($"{endereco_mov}.timestamp", agora),
|
|
($"{endereco_mov}.conectado", mod.MovMotor.Inicializado),
|
|
|
|
// legado
|
|
($"{endereco_mov}.freq_base", OIDCanService.ObterFreqBaseHz(OIDCanService.OidHandler.OidParametros.VelocidadeAtual)),
|
|
|
|
($"{endereco_mov}.endereco", mod.MovMotor._EnderecoCAN_Tx),
|
|
($"{endereco_mov}.mod_id", mod.Modulo_ID),
|
|
|
|
($"{endereco_mov}.cod_erro", mod.MovMotor.CodigoAlarme),
|
|
($"{endereco_mov}.rpm", mod.MovMotor.RPM_Roda),
|
|
($"{endereco_mov}.rpm_sp", mod.MovMotor.RPM_SP_Leitura),
|
|
($"{endereco_mov}.tensao", mod.MovMotor.Tensao),
|
|
($"{endereco_mov}.corrente_motor", mod.MovMotor.Corrente_Motor),
|
|
($"{endereco_mov}.corrente_barramento", mod.MovMotor.Corrente_Barramento),
|
|
($"{endereco_mov}.temperatura_driver", mod.MovMotor.TemperaturaDriver),
|
|
($"{endereco_mov}.temperatura_motor", mod.MovMotor.Temperatura),
|
|
($"{endereco_mov}.freado", mod.MovMotor.Leitura?.Freado ?? false),
|
|
|
|
($"{endereco_mov}.freq.erro", mod.MovMotor.Leitura?.CodigoAlarme?.frequencia ?? 0),
|
|
($"{endereco_mov}.freq.rpm", mod.MovMotor.Leitura?.ERPM?.frequencia ?? 0),
|
|
($"{endereco_mov}.freq.tensao", mod.MovMotor.Leitura?.Tensao?.frequencia ?? 0),
|
|
($"{endereco_mov}.freq.corrente_motor", mod.MovMotor.Leitura?.CorrenteMotor?.frequencia ?? 0),
|
|
($"{endereco_mov}.freq.corrente_barramento", mod.MovMotor.Leitura?.CorrenteBarramento?.frequencia ?? 0),
|
|
($"{endereco_mov}.freq.temperatura", mod.MovMotor.Leitura?.Temperatura?.frequencia ?? 0),
|
|
|
|
($"{endereco_mov}.freq.ciclo_trabalho", mod.MovMotor.Leitura?.CicloTrabalho?.frequencia ?? 0),
|
|
($"{endereco_mov}.freq.potencia", mod.MovMotor.Leitura?.Potencia?.frequencia ?? 0),
|
|
|
|
|
|
($"{endereco_mov}.comandar", mod.MovMotor.Comandar),
|
|
($"{endereco_mov}.comando_ligado", mod.MovMotor.ComandoLigado),
|
|
($"{endereco_mov}.leitura_ligado", mod.MovMotor.LeituraLigado),
|
|
($"{endereco_mov}.alarme_motor", mod.MovMotor.AlarmeMotor),
|
|
($"{endereco_mov}.alarme_comando", mod.MovMotor.AlarmeComando),
|
|
($"{endereco_mov}.alarme_sensores", mod.MovMotor.AlarmeSensores),
|
|
|
|
($"{endereco_mov}.sentido", mod.MovMotor.Sentido),
|
|
($"{endereco_mov}.sentido_sp", mod.MovMotor.Sentido_SP),
|
|
|
|
($"{endereco_mov}.erro_rpm", erroRpmMov),
|
|
($"{endereco_mov}.erro_rpm_pct", erroRpmPctMov),
|
|
($"{endereco_mov}.atingiu_rpm_sp", atingiuRpmSp),
|
|
|
|
($"{endereco_mov}.ultimo_comando_ms", idadeUltimoComandoMovMs),
|
|
($"{endereco_mov}.ultima_resposta_ms", idadeUltimaRespostaMovMs),
|
|
|
|
($"{endereco_mov}.freio_sp", mod.MovMotor.UltimoFreioSP),
|
|
($"{endereco_mov}.modo_controle", mod.MovMotor.Leitura?.ModoControle),
|
|
($"{endereco_mov}.ultimo_modo_controle", mod.MovMotor.UltimoModoControleEnviado),
|
|
($"{endereco_mov}.ultimo_setpoint", mod.MovMotor.UltimoSetPointEnviado),
|
|
};
|
|
|
|
camposMov.AddRange(OIDCanService.GerarCamposRedisHealthBase(endereco_mov));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), camposMov.ToArray());
|
|
|
|
dirAtivos.Add(endereco_dir);
|
|
movAtivos.Add(endereco_mov);
|
|
}
|
|
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), ("ativos", dirAtivos.ToList()));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), ("ativos", movAtivos.ToList()));
|
|
|
|
#endregion
|
|
|
|
#region SEN
|
|
|
|
var DadosSen = op.DispSen.Dados;
|
|
DateTime ultimaRespostaSen = DadosSen.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
|
|
|
double idadeUltimaRespostaSenMs = CalcularIdadeMs(ultimaRespostaSen);
|
|
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen),
|
|
("timestamp", agora),
|
|
("conectado", DadosSen.Conectado),
|
|
("telemetria_recente", DadosSen.TelemetriaRecente),
|
|
("firmware_conectado", DadosSen.FirmwareDeclarouConectado),
|
|
("freq_base", DadosSen.FrequenciaBase),
|
|
("bms_ligado", _Sensoriamento.Bateria?.BmsLigado ?? false),
|
|
|
|
("ultima_resposta_ms", idadeUltimaRespostaSenMs),
|
|
("latencia_loop_ms", DadosSen.DadosLeitura?.LatenciaLoop ?? 0),
|
|
("percentual_bateria", _Sensoriamento.Bateria?.PercentualBateria ?? 0)
|
|
);
|
|
var senSensoresAtualizados = new List<(string caminho, object valor)>();
|
|
var senSensoresAtivos = new List<string>();
|
|
foreach (var sensor in DadosSen.Sensores)
|
|
{
|
|
int tipo = (int)sensor.Componente;
|
|
int id = sensor.ID_Num;
|
|
string basePath = $"sensores.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
|
|
|
|
var dados = sensor.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
nominal = y.nominal,
|
|
maximo = y.maximo,
|
|
minimo = y.minimo,
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia,
|
|
unidade = y.unidade_medida,
|
|
})
|
|
.ToList();
|
|
|
|
double idadeUltimaRespostaSensorMs = CalcularIdadeMs(sensor.UltimaLeitura);
|
|
|
|
var sensorObj = new
|
|
{
|
|
conectado = sensor.Inicializado,
|
|
id,
|
|
label = sensor.ID,
|
|
tipo,
|
|
freq_base = DadosSen.FrequenciaBase,
|
|
aferir = sensor.Aferir,
|
|
mandatorio = sensor.Mandatorio,
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
|
|
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
|
};
|
|
|
|
senSensoresAtivos.Add($"{tipo}.{id}");
|
|
senSensoresAtualizados.Add(($"{basePath}", sensorObj));
|
|
}
|
|
senSensoresAtualizados.Add(("sensores.ativos", senSensoresAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senSensoresAtualizados.ToArray());
|
|
|
|
var senServosAtualizados = new List<(string caminho, object valor)>();
|
|
var senServosAtivos = new List<string>();
|
|
foreach (var servo in DadosSen.Servos)
|
|
{
|
|
int tipo = (int)servo.Componente;
|
|
int id = servo.ID_Num;
|
|
string basePath = $"servos.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
|
|
|
|
var dados = servo.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia
|
|
})
|
|
.ToList();
|
|
|
|
bool possuiComandoServo = servo.UltimoComandoEnviado != DateTime.MinValue;
|
|
|
|
DateTime ultimaRespostaAnguloServo =
|
|
servo.ValoresLeituras?
|
|
.Where(x =>x.funcao == FuncoesPinout.ServoAnguloLeitura)
|
|
.OrderByDescending(x =>x.atual?.lidoEm ?? DateTime.MinValue)
|
|
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
|
|
|
bool possuiRespostaServo = ultimaRespostaAnguloServo != DateTime.MinValue;
|
|
|
|
double idadeUltimoComandoServoMs = CalcularIdadeMs(servo.UltimoComandoEnviado);
|
|
double idadeUltimaRespostaServoMs = CalcularIdadeMs(ultimaRespostaAnguloServo);
|
|
|
|
bool telemetriaPosComandoServo =
|
|
possuiComandoServo &&
|
|
possuiRespostaServo &&
|
|
ultimaRespostaAnguloServo >= servo.UltimoComandoEnviado;
|
|
|
|
var servoObj = new
|
|
{
|
|
conectado = servo.Inicializado,
|
|
id,
|
|
label = servo.ID,
|
|
tipo,
|
|
freq_base = DadosSen.FrequenciaBase,
|
|
controlar = servo.Controlar,
|
|
mandatorio = servo.Mandatorio,
|
|
|
|
angulo_sp = servo._AnguloControle,
|
|
angulo_desejado = servo.UltimoAnguloDesejado,
|
|
angulo_leitura = servo._AnguloLeiutra,
|
|
|
|
possui_comando = possuiComandoServo,
|
|
telemetria_pos_comando = telemetriaPosComandoServo,
|
|
|
|
ultimo_comando_ms = idadeUltimoComandoServoMs,
|
|
|
|
ultima_resposta_ms = idadeUltimaRespostaServoMs,
|
|
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
};
|
|
|
|
senServosAtivos.Add($"{tipo}.{id}");
|
|
senServosAtualizados.Add(($"{basePath}", servoObj));
|
|
}
|
|
senServosAtualizados.Add(("servos.ativos", senServosAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senServosAtualizados.ToArray());
|
|
|
|
var senRelesAtualizados = new List<(string caminho, object valor)>();
|
|
var senRelesAtivos = new List<string>();
|
|
foreach (var rele in DadosSen.Reles)
|
|
{
|
|
int tipo = (int)rele.Componente;
|
|
int id = rele.ID_Num;
|
|
string basePath = $"reles.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
|
|
|
|
var dados = rele.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia
|
|
})
|
|
.ToList();
|
|
|
|
DateTime ultimaRespostaEstadoRele =
|
|
rele.ValoresLeituras?
|
|
.Where(x =>x.funcao == FuncoesPinout.EstadoLeitura)
|
|
.OrderByDescending(x =>x.atual?.lidoEm ?? DateTime.MinValue)
|
|
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
|
|
|
bool possuiComandoRele = rele.UltimoComandoEnviado != DateTime.MinValue;
|
|
|
|
bool possuiRespostaRele = ultimaRespostaEstadoRele != DateTime.MinValue;
|
|
|
|
double idadeUltimoComandoReleMs = CalcularIdadeMs(rele.UltimoComandoEnviado);
|
|
double idadeUltimaRespostaReleMs = CalcularIdadeMs(ultimaRespostaEstadoRele);
|
|
|
|
bool telemetriaPosComandoRele =
|
|
possuiComandoRele &&
|
|
possuiRespostaRele &&
|
|
ultimaRespostaEstadoRele >= rele.UltimoComandoEnviado;
|
|
|
|
var releObj = new
|
|
{
|
|
conectado = rele.Inicializado,
|
|
id,
|
|
label = rele.ID,
|
|
tipo,
|
|
freq_base = DadosSen.FrequenciaBase,
|
|
controlar = rele.Controlar,
|
|
mandatorio = rele.Mandatorio,
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
|
|
estado_desejado = rele.UltimoEstadoDesejado,
|
|
estado_controle = rele.StatusControle,
|
|
estado_leitura = rele._EstadoLeitura,
|
|
|
|
possui_comando = possuiComandoRele,
|
|
telemetria_pos_comando = telemetriaPosComandoRele,
|
|
|
|
ultimo_comando_ms = idadeUltimoComandoReleMs,
|
|
ultima_resposta_ms = idadeUltimaRespostaReleMs,
|
|
};
|
|
|
|
senRelesAtivos.Add($"{tipo}.{id}");
|
|
senRelesAtualizados.Add(($"{basePath}", releObj));
|
|
}
|
|
senRelesAtualizados.Add(("reles.ativos", senRelesAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senRelesAtualizados.ToArray());
|
|
|
|
var senLedsAtualizados = new List<(string caminho, object valor)>();
|
|
var senLedsAtivos = new List<string>();
|
|
foreach (var led in DadosSen.Sinaleiros)
|
|
{
|
|
int tipo = (int)led.Componente;
|
|
int id = led.ID_Num;
|
|
string basePath = $"sinaleiros.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
|
|
|
|
var dados = led.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia
|
|
})
|
|
.ToList();
|
|
|
|
DateTime ultimaRespostaLedId =
|
|
led.ValoresLeituras?
|
|
.Where(x =>x.funcao == FuncoesPinout.StatusLedID)
|
|
.OrderByDescending(x => x.atual?.lidoEm ?? DateTime.MinValue)
|
|
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
|
|
|
DateTime ultimaRespostaLedStatus =
|
|
led.ValoresLeituras?
|
|
.Where(x =>x.funcao == FuncoesPinout.StatusLedLeitura)
|
|
.OrderByDescending(x =>x.atual?.lidoEm ?? DateTime.MinValue)
|
|
.FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
|
|
|
|
bool possuiComandoLed = led.UltimoComandoEnviado != DateTime.MinValue;
|
|
|
|
bool possuiRespostaLed =
|
|
ultimaRespostaLedId != DateTime.MinValue &&
|
|
ultimaRespostaLedStatus != DateTime.MinValue;
|
|
|
|
/*
|
|
* Usa a mais antiga das duas respostas.
|
|
* Assim ultima_resposta_ms representa a idade do conjunto completo.
|
|
*/
|
|
DateTime ultimaRespostaLed =
|
|
possuiRespostaLed
|
|
? (ultimaRespostaLedId <= ultimaRespostaLedStatus ? ultimaRespostaLedId : ultimaRespostaLedStatus)
|
|
: DateTime.MinValue;
|
|
|
|
double idadeUltimoComandoLedMs = CalcularIdadeMs(led.UltimoComandoEnviado);
|
|
double idadeUltimaRespostaLedMs = CalcularIdadeMs(ultimaRespostaLed);
|
|
|
|
bool telemetriaPosComandoLed =
|
|
possuiComandoLed &&
|
|
possuiRespostaLed &&
|
|
ultimaRespostaLedId >= led.UltimoComandoEnviado &&
|
|
ultimaRespostaLedStatus >= led.UltimoComandoEnviado;
|
|
|
|
int statusLedSp = led.UltimoComportamentoDesejado != null ? (int)led.UltimoComportamentoDesejado.statusLED : -1;
|
|
|
|
var ledObj = new
|
|
{
|
|
conectado = led.Inicializado,
|
|
id,
|
|
label = led.ID,
|
|
tipo,
|
|
freq_base = DadosSen.FrequenciaBase,
|
|
controlar = led.Controlar,
|
|
mandatorio = led.Mandatorio,
|
|
|
|
status_led_id_sp = led.UltimoComportamentoDesejado?.id ?? -1,
|
|
|
|
status_led_sp = statusLedSp,
|
|
|
|
status_led_id = (int)led._StatusLedId,
|
|
status_led_leitura = (int)led._StatusLed,
|
|
|
|
possui_comando = possuiComandoLed,
|
|
telemetria_pos_comando = telemetriaPosComandoLed,
|
|
|
|
ultimo_comando_ms = idadeUltimoComandoLedMs,
|
|
|
|
ultima_resposta_ms = idadeUltimaRespostaLedMs,
|
|
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
};
|
|
|
|
senLedsAtivos.Add($"{tipo}.{id}");
|
|
senLedsAtualizados.Add(($"{basePath}", ledObj));
|
|
}
|
|
senLedsAtualizados.Add(("sinaleiros.ativos", senLedsAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senLedsAtualizados.ToArray());
|
|
|
|
#endregion
|
|
|
|
#region ATU
|
|
|
|
var DadosAtu = op.DispAtu.Dados;
|
|
DateTime ultimaRespostaAtu = DadosAtu.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
|
|
|
double idadeUltimaRespostaAtuMs = CalcularIdadeMs(ultimaRespostaAtu);
|
|
|
|
bool pulverizadorAutomatico = op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
|
|
|
|
double pressaoAlvo = op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
|
|
|
double pressaoLinha = DadosAtu?.BombaPressurizadora?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
|
double toleranciaPressao = Math.Max(2.0, pressaoAlvo * 0.15);
|
|
bool pressaoLinhaRegular = pressaoAlvo <= 0 || pressaoLinha >= Math.Max(0, pressaoAlvo - toleranciaPressao);
|
|
RedisService.AtualizarCampos(
|
|
RedisService.ModKey(Enums.T_Code.Atu),
|
|
("timestamp", agora),
|
|
("conectado", DadosAtu.Conectado),
|
|
("freq_base", DadosAtu.FrequenciaBase),
|
|
|
|
("ultima_resposta_ms", idadeUltimaRespostaAtuMs),
|
|
("latencia_loop_ms", DadosAtu.DadosLeitura?.LatenciaLoop ?? 0),
|
|
|
|
("pulverizador_automatico", pulverizadorAutomatico),
|
|
("pulverizador_liberado", DadosAtu.ContadorCarga.PulverizadorLiberado),
|
|
|
|
("operacao_iniciada", op.Sensoriamento.Operacao.OperacaoIniciada),
|
|
("calibrando", op.Sensoriamento.Operacao.Calibrando),
|
|
("pausa", op.Sensoriamento.Operacao.Pausa),
|
|
("emergencia", op.Sensoriamento.Operacao.Emergencia),
|
|
("finalizando", op.Sensoriamento.Operacao.Finalizando),
|
|
|
|
("percentual_reservatorio", DadosAtu.ContadorCarga.PercentualAtual),
|
|
("pressao_linha", pressaoLinha),
|
|
("pressao_linha_regular", pressaoLinhaRegular),
|
|
("vazao_linha_mls", DadosAtu.VazaoFluxoMLpSReferencia),
|
|
|
|
("tempo_atuado", DadosAtu.TempoAtuado),
|
|
("volume_vazado_ml", DadosAtu.VolumeVazadoML),
|
|
("vazao_media_mls", DadosAtu.VazaoMediaMLs),
|
|
|
|
("bomba_cavitada", _Sensoriamento.Atuador?.BombaCavitada ?? false)
|
|
);
|
|
var atuSensoresAtualizados = new List<(string caminho, object valor)>();
|
|
var atuSensoresAtivos = new List<string>();
|
|
foreach (var sensor in DadosAtu.Sensores)
|
|
{
|
|
int tipo = (int)sensor.Componente;
|
|
int id = sensor.ID_Num;
|
|
string basePath = $"sensores.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Atu), $"{basePath}.saude");
|
|
|
|
var dados = sensor.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
nominal = y.nominal,
|
|
maximo = y.maximo,
|
|
minimo = y.minimo,
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia
|
|
})
|
|
.ToList();
|
|
|
|
double idadeUltimaRespostaSensorMs = CalcularIdadeMs(sensor.UltimaLeitura);
|
|
|
|
var sensorObj = new
|
|
{
|
|
conectado = sensor.Inicializado,
|
|
id,
|
|
label = sensor.ID,
|
|
tipo,
|
|
aferir = sensor.Aferir,
|
|
mandatorio = sensor.Mandatorio,
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
freq_base = DadosAtu.FrequenciaBase,
|
|
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
|
};
|
|
|
|
atuSensoresAtivos.Add($"{tipo}.{id}");
|
|
atuSensoresAtualizados.Add(($"{basePath}", sensorObj));
|
|
}
|
|
atuSensoresAtualizados.Add(("sensores.ativos", atuSensoresAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuSensoresAtualizados.ToArray());
|
|
|
|
var atuBicosAtualizados = new List<(string caminho, object valor)>();
|
|
var atuBicosAtivos = new List<string>();
|
|
foreach (var bico in DadosAtu.BicosPulverizadores)
|
|
{
|
|
int tipo = (int)bico.Componente;
|
|
int id = bico.ID_Num;
|
|
string basePath = $"bicos.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Atu), $"{basePath}.saude");
|
|
|
|
var dados = bico.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
nominal = y.nominal,
|
|
maximo = y.maximo,
|
|
minimo = y.minimo,
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia
|
|
})
|
|
.ToList();
|
|
|
|
DateTime ultimoComandoBico = bico.UltimoComandoEnfileirado;
|
|
|
|
double idadeUltimoComandoBicoMs = CalcularIdadeMs(ultimoComandoBico);
|
|
|
|
double idadeUltimaRespostaBicoMs = CalcularIdadeMs(bico.UltimaLeitura);
|
|
|
|
var bicoObj = new
|
|
{
|
|
conectado = bico.Inicializado,
|
|
id,
|
|
label = bico.ID,
|
|
tipo,
|
|
comandar = bico.Comandar,
|
|
mandatorio = bico.Mandatorio,
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
freq_base = DadosAtu.FrequenciaBase,
|
|
|
|
comando_atuar = bico.ComandoAtuar,
|
|
estado_desejado = bico.UltimoStatusDesejado,
|
|
angulo_desejado = bico.UltimoAnguloDesejado,
|
|
|
|
estado_leitura = bico._EstadoLeitura,
|
|
angulo_leitura = bico._AnguloBicoLeitura,
|
|
|
|
com_movimento = bico.ComMovimento,
|
|
angulo_neutro = bico.AnguloNeutro,
|
|
angulo_controle = bico.AnguloControle,
|
|
angulo_abertura = bico.AnguloAbertura,
|
|
|
|
atuacoes = bico.Atuacoes,
|
|
tempo_atuado = bico.TempoAtuado,
|
|
tempo_atuado_em_movimento = bico.TempoAtuadoEmMovimento,
|
|
tempo_com_fluxo = bico.TempoComFluxo,
|
|
vazao_instantanea_mls = bico.VazaoInstantaneaMLs,
|
|
vazao_media_mls = bico.VazaoMediaMLs,
|
|
volume_vazado_ml = bico.VolumeVazadoML,
|
|
|
|
comando_solicitado = bico.Comandar && bico.ComandoAtuar,
|
|
status_desejado_fisico = bico.UltimoStatusDesejado,
|
|
angulo_desejado_fisico = bico.UltimoAnguloDesejado,
|
|
ultimo_comando_ms = idadeUltimoComandoBicoMs,
|
|
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
|
|
telemetria_pos_comando =
|
|
ultimoComandoBico != DateTime.MinValue &&
|
|
bico.UltimaRespostaPosteriorAoComando != DateTime.MinValue &&
|
|
bico.UltimaRespostaPosteriorAoComando >= ultimoComandoBico
|
|
};
|
|
|
|
atuBicosAtualizados.Add(($"{basePath}", bicoObj));
|
|
atuBicosAtivos.Add($"{tipo}.{id}");
|
|
}
|
|
atuBicosAtualizados.Add(("bicos.ativos", atuBicosAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuBicosAtualizados.ToArray());
|
|
|
|
var atuBombasAtualizados = new List<(string caminho, object valor)>();
|
|
var atuBombasAtivos = new List<string>();
|
|
foreach (var bomba in DadosAtu.BombasPressurizadoras)
|
|
{
|
|
int tipo = (int)bomba.Componente;
|
|
int id = bomba.ID_Num;
|
|
string basePath = $"bombas.{tipo}.{id}";
|
|
|
|
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Atu), $"{basePath}.saude");
|
|
|
|
var dados = bomba.ValoresLeituras
|
|
.Where(y => y.analise_health)
|
|
.Select(y => new
|
|
{
|
|
label = y.funcao.ToString(),
|
|
nominal = y.nominal,
|
|
maximo = y.maximo,
|
|
minimo = y.minimo,
|
|
atual = y.atual.valor,
|
|
freq = y.atual.frequencia
|
|
})
|
|
.ToList();
|
|
|
|
DateTime ultimoComandoBomba = bomba.UltimoComandoEnfileirado;
|
|
|
|
double idadeUltimoComandoBombaMs = CalcularIdadeMs(ultimoComandoBomba);
|
|
|
|
double idadeUltimaRespostaBombaMs = CalcularIdadeMs(bomba.UltimaLeitura);
|
|
|
|
var sensorObj = new
|
|
{
|
|
conectado = bomba.Inicializado,
|
|
id,
|
|
label = bomba.ID,
|
|
tipo,
|
|
comandar = bomba.Comandar,
|
|
mandatorio = bomba.Mandatorio,
|
|
dados,
|
|
timestamp = agora,
|
|
saude = saude ?? new { },
|
|
freq_base = DadosAtu.FrequenciaBase,
|
|
|
|
comando_atuar = bomba.ComandoAtuar,
|
|
estado_desejado = bomba.UltimoStatusDesejado,
|
|
potencia_desejada = bomba.UltimaPotenciaDesejada,
|
|
pressao_desejada = bomba.UltimaPressaoDesejada,
|
|
|
|
estado_leitura = bomba._EstadoLeitura,
|
|
bomba_ligada = bomba._BombaLigada,
|
|
|
|
potencia_sp_model = bomba.PotenciaSP,
|
|
pressao_sp_model = bomba.PressaoSP,
|
|
|
|
potencia_sp = bomba._PotenciaSP,
|
|
potencia = bomba._Potencia,
|
|
pressao_sp = bomba._PressaoSP,
|
|
pressao = bomba._PressaoAtual,
|
|
|
|
modo_controle = bomba.Controle?.Modo,
|
|
sensor_id_num = bomba.Sensor_ID_Num,
|
|
|
|
atuacoes = bomba.Atuacoes,
|
|
tempo_atuado = bomba.TempoAtuado,
|
|
tempo_com_potencia = bomba.TempoComPotencia,
|
|
potencia_media_ativa = bomba.PotenciaMediaAtiva,
|
|
|
|
comando_solicitado = bomba.Comandar && bomba.ComandoAtuar,
|
|
status_desejado_fisico = bomba.UltimoStatusDesejado,
|
|
potencia_desejada_fisica = bomba.UltimaPotenciaDesejada,
|
|
pressao_desejada_fisica = bomba.UltimaPressaoDesejada,
|
|
ultimo_comando_ms = idadeUltimoComandoBombaMs,
|
|
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
|
|
telemetria_pos_comando =
|
|
ultimoComandoBomba != DateTime.MinValue &&
|
|
bomba.UltimaRespostaPosteriorAoComando != DateTime.MinValue &&
|
|
bomba.UltimaRespostaPosteriorAoComando >= ultimoComandoBomba,
|
|
desligar_sem_bicos_abertos = bomba.Controle?.DesligarSemBicosAbertos ?? true,
|
|
qtd_bicos_ligados = bomba._QtdBicosLigados,
|
|
|
|
potencia_final = bomba._PotenciaFinal,
|
|
potencia_feedforward = bomba._PotenciaFeedForwared,
|
|
pressao_sensor = bomba._PressaoSensor,
|
|
pressao_sp_controle = bomba._PressaoSPControle,
|
|
pressao_sp_fora_mapa = bomba._PressaoSPForaDoMapa,
|
|
fluxo_insuficiente = bomba._FluxoInsuficiente,
|
|
recuperacao_fluxo_ativa = bomba._RecuperacaoFluxoAtiva,
|
|
cavitacao_suspeita = bomba._CavitacaoSuspeita,
|
|
cavitada = bomba._Cavitada,
|
|
corte_seguranca_ativo = bomba._CorteSegurancaAtivo,
|
|
pressao_sp_limitado = bomba._PressaoSPLimitadoPorCapacidade,
|
|
falha_sensor_pressao = bomba._FalhaSensorPressao
|
|
};
|
|
|
|
atuBombasAtivos.Add($"{tipo}.{id}");
|
|
atuBombasAtualizados.Add(($"{basePath}", sensorObj));
|
|
}
|
|
atuBombasAtualizados.Add(("bombas.ativos", atuBombasAtivos));
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuBombasAtualizados.ToArray());
|
|
|
|
#endregion
|
|
|
|
|
|
//var DadosLra = Variaveis.LoraService;
|
|
//RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Lra),
|
|
// ("timestamp", agora),
|
|
// ("conectado", DadosLra.Conectado),
|
|
// ("configurado", DadosLra.Configurado),
|
|
// ("tempo_base_tx", DadosLra.TempoEnvioDadosBase / 1000.0),
|
|
// ("tempo_base_rx", VariaveisEquipamento.TempoEntrePingsConexao / 1000.0),
|
|
// ("last_tx", DadosLra.UltimoTxDados),
|
|
// ("last_rx", DadosLra.UltimoRxDados)
|
|
//);
|
|
|
|
var DadosMod = _Sensoriamento.DadosPerformance;
|
|
RedisService.AtualizarCampos(
|
|
RedisService.ModKey(Enums.T_Code.Npc),
|
|
// Base / sincronismo
|
|
("timestamp", agora),
|
|
("timestamp_unix_ms", DadosMod.TimestampUnixMs),
|
|
("freq_base", 1.0 / op.tmrLeiturasInterval),
|
|
|
|
// CPU
|
|
("cpu_load_percent", DadosMod.CPU_load_percent ?? 0),
|
|
("cpu_temp_package_c", DadosMod.CPU_temp_package_c ?? 0),
|
|
("cpu_power_package_w", DadosMod.CPU_power_package_w ?? 0),
|
|
("cpu_power_cores_w", DadosMod.CPU_power_cores_w ?? 0),
|
|
("cpu_power_graphics_w", DadosMod.CPU_power_graphics_w ?? 0),
|
|
("cpu_power_dram_w", DadosMod.CPU_power_dram_w ?? 0),
|
|
("cpu_clock_avg_mhz", DadosMod.CPU_clock_avg_mhz ?? 0),
|
|
("cpu_clock_max_mhz", DadosMod.CPU_clock_max_mhz ?? 0),
|
|
("cpu_bus_speed_mhz", DadosMod.CPU_bus_speed_mhz ?? 0),
|
|
|
|
// RAM
|
|
("ram_usage_percent", DadosMod.RAM_usage_percent ?? 0),
|
|
("ram_used_gb", DadosMod.RAM_used_gb ?? 0),
|
|
("ram_available_gb", DadosMod.RAM_available_gb ?? 0),
|
|
("ram_total_gb", DadosMod.RAM_total_gb ?? 0),
|
|
|
|
// GPU loads
|
|
("gpu_load_percent", DadosMod.GPU_load_percent ?? 0),
|
|
("gpu_core_load_percent", DadosMod.GPU_core_load_percent ?? 0),
|
|
("gpu_frame_buffer_load_percent", DadosMod.GPU_frame_buffer_load_percent ?? 0),
|
|
("gpu_memory_load_percent", DadosMod.GPU_memory_load_percent ?? 0),
|
|
("gpu_video_engine_load_percent", DadosMod.GPU_video_engine_load_percent ?? 0),
|
|
("gpu_bus_interface_load_percent", DadosMod.GPU_bus_interface_load_percent ?? 0),
|
|
|
|
// GPU térmico / energia / clock
|
|
("gpu_temp_core_c", DadosMod.GPU_temp_core_c ?? 0),
|
|
("gpu_power_w", DadosMod.GPU_power_w ?? 0),
|
|
("gpu_core_clock_mhz", DadosMod.GPU_core_clock_mhz ?? 0),
|
|
("gpu_memory_clock_mhz", DadosMod.GPU_memory_clock_mhz ?? 0),
|
|
("gpu_shader_clock_mhz", DadosMod.GPU_shader_clock_mhz ?? 0),
|
|
|
|
// GPU fan / PCIe
|
|
("gpu_fan_percent", DadosMod.GPU_fan_percent ?? 0),
|
|
("gpu_fan_rpm", DadosMod.GPU_fan_rpm ?? 0),
|
|
("gpu_pcie_rx", DadosMod.GPU_pcie_rx ?? 0),
|
|
("gpu_pcie_tx", DadosMod.GPU_pcie_tx ?? 0),
|
|
|
|
// VRAM
|
|
("gpu_vram_total_mb", DadosMod.GPU_vram_total_mb ?? 0),
|
|
("gpu_vram_used_mb", DadosMod.GPU_vram_used_mb ?? 0),
|
|
("gpu_vram_free_mb", DadosMod.GPU_vram_free_mb ?? 0),
|
|
("gpu_vram_used_percent", DadosMod.GPU_vram_used_percent ?? 0),
|
|
|
|
// Disco
|
|
("disk_used_space_percent", DadosMod.Disk_used_space_percent ?? 0),
|
|
|
|
// Processo C#
|
|
("process_thread_count", DadosMod.Process_thread_count),
|
|
("process_working_set_mb", DadosMod.Process_working_set_mb),
|
|
("process_private_memory_mb", DadosMod.Process_private_memory_mb),
|
|
("process_virtual_memory_mb", DadosMod.Process_virtual_memory_mb),
|
|
("process_cpu_time_total_s", DadosMod.Process_cpu_time_total_s),
|
|
|
|
// ThreadPool
|
|
("threadpool_available_worker_threads", DadosMod.ThreadPool_available_worker_threads),
|
|
("threadpool_available_completion_port_threads", DadosMod.ThreadPool_available_completion_port_threads),
|
|
("threadpool_max_worker_threads", DadosMod.ThreadPool_max_worker_threads),
|
|
("threadpool_max_completion_port_threads", DadosMod.ThreadPool_max_completion_port_threads),
|
|
("threadpool_used_worker_threads", DadosMod.ThreadPool_used_worker_threads),
|
|
("threadpool_used_completion_port_threads", DadosMod.ThreadPool_used_completion_port_threads),
|
|
("threadpool_used_worker_percent", DadosMod.ThreadPool_used_worker_percent),
|
|
("threadpool_used_completion_port_percent", DadosMod.ThreadPool_used_completion_port_percent)
|
|
);
|
|
|
|
var DadosCan = CanManager.CanService;
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Can),
|
|
("timestamp", agora),
|
|
|
|
// Estado básico
|
|
("conectado", DadosCan.IsConnected),
|
|
("iniciado", DadosCan.Iniciado),
|
|
("executando", SerialService.DispositivosMapeados.Any(x => SerialService.DispositivosCan.Contains(x.Dispositivo))),
|
|
("fila_liberada", DadosCan.FilaLiberada),
|
|
("tipo_servico", CanManager.TipoServico.ToString()),
|
|
|
|
// Frequências
|
|
("freq_tx", DadosCan.FreqTx),
|
|
("freq_rx", DadosCan.FreqRx),
|
|
("freq_tx_janela", DadosCan.FreqTxJanela),
|
|
("freq_rx_janela", DadosCan.FreqRxJanela),
|
|
("freq_tx_ewma", DadosCan.FreqTxEwma),
|
|
("freq_rx_ewma", DadosCan.FreqRxEwma),
|
|
|
|
// Timestamps absolutos para o Python
|
|
("last_tx_ms", DadosCan.UltimoTxUnixMs),
|
|
("last_rx_ms", DadosCan.UltimoRxUnixMs),
|
|
("last_ec_ms", DadosCan.UltimoErroCriticoUnixMs),
|
|
|
|
// Contadores gerais
|
|
("tx_total", DadosCan.TxTotal),
|
|
("rx_total", DadosCan.RxTotal),
|
|
("tx_falha_total", DadosCan.TxFalhaTotal),
|
|
("rx_invalido_total", DadosCan.RxInvalidoTotal),
|
|
("rx_descartado_total", DadosCan.RxDescartadoFilaCheiaTotal),
|
|
("timeouts_resposta_total", DadosCan.TimeoutsRespostaTotal),
|
|
("reconexoes_total", DadosCan.ReconexoesTotal),
|
|
|
|
// Timeouts por origem
|
|
("timeouts_por_id_total", DadosCan.TimeoutsRespostaPorId),
|
|
("timeouts_por_dispositivo_total", DadosCan.TimeoutsRespostaPorDispositivo),
|
|
("timeouts_por_chave_total", DadosCan.TimeoutsRespostaPorChave),
|
|
("pendentes_resp_por_id", DadosCan.PendentesAguardandoRespostaPorId),
|
|
|
|
// Filas
|
|
("pendentes_total", DadosCan.PendentesTotal),
|
|
("pendentes_nao_enviadas", DadosCan.PendentesNaoEnviadas),
|
|
("pendentes_aguardando_resposta", DadosCan.PendentesAguardandoResposta),
|
|
("frames_rx_fila", DadosCan.FramesRxNaFila),
|
|
("idade_max_fila_nao_enviada_ms", DadosCan.IdadeMaxFilaNaoEnviadaMs),
|
|
("pendentes_nao_enviadas_atrasadas", DadosCan.PendentesNaoEnviadasAtrasadas),
|
|
("idade_media_fila_nao_enviada_ms", DadosCan.IdadeMediaFilaNaoEnviadaMs),
|
|
|
|
// Latência
|
|
("latencia_media_ms", DadosCan.LatenciaMediaMs),
|
|
("latencia_max_ms", DadosCan.LatenciaMaxMs),
|
|
|
|
// Compatibilidade com health antigo
|
|
("erros_criticos", DadosCan.ErrosCriticos),
|
|
("last_tx", DadosCan.UltimoTx),
|
|
("last_rx", DadosCan.UltimoRx),
|
|
("last_ec", DadosCan.UltimoErroCritico),
|
|
("last_ec_motivo", DadosCan.UltimoErroCriticoMotivo)
|
|
);
|
|
|
|
double MinOrDefault(List<double> items)
|
|
{
|
|
if (items.Count == 0) return 0;
|
|
return items.Min();
|
|
}
|
|
|
|
double MaxOrDefault(List<double> items)
|
|
{
|
|
if (items.Count == 0) return 0;
|
|
return items.Max();
|
|
}
|
|
|
|
var DadosBateria = DalyBMSService.DadosLeitura;
|
|
RedisService.AtualizarCampos(
|
|
RedisService.ModKey(T_Code.Bat), // ajusta o T_Code se o seu enum tiver outro nome
|
|
("timestamp", agora),
|
|
("iniciado", DadosBateria.Iniciado),
|
|
("momento_bms", DadosBateria.Momento),
|
|
// Dados principais do pack
|
|
("tensao_total_v", DadosBateria.Pack.TensaoTotal_V),
|
|
("tensao_medida_v", DadosBateria.Pack.TensaoMedida_V),
|
|
("corrente_a", DadosBateria.Pack.Corrente_A),
|
|
("soc_pct", DadosBateria.Pack.SOC_percent),
|
|
("num_celulas_serie", DadosBateria.Pack.NumeroCelulasSerie),
|
|
("num_sensores_temp", DadosBateria.Pack.NumeroSensoresTemperatura),
|
|
("estado_geral", DadosBateria.Pack.EstadoGeral),
|
|
("charge_mos_on", DadosBateria.Pack.ChargeMosOn),
|
|
("discharge_mos_on", DadosBateria.Pack.DischargeMosOn),
|
|
("cap_restante_ah", DadosBateria.Pack.CapacidadeRestante_Ah),
|
|
("ciclos_aproximados", DadosBateria.Pack.CiclosAproximados),
|
|
// Tensão de células
|
|
("tensao_min_celula_v", DadosBateria.TensaoMin > 0 ? DadosBateria.TensaoMin : MinOrDefault(DadosBateria.Celulas.Select(c => c.Tensao_V).ToList())),
|
|
("tensao_max_celula_v", DadosBateria.TensaoMax > 0 ? DadosBateria.TensaoMax : MaxOrDefault(DadosBateria.Celulas.Select(c => c.Tensao_V).ToList())),
|
|
("idx_celula_min", DadosBateria.Celulas.FirstOrDefault(c => c.EhMinima)?.Index ?? 0),
|
|
("idx_celula_max", DadosBateria.Celulas.FirstOrDefault(c => c.EhMaxima)?.Index ?? 0),
|
|
// Temperaturas
|
|
("temp_min_c", DadosBateria.TemperaturaMin > -200 ? DadosBateria.TemperaturaMin : DadosBateria.Temperaturas.Min(t => t.Temperatura_C)),
|
|
("temp_max_c", DadosBateria.TemperaturaMax > -200 ? DadosBateria.TemperaturaMax : DadosBateria.Temperaturas.Max(t => t.Temperatura_C)),
|
|
// Balanceamento
|
|
("celulas_em_balanceamento", DadosBateria.Balanceamento.QtdeCelulasEmBalanceamento),
|
|
// Alarmes
|
|
("tem_alarme", DadosBateria.Alarmes?.TemAlarme ?? false),
|
|
("fault_code", DadosBateria.Alarmes?.FaultCode ?? 0x00),
|
|
("qtd_alarmes", DadosBateria.Alarmes?.Mensagens?.Count ?? 0),
|
|
("alarmes_texto", DadosBateria.Alarmes.Mensagens != null && DadosBateria.Alarmes.Mensagens.Count > 0 ? string.Join(" | ", DadosBateria.Alarmes.Mensagens) : string.Empty),
|
|
// Opcional: bytes brutos de alarme se quiser debugar depois
|
|
("alarm_bytes_raw", DadosBateria.Alarmes.AlarmBytesRaw != null ? BitConverter.ToString(DadosBateria.Alarmes.AlarmBytesRaw) : string.Empty)
|
|
);
|
|
|
|
var DadosTrj = _Sensoriamento?.Trajetoria;
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Trj),
|
|
("timestamp", agora),
|
|
("iniciado", DadosTrj?.AutonomiaCorredor?.Iniciado ?? false),
|
|
("liberado", DadosTrj?.AutonomiaCorredor?.Liberado ?? false),
|
|
("bateria_ok", DadosTrj?.AutonomiaCorredor?.BateriaSuficiente ?? false),
|
|
("herbicida_ok", DadosTrj?.AutonomiaCorredor?.HerbicidaSuficiente ?? false),
|
|
("motivos", DadosTrj?.AutonomiaCorredor?.Motivos ?? new List<string>())
|
|
);
|
|
|
|
double distanciaBase = -1;
|
|
if (VariaveisOperacao.PosicaoBase?.Inicializado == true && _Sensoriamento?.Gps?.Inicializado == true)
|
|
{
|
|
distanciaBase = GPSUtils.DistanciaEntrePontos(VariaveisOperacao.PosicaoBase, _Sensoriamento.Gps);
|
|
}
|
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Ipb),
|
|
("timestamp", agora),
|
|
("iniciado", Variaveis.MqttServiceBase?.StatusConexao() ?? false),
|
|
("distancia_base", distanciaBase)
|
|
);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Variaveis.MostrarLog($"[HealthWorkerService] Erro em AtualizarDadosModulos: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|