233 lines
8.5 KiB
C#
233 lines
8.5 KiB
C#
using System.Reflection;
|
|
using RobotNet10.CustomConfiguration.Events;
|
|
using RobotNet10.CustomConfiguration.Models;
|
|
using RobotNet10.CustomConfiguration.Services;
|
|
using RobotNet10.FleetManager.Services.RobotConnections.Models;
|
|
using RobotNet10.MqttConnection;
|
|
|
|
namespace RobotNet10.FleetManager.Services.ConfigManager;
|
|
|
|
/// <summary>
|
|
/// Service implementation for managing application configurations
|
|
/// </summary>
|
|
public class ConnectionConfig : IConnectionConfig
|
|
{
|
|
private readonly IConfigManager _configManager;
|
|
private readonly Logger<ConnectionConfig> _logger;
|
|
|
|
private readonly Lock _lockObject = new();
|
|
private VDA5050ProtocolConfig? _vda5050Config;
|
|
private MQTTConfig? _mqttConfig;
|
|
private bool _vda5050ConfigLoaded = false;
|
|
private bool _mqttConfigLoaded = false;
|
|
|
|
private const string VDA5050_PROTOCOL_CONFIG_TYPE = "VDA5050ProtocolConfig";
|
|
private const string MQTT_CONFIG_TYPE = "MQTTConfig";
|
|
|
|
public ConnectionConfig(IConfigManager configManager, Logger<ConnectionConfig> logger)
|
|
{
|
|
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
|
|
_logger = logger;
|
|
|
|
// Subscribe to config changes
|
|
_configManager.ConfigChanged += OnConfigChanged;
|
|
}
|
|
|
|
public VDA5050ProtocolConfig GetVDA5050Config()
|
|
{
|
|
if (!_vda5050ConfigLoaded)
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
if (!_vda5050ConfigLoaded)
|
|
{
|
|
LoadVDA5050ConfigAsync().GetAwaiter().GetResult();
|
|
}
|
|
}
|
|
}
|
|
return _vda5050Config ?? throw new InvalidOperationException("VDA5050 configuration not loaded");
|
|
}
|
|
|
|
public MQTTConfig GetMqttConfig()
|
|
{
|
|
if (!_mqttConfigLoaded)
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
if (!_mqttConfigLoaded)
|
|
{
|
|
LoadMqttConfigAsync().GetAwaiter().GetResult();
|
|
}
|
|
}
|
|
}
|
|
return _mqttConfig ?? throw new InvalidOperationException("MQTT configuration not loaded");
|
|
}
|
|
|
|
private async Task LoadVDA5050ConfigAsync()
|
|
{
|
|
try
|
|
{
|
|
var configFile = await _configManager.GetConfigByTypeAsync(VDA5050_PROTOCOL_CONFIG_TYPE);
|
|
|
|
if (configFile == null)
|
|
{
|
|
_logger.Warning("VDA5050 Protocol configuration not found, using defaults");
|
|
_vda5050Config = new VDA5050ProtocolConfig
|
|
{
|
|
Manufacturer = "RobotNet",
|
|
Version = "2.1.0",
|
|
TopicPrefix = "uagv/v2"
|
|
};
|
|
}
|
|
else
|
|
{
|
|
_vda5050Config = MapConfigVariablesToObject<VDA5050ProtocolConfig>(configFile.Variables);
|
|
_logger.Info("VDA5050 Protocol configuration loaded successfully");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error($"Error loading VDA5050 Protocol configuration, using defaults: {ex.Message}");
|
|
_vda5050Config = new VDA5050ProtocolConfig
|
|
{
|
|
Manufacturer = "RobotNet",
|
|
Version = "2.1.0",
|
|
TopicPrefix = "uagv/v2"
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_vda5050ConfigLoaded = true;
|
|
}
|
|
}
|
|
|
|
private async Task LoadMqttConfigAsync()
|
|
{
|
|
var configFile = await _configManager.GetConfigByTypeAsync(MQTT_CONFIG_TYPE) ?? throw new InvalidOperationException($"MQTT configuration (ConfigType: {MQTT_CONFIG_TYPE}) not found");
|
|
_mqttConfig = MapConfigVariablesToObject<MQTTConfig>(configFile.Variables);
|
|
|
|
if (string.IsNullOrEmpty(_mqttConfig.Host))
|
|
{
|
|
throw new InvalidOperationException("MQTT configuration: Host is required");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(_mqttConfig.ClientId))
|
|
{
|
|
throw new InvalidOperationException("MQTT configuration: ClientId is required");
|
|
}
|
|
|
|
_mqttConfigLoaded = true;
|
|
_logger.Info("MQTT configuration loaded successfully");
|
|
}
|
|
|
|
private void OnConfigChanged(object? sender, ConfigChangedEventArgs e)
|
|
{
|
|
// Reload config if it's one of our config types
|
|
if (e.ConfigType == VDA5050_PROTOCOL_CONFIG_TYPE)
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
_vda5050ConfigLoaded = false;
|
|
_vda5050Config = null;
|
|
}
|
|
_logger.Info("VDA5050 Protocol configuration changed, will reload on next access");
|
|
}
|
|
else if (e.ConfigType == MQTT_CONFIG_TYPE)
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
_mqttConfigLoaded = false;
|
|
_mqttConfig = null;
|
|
}
|
|
_logger.Info("MQTT configuration changed, will reload on next access");
|
|
}
|
|
}
|
|
|
|
private T MapConfigVariablesToObject<T>(List<ConfigVariable> variables) where T : new()
|
|
{
|
|
var obj = new T();
|
|
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);
|
|
|
|
foreach (var property in properties)
|
|
{
|
|
// Find variable by exact name match (case-sensitive)
|
|
var variable = variables.FirstOrDefault(v => v.Name == property.Name);
|
|
|
|
if (variable != null && variable.Value != null)
|
|
{
|
|
try
|
|
{
|
|
var convertedValue = ConvertValue(variable.Value, property.PropertyType, variable.Type);
|
|
property.SetValue(obj, convertedValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Warning($"Failed to set property {property.Name} from variable {variable.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
private object? ConvertValue(object? value, Type targetType, ConfigVariableType variableType)
|
|
{
|
|
if (value == null)
|
|
return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
|
|
|
|
// If value is already of the correct type, return it
|
|
if (targetType.IsInstanceOfType(value))
|
|
return value;
|
|
|
|
try
|
|
{
|
|
// Handle nullable types
|
|
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
|
|
|
// Convert based on target type
|
|
if (underlyingType == typeof(string))
|
|
{
|
|
return value.ToString() ?? string.Empty;
|
|
}
|
|
else if (underlyingType == typeof(int))
|
|
{
|
|
if (value is int i) return i;
|
|
if (int.TryParse(value.ToString(), out var parsed)) return parsed;
|
|
if (value is double d) return (int)d;
|
|
throw new InvalidCastException($"Cannot convert {value.GetType()} to int");
|
|
}
|
|
else if (underlyingType == typeof(double))
|
|
{
|
|
if (value is double d) return d;
|
|
if (double.TryParse(value.ToString(), out var parsed)) return parsed;
|
|
if (value is int i) return i;
|
|
throw new InvalidCastException($"Cannot convert {value.GetType()} to double");
|
|
}
|
|
else if (underlyingType == typeof(bool))
|
|
{
|
|
if (value is bool b) return b;
|
|
if (bool.TryParse(value.ToString(), out var parsed)) return parsed;
|
|
// Handle numeric values: 0/1, "0"/"1", etc.
|
|
if (value.ToString()?.Trim() == "0" || value.ToString()?.Trim().ToLower() == "false") return false;
|
|
if (value.ToString()?.Trim() == "1" || value.ToString()?.Trim().ToLower() == "true") return true;
|
|
throw new InvalidCastException($"Cannot convert {value.GetType()} to bool");
|
|
}
|
|
else if (underlyingType.IsEnum)
|
|
{
|
|
var enumValue = Enum.Parse(underlyingType, value.ToString() ?? "", true);
|
|
return enumValue;
|
|
}
|
|
else
|
|
{
|
|
// Try standard conversion
|
|
return Convert.ChangeType(value, underlyingType);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Warning($"Error converting value {value} of type {value.GetType()} to {targetType}: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
}
|