Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,386 @@
using RobotNet10.CustomConfiguration.Models;
using RobotNet10.CustomConfiguration.Services;
namespace RobotNet10.FleetManager.Services.ConfigManager;
/// <summary>
/// Service implementation for managing ACSTraffic configurations
/// </summary>
public class ACSTrafficConfig : IACSTrafficConfig
{
private readonly IConfigManager _configManager;
private readonly Logger<ACSTrafficConfig> _logger;
private readonly Lock _lockObject = new();
private bool _configLoaded = false;
/// <summary>
/// Event triggered when ACSTraffic configuration is changed/reloaded
/// </summary>
public event EventHandler? ConfigChanged;
private bool _trafficEnable = false;
private int _trafficInterval = 1000;
private string _trafficURL = string.Empty;
private Dictionary<string, string> _acsZoneMaping = [];
private Dictionary<string, string> _acsOutMaping = [];
private bool _publishEnable = false;
private string _publishURL = string.Empty;
private int _publishInterval = 1000;
private const string ACS_TRAFFIC_CONFIG_TYPE = "ACSTrafficConfig";
public ACSTrafficConfig(IConfigManager configManager, Logger<ACSTrafficConfig> logger)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_logger = logger;
// Subscribe to config changes
_configManager.ConfigChanged += OnConfigChanged;
}
public bool TrafficEnable
{
get
{
EnsureConfigLoaded();
return _trafficEnable;
}
}
public int TrafficInterval
{
get
{
EnsureConfigLoaded();
return _trafficInterval;
}
}
public string TrafficURL
{
get
{
EnsureConfigLoaded();
return _trafficURL;
}
}
public Dictionary<string, string> ACSZoneMaping
{
get
{
EnsureConfigLoaded();
return _acsZoneMaping;
}
}
public bool PublishEnable
{
get
{
EnsureConfigLoaded();
return _publishEnable;
}
}
public string PublishURL
{
get
{
EnsureConfigLoaded();
return _publishURL;
}
}
public int PublishInterval
{
get
{
EnsureConfigLoaded();
return _publishInterval;
}
}
public Dictionary<string, string> ACSOutMaping
{
get
{
EnsureConfigLoaded();
return _acsOutMaping;
}
}
private void EnsureConfigLoaded()
{
if (!_configLoaded)
{
lock (_lockObject)
{
if (!_configLoaded)
{
LoadACSTrafficConfigAsync().GetAwaiter().GetResult();
}
}
}
}
private async Task LoadACSTrafficConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(ACS_TRAFFIC_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("ACSTraffic configuration not found, using defaults");
_trafficEnable = false;
_trafficInterval = 1000;
_trafficURL = string.Empty;
_acsZoneMaping = [];
_acsOutMaping = [];
_publishEnable = false;
_publishURL = string.Empty;
_publishInterval = 1000;
}
else
{
MapConfigVariablesToProperties(configFile.Variables);
_logger.Info("ACSTraffic configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading ACSTraffic configuration, using defaults: {ex.Message}");
_trafficEnable = false;
_trafficInterval = 1000;
_trafficURL = string.Empty;
_acsZoneMaping = [];
_acsOutMaping = [];
_publishEnable = false;
_publishURL = string.Empty;
_publishInterval = 1000;
}
finally
{
_configLoaded = true;
// Trigger ConfigChanged event after config is loaded/reloaded
OnConfigChanged();
}
}
private void OnConfigChanged(object? sender, RobotNet10.CustomConfiguration.Events.ConfigChangedEventArgs e)
{
// Reload config if our config type changed
if (e.ConfigType == ACS_TRAFFIC_CONFIG_TYPE)
{
lock (_lockObject)
{
_configLoaded = false;
}
_logger.Info($"ACSTraffic configuration changed ({e.ConfigType}), will reload on next access");
}
}
/// <summary>
/// Trigger ConfigChanged event to notify subscribers that config has been reloaded
/// </summary>
private void OnConfigChanged()
{
ConfigChanged?.Invoke(this, EventArgs.Empty);
}
private void MapConfigVariablesToProperties(List<ConfigVariable> variables)
{
foreach (var variable in variables)
{
if (variable.Value == null)
continue;
try
{
switch (variable.Name)
{
case nameof(TrafficEnable):
_trafficEnable = (bool)ConvertValue(variable.Value, typeof(bool))!;
break;
case nameof(TrafficInterval):
_trafficInterval = (int)ConvertValue(variable.Value, typeof(int))!;
break;
case nameof(TrafficURL):
_trafficURL = (string)ConvertValue(variable.Value, typeof(string))!;
break;
case nameof(ACSZoneMaping):
_acsZoneMaping = (Dictionary<string, string>)ConvertValue(variable.Value, typeof(Dictionary<string, string>))!;
break;
case nameof(ACSOutMaping):
_acsOutMaping = (Dictionary<string, string>)ConvertValue(variable.Value, typeof(Dictionary<string, string>))!;
break;
case nameof(PublishEnable):
_publishEnable = (bool)ConvertValue(variable.Value, typeof(bool))!;
break;
case nameof(PublishURL):
_publishURL = (string)ConvertValue(variable.Value, typeof(string))!;
break;
case nameof(PublishInterval):
_publishInterval = (int)ConvertValue(variable.Value, typeof(int))!;
break;
default:
_logger.Warning($"Unknown variable name: {variable.Name}");
break;
}
}
catch (Exception ex)
{
_logger.Warning($"Failed to set property {variable.Name} from variable: {ex.Message}");
}
}
}
private object? ConvertValue(object? value, Type targetType)
{
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 if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
// Handle Dictionary types - try to parse from JSON string
return ConvertDictionary(value, underlyingType);
}
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;
}
}
/// <summary>
/// Convert value to Dictionary type (supports Dictionary&lt;string, string&gt;)
/// </summary>
private object ConvertDictionary(object value, Type dictionaryType)
{
try
{
// Get key and value types
var genericArgs = dictionaryType.GetGenericArguments();
var keyType = genericArgs[0];
var valueType = genericArgs[1];
// If value is already the correct Dictionary type, return it
if (dictionaryType.IsInstanceOfType(value))
{
return value;
}
// If value is Dictionary<string, object>, try to convert
if (value is Dictionary<string, object> stringDict)
{
var result = Activator.CreateInstance(dictionaryType);
var addMethod = dictionaryType.GetMethod("Add");
if (addMethod != null && result != null)
{
foreach (var kvp in stringDict)
{
var key = ConvertValue(kvp.Key, keyType);
var val = ConvertValue(kvp.Value, valueType);
addMethod.Invoke(result, [key, val]);
}
return result;
}
}
// Try parse as JSON string
var jsonString = value.ToString();
if (!string.IsNullOrEmpty(jsonString))
{
try
{
var doc = System.Text.Json.JsonDocument.Parse(jsonString);
if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object)
{
var result = Activator.CreateInstance(dictionaryType);
var addMethod = dictionaryType.GetMethod("Add");
if (addMethod != null && result != null)
{
foreach (var prop in doc.RootElement.EnumerateObject())
{
// Convert key
var key = ConvertValue(prop.Name, keyType);
// Convert value
var val = ConvertValue(prop.Value.GetString() ?? prop.Value.GetRawText(), valueType);
addMethod.Invoke(result, [key, val]);
}
return result;
}
}
}
catch (Exception ex)
{
_logger.Warning($"Failed to parse Dictionary from JSON string: {ex.Message}");
}
}
// If all else fails, return default (empty dictionary)
_logger.Warning($"Cannot convert {value.GetType()} to {dictionaryType}, using default (empty dictionary)");
return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}");
}
catch (Exception ex)
{
_logger.Warning($"Error converting Dictionary: {ex.Message}");
// Return default (empty dictionary)
return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}");
}
}
}

