using RobotNet10.CustomConfiguration.Events;
using RobotNet10.CustomConfiguration.Models;
using RobotNet10.CustomConfiguration.Services;
using RobotNet10.RobotApp.Services.Navigation;
using RobotNet10.RobotApp.Services.Navigation.CSharp;
using System.Reflection;
namespace RobotNet10.RobotApp.Services.ConfigManager;
///
/// Service implementation for managing navigation configurations
///
public class NavigationConfig : INavigationConfig
{
private readonly IConfigManager _configManager;
private readonly Logger _logger;
private readonly Lock _lockObject = new();
// Config objects
private PurePursuitConfig? _purePursuitConfig;
private VelocitySignalProcessingConfig? _velocitySignalProcessingConfig;
private VelocityEstimatorConfig? _velocityEstimatorConfig;
private MotorDynamicsConfig? _motorDynamicsConfig;
private PIDConfig? _movePidConfig;
private PIDConfig? _rotatePidConfig;
private Navigation.NavigationConfig? _navigationConfig;
private RobotNet10.NavigationTune.Shared.Models.StanleyConfig? _stanleyConfig;
private DockToConfig? _dockToConfig;
private DockToConfig? _moveStraightConfig;
private LocalPlannerConfig? _localPlannerConfig;
// Load flags
private bool _purePursuitConfigLoaded = false;
private bool _velocitySignalProcessingConfigLoaded = false;
private bool _velocityEstimatorConfigLoaded = false;
private bool _motorDynamicsConfigLoaded = false;
private bool _movePidConfigLoaded = false;
private bool _rotatePidConfigLoaded = false;
private bool _navigationConfigLoaded = false;
private bool _stanleyConfigLoaded = false;
private bool _dockToConfigLoaded = false;
private bool _moveStraightConfigLoaded = false;
private bool _localPlannerConfigLoaded = false;
// Config type constants
private const string PURE_PURSUIT_CONFIG_TYPE = "PurePursuitConfig";
private const string VELOCITY_SIGNAL_PROCESSING_CONFIG_TYPE = "VelocitySignalProcessingConfig";
private const string VELOCITY_ESTIMATOR_CONFIG_TYPE = "VelocityEstimatorConfig";
private const string MOTOR_DYNAMICS_CONFIG_TYPE = "MotorDynamicsConfig";
private const string MOVE_PID_CONFIG_TYPE = "MovePidConfig";
private const string ROTATE_PID_CONFIG_TYPE = "RotatePidConfig";
private const string NAVIGATION_CONFIG_TYPE = "NavigationConfig";
private const string STANLEY_CONFIG_TYPE = "StanleyConfig";
private const string DOCK_TO_CONFIG_TYPE = "DockToConfig";
private const string MOVE_STRAIGHT_CONFIG_TYPE = "MoveStraightConfig";
private const string LOCAL_PLANNER_CONFIG_TYPE = "LocalPlannerConfig";
public NavigationConfig(IConfigManager configManager, Logger logger)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_logger = logger;
// Subscribe to config changes
_configManager.ConfigChanged += OnConfigChanged;
}
public PurePursuitConfig GetPurepursuitConfig()
{
if (!_purePursuitConfigLoaded)
{
lock (_lockObject)
{
if (!_purePursuitConfigLoaded)
{
LoadPurePursuitConfigAsync().GetAwaiter().GetResult();
}
}
}
return _purePursuitConfig ?? throw new InvalidOperationException("PurePursuit configuration not loaded");
}
public VelocitySignalProcessingConfig GetVelocitySignalProcessingConfig()
{
if (!_velocitySignalProcessingConfigLoaded)
{
lock (_lockObject)
{
if (!_velocitySignalProcessingConfigLoaded)
{
LoadVelocitySignalProcessingConfigAsync().GetAwaiter().GetResult();
}
}
}
return _velocitySignalProcessingConfig ?? throw new InvalidOperationException("VelocitySignalProcessing configuration not loaded");
}
public VelocityEstimatorConfig GetVelocityEstimatorConfig()
{
if (!_velocityEstimatorConfigLoaded)
{
lock (_lockObject)
{
if (!_velocityEstimatorConfigLoaded)
{
LoadVelocityEstimatorConfigAsync().GetAwaiter().GetResult();
}
}
}
return _velocityEstimatorConfig ?? throw new InvalidOperationException("VelocityEstimator configuration not loaded");
}
public MotorDynamicsConfig GetMotorDynamicsConfig()
{
if (!_motorDynamicsConfigLoaded)
{
lock (_lockObject)
{
if (!_motorDynamicsConfigLoaded)
{
LoadMotorDynamicsConfigAsync().GetAwaiter().GetResult();
}
}
}
return _motorDynamicsConfig ?? throw new InvalidOperationException("MotorDynamics configuration not loaded");
}
public PIDConfig GetMovePidConfig()
{
if (!_movePidConfigLoaded)
{
lock (_lockObject)
{
if (!_movePidConfigLoaded)
{
LoadMovePidConfigAsync().GetAwaiter().GetResult();
}
}
}
return _movePidConfig ?? throw new InvalidOperationException("MovePid configuration not loaded");
}
public PIDConfig GetRotatePidConfig()
{
if (!_rotatePidConfigLoaded)
{
lock (_lockObject)
{
if (!_rotatePidConfigLoaded)
{
LoadRotatePidConfigAsync().GetAwaiter().GetResult();
}
}
}
return _rotatePidConfig ?? throw new InvalidOperationException("RotatePid configuration not loaded");
}
public Navigation.NavigationConfig GetNavigationConfig()
{
if (!_navigationConfigLoaded)
{
lock (_lockObject)
{
if (!_navigationConfigLoaded)
{
LoadNavigationConfigAsync().GetAwaiter().GetResult();
}
}
}
return _navigationConfig ?? throw new InvalidOperationException("Navigation configuration not loaded");
}
public RobotNet10.NavigationTune.Shared.Models.StanleyConfig GetStanleyConig()
{
if (!_stanleyConfigLoaded)
{
lock (_lockObject)
{
if (!_stanleyConfigLoaded)
{
LoadStanleyConfigAsync().GetAwaiter().GetResult();
}
}
}
return _stanleyConfig ?? throw new InvalidOperationException("Stanley configuration not loaded");
}
private async Task LoadPurePursuitConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(PURE_PURSUIT_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("PurePursuit configuration not found, using defaults");
_purePursuitConfig = new PurePursuitConfig
{
LookaheadMin = 0.3,
Kdd = 1.0,
LookaheadMax = 2.0,
CurvatureGain = 1.0,
WaypointTolerance = 0.1,
MaxAngularVelocity = 1.5,
ResolutionSplit = 0.05f
};
}
else
{
_purePursuitConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("PurePursuit configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading PurePursuit configuration, using defaults: {ex.Message}");
_purePursuitConfig = new PurePursuitConfig
{
LookaheadMin = 0.3,
Kdd = 1.0,
LookaheadMax = 2.0,
CurvatureGain = 1.0,
WaypointTolerance = 0.1,
MaxAngularVelocity = 1.5,
ResolutionSplit = 0.05f
};
}
finally
{
_purePursuitConfigLoaded = true;
}
}
private async Task LoadVelocitySignalProcessingConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(VELOCITY_SIGNAL_PROCESSING_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("VelocitySignalProcessing configuration not found, using defaults");
_velocitySignalProcessingConfig = new VelocitySignalProcessingConfig
{
AlphaFilter = 0.3,
NoiseThreshold = 0.5
};
}
else
{
_velocitySignalProcessingConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("VelocitySignalProcessing configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading VelocitySignalProcessing configuration, using defaults: {ex.Message}");
_velocitySignalProcessingConfig = new VelocitySignalProcessingConfig
{
AlphaFilter = 0.3,
NoiseThreshold = 0.5
};
}
finally
{
_velocitySignalProcessingConfigLoaded = true;
}
}
private async Task LoadVelocityEstimatorConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(VELOCITY_ESTIMATOR_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("VelocityEstimator configuration not found, using defaults");
_velocityEstimatorConfig = new VelocityEstimatorConfig
{
MinBlendRatio = 0.15f,
MaxBlendRatio = 0.8f,
DefaultBlendRatio = 0.6,
GoodTrackingThreshold = 0.12f,
ModerateTrackingThreshold = 0.3,
GoodTrackingBlend = 0.7,
ModerateTrackingBlend = 0.5,
PoorTrackingBlend = 0.25f,
ConfidenceDecayRate = 0.95f,
MinConfidence = 0.3
};
}
else
{
_velocityEstimatorConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("VelocityEstimator configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading VelocityEstimator configuration, using defaults: {ex.Message}");
_velocityEstimatorConfig = new VelocityEstimatorConfig
{
MinBlendRatio = 0.15f,
MaxBlendRatio = 0.8f,
DefaultBlendRatio = 0.6,
GoodTrackingThreshold = 0.12f,
ModerateTrackingThreshold = 0.3,
GoodTrackingBlend = 0.7,
ModerateTrackingBlend = 0.5,
PoorTrackingBlend = 0.25f,
ConfidenceDecayRate = 0.95f,
MinConfidence = 0.3
};
}
finally
{
_velocityEstimatorConfigLoaded = true;
}
}
private async Task LoadMotorDynamicsConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(MOTOR_DYNAMICS_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("MotorDynamics configuration not found, using defaults");
_motorDynamicsConfig = new MotorDynamicsConfig
{
Tau = 0.3,
Delta = 0.05f
};
}
else
{
_motorDynamicsConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("MotorDynamics configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading MotorDynamics configuration, using defaults: {ex.Message}");
_motorDynamicsConfig = new MotorDynamicsConfig
{
Tau = 0.3,
Delta = 0.05f
};
}
finally
{
_motorDynamicsConfigLoaded = true;
}
}
private async Task LoadMovePidConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(MOVE_PID_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("MovePid configuration not found, using defaults");
_movePidConfig = new PIDConfig
{
Kp = 1.0,
Ki = 0.0001,
Kd = 0.6
};
}
else
{
_movePidConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("MovePid configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading MovePid configuration, using defaults: {ex.Message}");
_movePidConfig = new PIDConfig
{
Kp = 1.0,
Ki = 0.0001,
Kd = 0.6
};
}
finally
{
_movePidConfigLoaded = true;
}
}
private async Task LoadRotatePidConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(ROTATE_PID_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("RotatePid configuration not found, using defaults");
_rotatePidConfig = new PIDConfig
{
Kp = 0.4,
Ki = 0.0,
Kd = 0.05,
IntegralZone = 0.087 // ~5° in radians
};
}
else
{
_rotatePidConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("RotatePid configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading RotatePid configuration, using defaults: {ex.Message}");
_rotatePidConfig = new PIDConfig
{
Kp = 0.4,
Ki = 0.0,
Kd = 0.05
};
}
finally
{
_rotatePidConfigLoaded = true;
}
}
private async Task LoadNavigationConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(NAVIGATION_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("Navigation configuration not found, using defaults");
_navigationConfig = new Navigation.NavigationConfig
{
MaxLinearVelocity = 0.5,
MaxAngularVelocity = 0.5,
MinLinearVelocity = 0.1,
RotateAngularVelocity = 0.5,
Acceleration = 0.5,
Deceleration = 0.5,
HeadingTolerance = 2,
ReachedRadius = 0.05,
InitialRotationThreshold = 5,
DockToRotateSpeed = 0.05,
LoadedMaxLinearVelocity = 0.3,
LoadedHeadingErrorThresholdDegrees = 10.0,
};
}
else
{
_navigationConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("Navigation configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading Navigation configuration, using defaults: {ex.Message}");
_navigationConfig = new Navigation.NavigationConfig
{
MaxLinearVelocity = 0.5,
MaxAngularVelocity = 1.0,
MinLinearVelocity = 0.1,
RotateAngularVelocity = 0.5,
DockToRotateSpeed = 0.05,
LoadedMaxLinearVelocity = 0.3,
LoadedHeadingErrorThresholdDegrees = 10.0,
};
}
finally
{
_navigationConfigLoaded = true;
}
}
private async Task LoadStanleyConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(STANLEY_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("Stanley configuration not found, using defaults");
_stanleyConfig = new RobotNet10.NavigationTune.Shared.Models.StanleyConfig
{
K = 2.5,
Ks = 0.1,
WheelBase = 0.6,
MaxSteeringAngle = 0.5,
EnableCurvatureFeedforward = true,
KCurvatureFF = 1.0,
GoalTolerance = 0.05,
HeadingTolerance = 5.0,
GoalApproachDistance = 1.0,
GoalGainMultiplier = 2.0,
LowSpeedThreshold = 0.3,
LowSpeedAngularGain = 1.5,
ResolutionSplit = 0.05
};
}
else
{
_stanleyConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("Stanley configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading Stanley configuration, using defaults: {ex.Message}");
_stanleyConfig = new RobotNet10.NavigationTune.Shared.Models.StanleyConfig
{
K = 2.5,
Ks = 0.1,
WheelBase = 0.6,
MaxSteeringAngle = 0.5,
EnableCurvatureFeedforward = true,
KCurvatureFF = 1.0,
GoalTolerance = 0.05,
HeadingTolerance = 5.0,
GoalApproachDistance = 1.0,
GoalGainMultiplier = 2.0,
LowSpeedThreshold = 0.3,
LowSpeedAngularGain = 1.5,
ResolutionSplit = 0.05
};
}
finally
{
_stanleyConfigLoaded = true;
}
}
private async Task LoadDockToConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(DOCK_TO_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("DockTo configuration not found, using defaults");
_dockToConfig = new DockToConfig
{
K = 2.5,
Ks = 0.1,
WheelBase = 0.6,
MaxSteeringAngle = 0.5,
GoalApproachDistance = 1.0,
GoalGainMultiplier = 2.0,
ReachedRadius = 0.03,
ResolutionSplit = 0.05,
DockToLength = 3,
FinePositioningTimeoutMs = 6000,
FinePositioningMaxRetries = 3,
MaxGoalPositionShift = 0.5,
MaxGoalAngleShiftDegrees = 15.0
};
}
else
{
_dockToConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("DockTo configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading DockTo configuration, using defaults: {ex.Message}");
_dockToConfig = new DockToConfig
{
K = 2.5,
Ks = 0.1,
WheelBase = 0.6,
MaxSteeringAngle = 0.5,
GoalApproachDistance = 1.0,
GoalGainMultiplier = 2.0,
ReachedRadius = 0.03,
ResolutionSplit = 0.05,
DockToLength = 3,
FinePositioningTimeoutMs = 6000,
FinePositioningMaxRetries = 3,
MaxGoalPositionShift = 0.5,
MaxGoalAngleShiftDegrees = 15.0
};
}
finally
{
_dockToConfigLoaded = true;
}
}
private async Task LoadMoveStraightConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(MOVE_STRAIGHT_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("MoveStraight configuration not found, using defaults");
_moveStraightConfig = new DockToConfig
{
DockToDirection = RobotNet10.RobotApp.Shared.Enums.RobotDirection.FORWARD,
K = 2.5,
Ks = 0.1,
WheelBase = 0.6,
MaxSteeringAngle = 0.5,
GoalApproachDistance = 1.0,
GoalGainMultiplier = 2.0,
ReachedRadius = 0.05,
MaxAngularVelocity = 0.05,
ResolutionSplit = 0.05,
DecelerationDistance = 5.0,
DockingOvershootDetectionRadius = 0.5,
DockToLength = 3,
FinePositioningTimeoutMs = 6000,
FinePositioningMaxRetries = 3,
};
}
else
{
_moveStraightConfig = MapConfigVariablesToObject(configFile.Variables);
_moveStraightConfig.DockToDirection = RobotNet10.RobotApp.Shared.Enums.RobotDirection.FORWARD;
_logger.Info("MoveStraight configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading MoveStraight configuration, using defaults: {ex.Message}");
_moveStraightConfig = new DockToConfig
{
DockToDirection = RobotNet10.RobotApp.Shared.Enums.RobotDirection.FORWARD,
K = 2.5,
Ks = 0.1,
WheelBase = 0.6,
MaxSteeringAngle = 0.5,
GoalApproachDistance = 1.0,
GoalGainMultiplier = 2.0,
ReachedRadius = 0.05,
MaxAngularVelocity = 0.05,
ResolutionSplit = 0.05,
DecelerationDistance = 5.0,
DockingOvershootDetectionRadius = 0.5,
DockToLength = 3,
FinePositioningTimeoutMs = 6000,
FinePositioningMaxRetries = 3,
};
}
finally
{
_moveStraightConfigLoaded = true;
}
}
private async Task LoadLocalPlannerConfigAsync()
{
try
{
var configFile = await _configManager.GetConfigByTypeAsync(LOCAL_PLANNER_CONFIG_TYPE);
if (configFile == null)
{
_logger.Warning("LocalPlanner configuration not found, using defaults");
_localPlannerConfig = new LocalPlannerConfig();
}
else
{
_localPlannerConfig = MapConfigVariablesToObject(configFile.Variables);
_logger.Info("LocalPlanner configuration loaded successfully");
}
}
catch (Exception ex)
{
_logger.Error($"Error loading LocalPlanner configuration, using defaults: {ex.Message}");
_localPlannerConfig = new LocalPlannerConfig();
}
finally
{
_localPlannerConfigLoaded = true;
}
}
private void OnConfigChanged(object? sender, ConfigChangedEventArgs e)
{
// Reload config if it's one of our config types
lock (_lockObject)
{
switch (e.ConfigType)
{
case PURE_PURSUIT_CONFIG_TYPE:
_purePursuitConfigLoaded = false;
_purePursuitConfig = null;
_logger.Info("PurePursuit configuration changed, will reload on next access");
break;
case VELOCITY_SIGNAL_PROCESSING_CONFIG_TYPE:
_velocitySignalProcessingConfigLoaded = false;
_velocitySignalProcessingConfig = null;
_logger.Info("VelocitySignalProcessing configuration changed, will reload on next access");
break;
case VELOCITY_ESTIMATOR_CONFIG_TYPE:
_velocityEstimatorConfigLoaded = false;
_velocityEstimatorConfig = null;
_logger.Info("VelocityEstimator configuration changed, will reload on next access");
break;
case MOTOR_DYNAMICS_CONFIG_TYPE:
_motorDynamicsConfigLoaded = false;
_motorDynamicsConfig = null;
_logger.Info("MotorDynamics configuration changed, will reload on next access");
break;
case MOVE_PID_CONFIG_TYPE:
_movePidConfigLoaded = false;
_movePidConfig = null;
_logger.Info("MovePid configuration changed, will reload on next access");
break;
case ROTATE_PID_CONFIG_TYPE:
_rotatePidConfigLoaded = false;
_rotatePidConfig = null;
_logger.Info("RotatePid configuration changed, will reload on next access");
break;
case NAVIGATION_CONFIG_TYPE:
_navigationConfigLoaded = false;
_navigationConfig = null;
_logger.Info("Navigation configuration changed, will reload on next access");
break;
case STANLEY_CONFIG_TYPE:
_stanleyConfigLoaded = false;
_stanleyConfig = null;
_logger.Info("Stanley configuration changed, will reload on next access");
break;
case DOCK_TO_CONFIG_TYPE:
_dockToConfigLoaded = false;
_dockToConfig = null;
_logger.Info("DockTo configuration changed, will reload on next access");
break;
case MOVE_STRAIGHT_CONFIG_TYPE:
_moveStraightConfigLoaded = false;
_moveStraightConfig = null;
_logger.Info("MoveStraight configuration changed, will reload on next access");
break;
case LOCAL_PLANNER_CONFIG_TYPE:
_localPlannerConfigLoaded = false;
_localPlannerConfig = null;
_logger.Info("LocalPlanner configuration changed, will reload on next access");
break;
}
}
}
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(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, 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;
}
// Handle Dictionary types (e.g., Dictionary)
else if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
return ConvertToDictionary(value, targetType);
}
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;
}
}
///
/// Convert value to Dictionary
/// Handles conversion from Dictionary (JSON) to typed dictionaries
/// Supports enum keys (e.g., Dictionary)
///
private object ConvertToDictionary(object value, Type targetDictionaryType)
{
// Get key and value types from Dictionary
var genericArgs = targetDictionaryType.GetGenericArguments();
var keyType = genericArgs[0];
var valueType = genericArgs[1];
// Create dictionary instance
var dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
var dictionary = Activator.CreateInstance(dictionaryType);
var addMethod = dictionaryType.GetMethod("Add") ?? throw new InvalidOperationException("Cannot find Add method on Dictionary");
// Handle source Dictionary from JSON
if (value is Dictionary sourceDict)
{
foreach (var kvp in sourceDict)
{
// Convert key (usually from string to enum)
object convertedKey;
if (keyType.IsEnum)
{
// Parse enum from string
convertedKey = Enum.Parse(keyType, kvp.Key, ignoreCase: true);
}
else if (keyType == typeof(string))
{
convertedKey = kvp.Key;
}
else
{
convertedKey = Convert.ChangeType(kvp.Key, keyType);
}
// Convert value
object? convertedValue = ConvertDictionaryValue(kvp.Value, valueType);
// Add to dictionary
addMethod.Invoke(dictionary, [convertedKey, convertedValue]);
}
return dictionary ?? new Dictionary