agrobot_base/AgroBase/AgroBase/Services/Operadores/CameraWorkerService.cs

405 lines
12 KiB
C#

using AgroBase.Models;
using AgroBase.Models.Operadores;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static AgroBase.Models.Enums;
using static AgroBase.Models.Operadores.OperadoresModels;
namespace AgroBase.Services.Operadores
{
public class CameraWorkerService : IWorkerModel
{
public CameraWorkerModel DadosLeitura = new CameraWorkerModel();
public SaudeWorkerModel Saude { get; set; } = new SaudeWorkerModel();
private static readonly object _cameraLock = new object();
private static readonly object _refreshLock = new object();
private static readonly JsonSerializerSettings _jsonSettings =
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
Error = (sender, args) =>
{
args.ErrorContext.Handled = true;
}
};
private static List<CameraWorkerItemModel> CamerasConectadas = CriarListaVaziaComPlaceholder();
private static DateTime _ultimoRefreshUtc = DateTime.MinValue;
private static bool _refreshEmAndamento;
/*
* Pode chamar AtualizarDadosCameras() em tmrLeitura_Tick sem medo.
* Se o tick estiver em 100 ms, este throttle evita martelar Redis.
*/
private static readonly TimeSpan IntervaloMinimoRefresh = TimeSpan.FromMilliseconds(500);
private bool DebugMode = false;
public static List<CameraWorkerItemModel> ListaCameras
{
get
{
lock (_cameraLock)
{
return CamerasConectadas.Skip(1).ToList();
}
}
}
public static OAKCameraModel CameraCaminho
{
get
{
CameraWorkerItemModel item = ListaCameras.FirstOrDefault(x => x.dispositivo == T_Code.Snr);
return item?.ToOAK();
}
}
public static List<OAKCameraModel> CamerasSolo
{
get
{
return ListaCameras
.Where(x => x.dispositivo == T_Code.Cam)
.Select(x => x.ToOAK())
.Where(x => x != null)
.ToList();
}
}
public void MostrarLog(string message, int limite = 500)
{
if (!DebugMode)
return;
message = message ?? string.Empty;
Console.WriteLine(
"[CameraWorker] " +
message.Substring(
0,
Math.Min(limite, message.Length)
) +
(message.Length > limite ? "..." : "")
);
}
public async Task<bool> IniciarProcessamento()
{
RedisService.Subscribe(CmdKey.CameraWorkerTx, RedisCallback);
DadosLeitura.IniciadoEm = DateTime.Now;
DadosLeitura.Iniciado = true;
bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000);
if (pronto())
{
MostrarLog("Processamento iniciado com sucesso");
return true;
}
MostrarLog("Timeout ao iniciar script");
DadosLeitura.ReiniciarEstado();
return false;
}
public void RedisCallback(string mensagem)
{
try
{
if (string.IsNullOrWhiteSpace(mensagem))
return;
var comando = JsonConvert.DeserializeObject<ComandoRedis>(mensagem, _jsonSettings);
if (comando == null)
return;
var cmd = (CameraWorkerCommandType)comando.cmd;
switch (cmd)
{
case CameraWorkerCommandType.ScriptCarregado:
DadosLeitura.Pronto = true;
DadosLeitura.ProntoEm = DateTime.Now;
MostrarLog("Comando recebido: cmd = " + cmd);
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()
{
Saude = OperadoresModels.AtualizarSaude(CtxKey.DadosCameraWorker);
}
/// <summary>
/// Lê câmeras diretamente do Redis e retorna uma lista nova.
/// Não atualiza o snapshot global. Para runtime, use AtualizarDadosCameras().
/// </summary>
public static List<CameraWorkerItemModel> GetListaCameras()
{
return LerCamerasDoRedis();
}
/// <summary>
/// Atualiza o snapshot global das câmeras.
/// Pode ser chamado no tmrLeitura_Tick. Internamente possui throttle.
/// </summary>
public static void AtualizarDadosCameras()
{
AtualizarDadosCameras(false);
}
public static void AtualizarDadosCameras(bool forcar)
{
DateTime agoraUtc = DateTime.UtcNow;
if (!forcar && agoraUtc - _ultimoRefreshUtc < IntervaloMinimoRefresh)
{
return;
}
lock (_refreshLock)
{
if (_refreshEmAndamento)
return;
if (!forcar && DateTime.UtcNow - _ultimoRefreshUtc < IntervaloMinimoRefresh)
{
return;
}
_refreshEmAndamento = true;
}
try
{
List<CameraWorkerItemModel> novaLista = LerCamerasDoRedis();
lock (_cameraLock)
{
CamerasConectadas = novaLista;
_ultimoRefreshUtc = DateTime.UtcNow;
}
}
catch (Exception ex)
{
Variaveis.MostrarLog("[CameraWorkerService.AtualizarDadosCameras] Erro ao atualizar câmeras: " + ex.Message);
}
finally
{
lock (_refreshLock)
{
_refreshEmAndamento = false;
}
}
}
private static OAKCameraModel AtualizarDadosCamera(string mx_id)
{
if (string.IsNullOrWhiteSpace(mx_id))
return null;
CameraWorkerItemModel camera = LerCameraDoRedis(mx_id);
if (camera == null ||
string.IsNullOrWhiteSpace(camera.mx_id))
{
return null;
}
lock (_cameraLock)
{
int index =
CamerasConectadas.FindIndex(
x => x.mx_id == mx_id
);
if (index >= 0)
CamerasConectadas[index] = camera;
else
CamerasConectadas.Add(camera);
OrdenarCamerasMantendoPlaceholder(CamerasConectadas);
}
return camera.ToOAK();
}
private static List<CameraWorkerItemModel> LerCamerasDoRedis()
{
var resultado = CriarListaVaziaComPlaceholder();
string camerasStr = RedisService.Get(CtxKey.DadosCameras);
if (string.IsNullOrWhiteSpace(camerasStr))
return resultado;
Dictionary<string, CameraWorkerItemModel> cameras;
try
{
cameras =
JsonConvert.DeserializeObject<
Dictionary<string, CameraWorkerItemModel>
>(
camerasStr,
_jsonSettings
);
}
catch (Exception ex)
{
Console.WriteLine(
"[CameraWorkerService] Falha ao ler índice de câmeras: " +
ex.Message
);
return resultado;
}
if (cameras == null || cameras.Count == 0)
return resultado;
foreach (string key in cameras.Keys.ToList())
{
CameraWorkerItemModel camera = LerCameraDoRedis(key);
if (camera == null)
continue;
if (string.IsNullOrWhiteSpace(camera.mx_id))
continue;
resultado.Add(camera);
}
OrdenarCamerasMantendoPlaceholder(resultado);
return resultado;
}
private static CameraWorkerItemModel LerCameraDoRedis(string redisIdOuMxId)
{
if (string.IsNullOrWhiteSpace(redisIdOuMxId))
return null;
string cameraStr = RedisService.Get(RedisService.CamKey(redisIdOuMxId));
if (string.IsNullOrWhiteSpace(cameraStr))
return null;
CameraWorkerItemModel camera;
try
{
camera = JsonConvert.DeserializeObject<CameraWorkerItemModel>(cameraStr, _jsonSettings);
}
catch (Exception ex)
{
Variaveis.MostrarLog("[CameraWorkerService.LerCameraDoRedis] Falha ao ler câmera " + redisIdOuMxId + ": " + ex.Message);
Variaveis.MostrarLog("[CameraWorkerService.LerCameraDoRedis] JSON câmera: " + cameraStr);
return null;
}
if (camera == null || string.IsNullOrWhiteSpace(camera.mx_id))
{
return null;
}
string imuStr = RedisService.Get(RedisService.CamImuKey(camera.mx_id));
if (!string.IsNullOrWhiteSpace(imuStr))
{
try
{
var imu = JsonConvert.DeserializeObject<OAKImuModel>(imuStr, _jsonSettings);
camera.imu = imu?.dados;
}
catch (Exception ex)
{
Variaveis.MostrarLog("[CameraWorkerService.LerCameraDoRedis] Falha ao ler IMU da câmera " + camera.mx_id + ": " + ex.Message);
}
}
return camera;
}
private static List<CameraWorkerItemModel> CriarListaVaziaComPlaceholder()
{
return new List<CameraWorkerItemModel>
{
new CameraWorkerItemModel
{
dispositivo = T_Code.Vzo,
modelo = "Sem Video"
}
};
}
private static void OrdenarCamerasMantendoPlaceholder(List<CameraWorkerItemModel> cameras)
{
if (cameras == null || cameras.Count <= 1)
return;
CameraWorkerItemModel placeholder = cameras.FirstOrDefault(x => x.dispositivo == T_Code.Vzo && x.modelo == "Sem Video");
var reais =
cameras
.Where(x => !(x.dispositivo == T_Code.Vzo && x.modelo == "Sem Video"))
.OrderBy(PrioridadeCamera)
.ThenBy(x => x.mx_id)
.ToList();
cameras.Clear();
if (placeholder != null)
cameras.Add(placeholder);
else
cameras.Add(CriarListaVaziaComPlaceholder()[0]);
cameras.AddRange(reais);
}
private static int PrioridadeCamera(CameraWorkerItemModel camera)
{
if (camera == null)
return 99;
if (camera.dispositivo == T_Code.Snr)
return 0;
if (camera.dispositivo == T_Code.Cam)
return 1;
return 2;
}
}
}