View File

@@ -0,0 +1,232 @@
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;
}
}
}

View File

@@ -0,0 +1,52 @@
namespace RobotNet10.FleetManager.Services.ConfigManager;
/// <summary>
/// Service for managing ACSTraffic configurations
/// </summary>
public interface IACSTrafficConfig
{
/// <summary>
/// Event triggered when ACSTraffic configuration is changed/reloaded
/// </summary>
event EventHandler? ConfigChanged;
/// <summary>
/// Enable ACS Traffic control
/// </summary>
bool TrafficEnable { get; }
/// <summary>
/// Traffic interval time in milliseconds
/// </summary>
int TrafficInterval { get; }
/// <summary>
/// Traffic URL
/// </summary>
string TrafficURL { get; }
/// <summary>
/// ACS Zone mapping dictionary
/// </summary>
Dictionary<string, string> ACSZoneMaping { get; }
/// <summary>
/// ACS Out zone mapping with node
/// </summary>
Dictionary<string, string> ACSOutMaping { get; }
/// <summary>
/// Enable publish
/// </summary>
bool PublishEnable { get; }
/// <summary>
/// Publish URL
/// </summary>
string PublishURL { get; }
/// <summary>
/// Publish interval in milliseconds
/// </summary>
int PublishInterval { get; }
}

