using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using Newtonsoft.Json; using TeamAAS.Feeder.Attributes; using TeamAAS.Feeder.Devices; using TeamAAS.Feeder.Interfaces; using TeamAAS.Feeder.Models; using TeamAAS.Feeder.Enums; using TeamAAS.Feeder.Models; namespace TeamAAS.Feeder { /// /// 供料器管理器。懒汉单例:FeederManager.Instance。 /// 整合:设备 CRUD + 配方管理 + 反射发现设备类型 + 配置持久化。 /// 管理类按架构规则保留在根命名空间 TeamAAS.Feeder。 /// public class FeederManager : IFeederManager { #region 单例 private static readonly Lazy _instance = new Lazy(() => new FeederManager(), isThreadSafe: true); /// /// 懒汉单例入口。首次访问时初始化,线程安全。 /// public static FeederManager Instance => _instance.Value; #endregion private readonly object _sync = new object(); private readonly Dictionary _feeders = new Dictionary(); private readonly Dictionary _devices = new Dictionary(); private List _recipes = new List(); private List _scannedTypes; private readonly object _scanLock = new object(); public static string DefaultConfigPath => TeamAAS.PathHelper.FeedersConfigFile; private static string DefaultRecipeFilePath => TeamAAS.PathHelper.FeederRecipesFile; public event EventHandler FeederListChanged; public FeederManager() { _recipes = LoadAllRecipesInternal(); } #region 设备配置 CRUD public void LoadConfig(string path = null) { path = path ?? DefaultConfigPath; if (!File.Exists(path)) return; try { var list = TeamAAS.JsonFileStore.Load>(path); if (list == null) return; lock (_sync) { _feeders.Clear(); foreach (var info in list) { if (!_feeders.ContainsKey(info.Id)) _feeders.Add(info.Id, info); } } FeederListChanged?.Invoke(this, EventArgs.Empty); } catch (Exception ex) { Trace.WriteLine($"FeederManager.LoadConfig 失败: {ex.Message}"); } } public bool SaveConfig(string path = null) { path = path ?? DefaultConfigPath; try { List list; lock (_sync) { list = _feeders.Values.ToList(); } return TeamAAS.JsonFileStore.Save(path, list); } catch (Exception ex) { Trace.WriteLine($"FeederManager.SaveConfig 失败: {ex.Message}"); return false; } } public FeederInfo Add(FeederInfo info) { if (info == null) throw new ArgumentNullException(nameof(info)); lock (_sync) { if (_feeders.ContainsKey(info.Id)) _feeders[info.Id] = info; else _feeders.Add(info.Id, info); } FeederListChanged?.Invoke(this, EventArgs.Empty); return info; } public bool Remove(Guid id) { bool removed; lock (_sync) { removed = _feeders.Remove(id); if (_devices.TryGetValue(id, out var dev)) { dev.Dispose(); _devices.Remove(id); } } if (removed) FeederListChanged?.Invoke(this, EventArgs.Empty); return removed; } public FeederInfo Get(Guid id) { lock (_sync) { return _feeders.TryGetValue(id, out var f) ? f : null; } } public IReadOnlyList GetAll() { lock (_sync) { return _feeders.Values.ToList().AsReadOnly(); } } #endregion #region 设备实例管理 public IFeeder GetDevice(Guid id) { lock (_sync) { return _devices.TryGetValue(id, out var d) ? d : null; } } public IFeeder GetOrCreateDevice(FeederInfo info) { if (info == null) return null; lock (_sync) { if (_devices.TryGetValue(info.Id, out var existing)) { try { existing.Dispose(); } catch { } _devices.Remove(info.Id); } var device = CreateDeviceInstance(info); _devices[info.Id] = device; return device; } } #endregion #region 配方管理 public IReadOnlyList GetRecipes() { lock (_sync) { return _recipes.ToList().AsReadOnly(); } } public FeederRecipe GetRecipe(string name) { lock (_sync) { return _recipes.FirstOrDefault(r => r.RecipeName == name); } } public bool SaveRecipe(FeederRecipe recipe) { if (recipe == null || string.IsNullOrWhiteSpace(recipe.RecipeName)) return false; var ok = SaveRecipeInternal(recipe); if (ok) { lock (_sync) { var existing = _recipes.FirstOrDefault(r => r.RecipeName == recipe.RecipeName); if (existing != null) _recipes[_recipes.IndexOf(existing)] = recipe; else _recipes.Add(recipe); } } return ok; } public bool DeleteRecipe(string name) { var ok = DeleteRecipeInternal(name); if (ok) { lock (_sync) { var r = _recipes.FirstOrDefault(x => x.RecipeName == name); if (r != null) _recipes.Remove(r); } } return ok; } public FeederRecipe LoadRecipe(string path) { var recipe = LoadRecipeInternal(path); if (recipe != null) { lock (_sync) { var existing = _recipes.FirstOrDefault(r => r.RecipeName == recipe.RecipeName); if (existing != null) _recipes[_recipes.IndexOf(existing)] = recipe; else _recipes.Add(recipe); } } return recipe; } private static List LoadAllRecipesInternal() { try { if (!File.Exists(DefaultRecipeFilePath)) return new List(); var json = File.ReadAllText(DefaultRecipeFilePath); var list = JsonConvert.DeserializeObject>(json); return list ?? new List(); } catch (Exception ex) { Trace.WriteLine($"FeederManager.LoadAllRecipes 失败: {ex.Message}"); return new List(); } } private static bool SaveAllRecipesInternal(List recipes) { try { var json = JsonConvert.SerializeObject(recipes, Formatting.Indented); File.WriteAllText(DefaultRecipeFilePath, json); return true; } catch (Exception ex) { Trace.WriteLine($"FeederManager.SaveAllRecipes 失败: {ex.Message}"); return false; } } private static bool SaveRecipeInternal(FeederRecipe recipe) { try { recipe.UpdatedAt = DateTime.Now; var list = LoadAllRecipesInternal(); var idx = list.FindIndex(r => r.RecipeName == recipe.RecipeName); if (idx >= 0) list[idx] = recipe; else list.Add(recipe); return SaveAllRecipesInternal(list); } catch (Exception ex) { Trace.WriteLine($"FeederManager.SaveRecipe 失败: {ex.Message}"); return false; } } private static bool DeleteRecipeInternal(string recipeName) { try { var list = LoadAllRecipesInternal(); var removed = list.RemoveAll(r => r.RecipeName == recipeName); if (removed > 0) return SaveAllRecipesInternal(list); return false; } catch (Exception ex) { Trace.WriteLine($"FeederManager.DeleteRecipe 失败: {ex.Message}"); return false; } } private static FeederRecipe LoadRecipeInternal(string path) { try { if (!File.Exists(path)) return null; var json = File.ReadAllText(path); return JsonConvert.DeserializeObject(json); } catch (Exception ex) { Trace.WriteLine($"FeederManager.LoadRecipe 失败: {ex.Message}"); return null; } } #endregion #region 反射扫描 + 工厂 public IReadOnlyList GetAvailableTypes() { if (_scannedTypes != null) return _scannedTypes.AsReadOnly(); lock (_scanLock) { if (_scannedTypes != null) return _scannedTypes.AsReadOnly(); _scannedTypes = ScanTypes(); return _scannedTypes.AsReadOnly(); } } private List ScanTypes() { var result = new List(); // 通用模块扫描:收集全部 IFeeder 实现(含 Runtime\Plugins 里的插件 DLL) foreach (var type in TeamAAS.Modularity.AssemblyScanner.FindImplementations()) { var attrs = type.GetCustomAttributes(); foreach (var attr in attrs) { result.Add(new FeederTypeInfo { TypeKey = type.FullName, DisplayName = attr.DisplayName, Brand = attr.Brand, Description = attr.Description, Type = type }); } } // fallback:如果反射没扫到品牌特性,注册内置 TeamFeeder(兼容旧代码) if (!result.Any(t => t.Brand == FeederBrand.Team)) { result.Add(new FeederTypeInfo { TypeKey = typeof(TeamFeeder).FullName, DisplayName = "Team 供料器", Brand = FeederBrand.Team, Description = "内置 Team 品牌供料器实现", Type = typeof(TeamFeeder) }); } return result .GroupBy(t => t.TypeKey + "|" + t.Brand) .Select(g => g.First()) .OrderBy(t => t.DisplayName) .ToList(); } private IFeeder CreateDeviceInstance(FeederInfo info) { if (info == null) throw new ArgumentNullException(nameof(info)); var types = GetAvailableTypes(); var match = types.FirstOrDefault(t => t.Brand == info.FeederBrand); if (match == null) throw new NotSupportedException($"暂不支持的供料器品牌: {info.FeederBrand}"); return (IFeeder)Activator.CreateInstance( match.Type, info.Id, info.FeederNo, info.FeederName, info.IP, info.Port); } #endregion } public class FeederTypeInfo { public string TypeKey { get; set; } public string DisplayName { get; set; } public FeederBrand Brand { get; set; } public string Description { get; set; } public Type Type { get; set; } public override string ToString() => DisplayName; } }