Files
I150/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp/Services/ConfigManager/ConnectionConfig.cs
2026-07-03 16:37:12 +07:00

370 lines
14 KiB
C#

using RobotNet10.CustomConfiguration.Events;
using RobotNet10.CustomConfiguration.Models;
using RobotNet10.CustomConfiguration.Services;
using RobotNet10.MqttConnection;
using RobotNet10.RobotApp.Services.Robot.Connection.Models;
using System.Reflection;
using System.Text.Json;
namespace RobotNet10.RobotApp.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";
private const string VDA5050_CONFIG_FILE_NAME = "VDA5050ProtocolConfig.config.json";
private const string MQTT_CONFIG_FILE_NAME = "MQTTConfig.config.json";
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)
{
if (!TryLoadVda5050ConfigFromLocalFile(out var vdaConfig))
{
_logger.Warning("VDA5050 Protocol configuration not found, using defaults");
_vda5050Config = new VDA5050ProtocolConfig
{
Manufacturer = "RobotNet",
Version = "2.1.0",
TopicPrefix = "uagv/v2",
SerialNumber = "Robot-Dev"
};
}
else
{
_vda5050Config = vdaConfig;
}
}
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);
if (configFile is not null)
{
_mqttConfig = MapConfigVariablesToObject<MQTTConfig>(configFile.Variables);
}
else if (TryLoadMqttConfigFromLocalFile(out var localMqttConfig))
{
_mqttConfig = localMqttConfig;
}
else
{
throw new InvalidOperationException($"MQTT configuration (ConfigType: {MQTT_CONFIG_TYPE}) not found");
}
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 bool TryLoadVda5050ConfigFromLocalFile(out VDA5050ProtocolConfig config)
{
config = new VDA5050ProtocolConfig();
foreach (var path in GetLocalConfigCandidates(VDA5050_CONFIG_FILE_NAME))
{
try
{
if (!File.Exists(path))
{
continue;
}
using var doc = JsonDocument.Parse(File.ReadAllText(path));
config = new VDA5050ProtocolConfig
{
Manufacturer = GetStringVariable(doc.RootElement, "Manufacturer", "RobotNet"),
Version = GetStringVariable(doc.RootElement, "Version", "2.1.0"),
TopicPrefix = GetStringVariable(doc.RootElement, "TopicPrefix", "uagv/v2"),
SerialNumber = GetStringVariable(doc.RootElement, "SerialNumber", "Robot-Dev")
};
_logger.Info($"VDA5050 Protocol configuration loaded from local file: {path}");
return true;
}
catch (Exception ex)
{
_logger.Warning($"Failed reading VDA5050 config file '{path}': {ex.Message}");
}
}
return false;
}
private bool TryLoadMqttConfigFromLocalFile(out MQTTConfig config)
{
config = new MQTTConfig();
foreach (var path in GetLocalConfigCandidates(MQTT_CONFIG_FILE_NAME))
{
try
{
if (!File.Exists(path))
{
continue;
}
using var doc = JsonDocument.Parse(File.ReadAllText(path));
config = new MQTTConfig
{
Host = GetStringVariable(doc.RootElement, "Host", string.Empty),
Port = GetIntVariable(doc.RootElement, "Port", 1883),
ClientId = GetStringVariable(doc.RootElement, "ClientId", string.Empty),
Username = GetStringVariable(doc.RootElement, "Username", string.Empty),
Password = GetStringVariable(doc.RootElement, "Password", string.Empty),
EnablePassword = GetBoolVariable(doc.RootElement, "EnablePassword", false),
EnableTls = GetBoolVariable(doc.RootElement, "EnableTls", false),
EnableCA = GetBoolVariable(doc.RootElement, "EnableCA", false),
CaCertificatesPath = GetStringVariable(doc.RootElement, "CaCertificatesPath", string.Empty),
ClientCertificatePath = GetStringVariable(doc.RootElement, "ClientCertificatePath", string.Empty),
ClientKeyPath = GetStringVariable(doc.RootElement, "ClientKeyPath", string.Empty),
PublishRepeat = GetIntVariable(doc.RootElement, "PublishRepeat", 2)
};
_logger.Info($"MQTT configuration loaded from local file: {path}");
return true;
}
catch (Exception ex)
{
_logger.Warning($"Failed reading MQTT config file '{path}': {ex.Message}");
}
}
return false;
}
private static IEnumerable<string> GetLocalConfigCandidates(string fileName)
{
yield return Path.Combine(Directory.GetCurrentDirectory(), "RobotAppCustomConfigs", "configs", fileName);
yield return Path.Combine(AppContext.BaseDirectory, "RobotAppCustomConfigs", "configs", fileName);
}
private static string GetStringVariable(JsonElement root, string variableName, string fallback)
{
if (!root.TryGetProperty("variables", out var variables) || variables.ValueKind != JsonValueKind.Array)
{
return fallback;
}
foreach (var variable in variables.EnumerateArray())
{
if (!variable.TryGetProperty("name", out var nameElement) || !string.Equals(nameElement.GetString(), variableName, StringComparison.Ordinal))
{
continue;
}
if (variable.TryGetProperty("value", out var valueElement) && valueElement.ValueKind != JsonValueKind.Null)
{
return valueElement.ToString() ?? fallback;
}
}
return fallback;
}
private static int GetIntVariable(JsonElement root, string variableName, int fallback)
{
var rawValue = GetStringVariable(root, variableName, fallback.ToString());
return int.TryParse(rawValue, out var value) ? value : fallback;
}
private static bool GetBoolVariable(JsonElement root, string variableName, bool fallback)
{
var rawValue = GetStringVariable(root, variableName, fallback.ToString());
return bool.TryParse(rawValue, out var value) ? value : fallback;
}
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;
}
}
}