View File

@@ -0,0 +1,20 @@
using RobotNet10.FleetManager.Services.RobotConnections.Models;
using RobotNet10.MqttConnection;
namespace RobotNet10.FleetManager.Services.ConfigManager;
/// <summary>
/// Service for managing application configurations
/// </summary>
public interface IConnectionConfig
{
/// <summary>
/// Get VDA5050 Protocol configuration
/// </summary>
VDA5050ProtocolConfig GetVDA5050Config();
/// <summary>
/// Get MQTT configuration
/// </summary>
MQTTConfig GetMqttConfig();
}

View File

@@ -0,0 +1,14 @@
using RobotNet10.FleetManager.Services.TrafficControl.Models;
namespace RobotNet10.FleetManager.Services.ConfigManager;
/// <summary>
/// Service for managing TrafficControl configurations
/// </summary>
public interface ITrafficConfig
{
/// <summary>
/// Get TrafficControl configuration
/// </summary>
TrafficControlConfig GetTrafficControlConfig();
}

View File

@@ -0,0 +1,329 @@
using System.Reflection;
using RobotNet10.CustomConfiguration.Events;
using RobotNet10.CustomConfiguration.Models;
using RobotNet10.CustomConfiguration.Services;
using RobotNet10.FleetManager.Services.TrafficControl.Models;
namespace RobotNet10.FleetManager.Services.ConfigManager;
/// <summary>
/// Service implementation for managing TrafficControl configurations
/// </summary>
public class TrafficConfig : ITrafficConfig
{
private readonly IConfigManager _configManager;
private readonly Logger<TrafficConfig> _logger;
private readonly Lock _lockObject = new();
private TrafficControlConfig? _trafficControlConfig;
private bool _configLoaded = false;
private const string CONFLICT_DETECTION_CONFIG_TYPE = "TrafficConflictDetectionConfig";
private const string BASE_HORIZON_CONFIG_TYPE = "TrafficBaseHorizonConfig";
private const string CONFLICT_RESOLUTION_CONFIG_TYPE = "TrafficConflictResolutionConfig";
private const string PRIORITY_CONFIG_TYPE = "TrafficPriorityConfig";
private const string PATH_PLANNING_CONFIG_TYPE = "TrafficPathPlanningConfig";
public TrafficConfig(IConfigManager configManager, Logger<TrafficConfig> logger)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_logger = logger;
// Subscribe to config changes
_configManager.ConfigChanged += OnConfigChanged;
}
public TrafficControlConfig GetTrafficControlConfig()
{
if (!_configLoaded)
{
lock (_lockObject)
{
if (!_configLoaded)
{
LoadTrafficControlConfigAsync().GetAwaiter().GetResult();
}
}
}
return _trafficControlConfig ?? throw new InvalidOperationException("TrafficControl configuration not loaded");
}
private async Task LoadTrafficControlConfigAsync()
{
try
{
// Load all nested configs
var conflictDetectionConfig = await LoadNestedConfigAsync<ConflictDetectionConfig>(CONFLICT_DETECTION_CONFIG_TYPE);
var baseHorizonConfig = await LoadNestedConfigAsync<BaseHorizonConfig>(BASE_HORIZON_CONFIG_TYPE);
var conflictResolutionConfig = await LoadNestedConfigAsync<ConflictResolutionConfig>(CONFLICT_RESOLUTION_CONFIG_TYPE);
var priorityConfig = await LoadNestedConfigAsync<PriorityConfig>(PRIORITY_CONFIG_TYPE);
var pathPlanningConfig = await LoadNestedConfigAsync<PathPlanningConfig>(PATH_PLANNING_CONFIG_TYPE);
// Combine into TrafficControlConfig
_trafficControlConfig = new TrafficControlConfig
{
ConflictDetection = conflictDetectionConfig,
BaseHorizon = baseHorizonConfig,
ConflictResolution = conflictResolutionConfig,
Priority = priorityConfig,
PathPlanning = pathPlanningConfig
};
_configLoaded = true;
_logger.Info("TrafficControl configuration loaded successfully");
}
catch (Exception ex)
{
_logger.Error($"Error loading TrafficControl configuration, using defaults: {ex.Message}");
_trafficControlConfig = new TrafficControlConfig();
_configLoaded = true;
}
}
private async Task<T> LoadNestedConfigAsync<T>(string configType) where T : new()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(configType);
if (configFile == null)
{
_logger.Warning($"{configType} configuration not found, using defaults");
return new T();
}
var config = MapConfigVariablesToObject<T>(configFile.Variables);
_logger.Info($"{configType} configuration loaded successfully");
return config;
}
catch (Exception ex)
{
_logger.Warning($"Error loading {configType} configuration, using defaults: {ex.Message}");
return new T();
}
}
private void OnConfigChanged(object? sender, ConfigChangedEventArgs e)
{
// Reload config if any of our config types changed
if (e.ConfigType == CONFLICT_DETECTION_CONFIG_TYPE ||
e.ConfigType == BASE_HORIZON_CONFIG_TYPE ||
e.ConfigType == CONFLICT_RESOLUTION_CONFIG_TYPE ||
e.ConfigType == PRIORITY_CONFIG_TYPE ||
e.ConfigType == PATH_PLANNING_CONFIG_TYPE)
{
lock (_lockObject)
{
_configLoaded = false;
_trafficControlConfig = null;
}
_logger.Info($"TrafficControl configuration changed ({e.ConfigType}), 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);
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)
{
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 if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
// Handle Dictionary types - try to parse from JSON string
return ConvertDictionary(value, underlyingType);
}
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;
}
}
/// <summary>
/// Convert value to Dictionary type (supports Dictionary&lt;NavigationType, PathPlanningMethod&gt;)
/// </summary>
private object ConvertDictionary(object value, Type dictionaryType)
{
try
{
// Get key and value types
var genericArgs = dictionaryType.GetGenericArguments();
var keyType = genericArgs[0];
var valueType = genericArgs[1];
// If value is already the correct Dictionary type, return it
if (dictionaryType.IsInstanceOfType(value))
{
return value;
}
// If value is Dictionary<string, object>, try to convert
if (value is Dictionary<string, object> stringDict)
{
var result = Activator.CreateInstance(dictionaryType);
var addMethod = dictionaryType.GetMethod("Add");
if (addMethod != null && result != null)
{
foreach (var kvp in stringDict)
{
var key = ConvertValue(kvp.Key, keyType);
var val = ConvertValue(kvp.Value, valueType);
addMethod.Invoke(result, [key, val]);
}
return result;
}
}
// Try parse as JSON string
var jsonString = value.ToString();
if (!string.IsNullOrEmpty(jsonString))
{
try
{
var doc = System.Text.Json.JsonDocument.Parse(jsonString);
if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object)
{
var result = Activator.CreateInstance(dictionaryType);
var addMethod = dictionaryType.GetMethod("Add");
if (addMethod != null && result != null)
{
foreach (var prop in doc.RootElement.EnumerateObject())
{
// Convert key (e.g., "Differential" -> NavigationType.Differential)
var key = ConvertEnumKey(prop.Name, keyType);
// Convert value (e.g., "Basic" -> PathPlanningMethod.Basic)
var val = ConvertEnumValue(prop.Value.GetString() ?? prop.Value.GetRawText(), valueType);
addMethod.Invoke(result, [key, val]);
}
return result;
}
}
}
catch (Exception ex)
{
_logger.Warning($"Failed to parse Dictionary from JSON string: {ex.Message}");
}
}
// If all else fails, return default (empty dictionary)
_logger.Warning($"Cannot convert {value.GetType()} to {dictionaryType}, using default (empty dictionary)");
return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}");
}
catch (Exception ex)
{
_logger.Warning($"Error converting Dictionary: {ex.Message}");
// Return default (empty dictionary)
return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}");
}
}
/// <summary>
/// Convert string to enum key (e.g., "Differential" -> NavigationType.Differential)
/// </summary>
private static object ConvertEnumKey(string keyString, Type enumType)
{
if (enumType.IsEnum)
{
if (Enum.TryParse(enumType, keyString, true, out var enumValue))
{
return enumValue;
}
}
// If not enum, try standard conversion
return Convert.ChangeType(keyString, enumType);
}
/// <summary>
/// Convert string to enum value (e.g., "Basic" -> PathPlanningMethod.Basic)
/// </summary>
private static object ConvertEnumValue(string valueString, Type enumType)
{
if (enumType.IsEnum)
{
if (Enum.TryParse(enumType, valueString, true, out var enumValue))
{
return enumValue;
}
}
// If not enum, try standard conversion
return Convert.ChangeType(valueString, enumType);
}
}