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; /// /// Service implementation for managing application configurations /// public class ConnectionConfig : IConnectionConfig { private readonly IConfigManager _configManager; private readonly Logger _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 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(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(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(List 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; } } }