|
|
@@ -1,5 +1,6 @@
|
|
|
using Cognex.VisionPro;
|
|
|
using Cognex.VisionPro.ToolBlock;
|
|
|
+using SqlSugar;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Collections.ObjectModel;
|
|
|
@@ -14,6 +15,8 @@ using TeamAAS_VP.Models.Feeder;
|
|
|
using TeamAAS_VP.Models.Lights;
|
|
|
using TeamAAS_VP.Models.PLC;
|
|
|
using TeamAAS_VP.Models.ScrewDriver;
|
|
|
+using TeamAAS_VP.Data; // for ISystemDatabaseService (if used)
|
|
|
+using System.Threading;
|
|
|
|
|
|
namespace TeamAAS_VP.Services
|
|
|
{
|
|
|
@@ -37,15 +40,17 @@ namespace TeamAAS_VP.Services
|
|
|
// Feeder清料任务配置路径
|
|
|
public static readonly string FeederClearanceTasksConfigurationPath = "..//Config//FeederClearanceTasksConfiguration.cfg";
|
|
|
// 光源控制器配置路径
|
|
|
- public static readonly string LightControllersConfigurationPath = "..//Config//LightControllersConfiguration.cfg";
|
|
|
- // 电批配置路径(单设备)
|
|
|
public static readonly string ScrewDriverConfigurationPath = "..//Config//ScrewDriverConfiguration.cfg";
|
|
|
+ // 螺丝供料器(物料)配置路径
|
|
|
+ public static readonly string ScrewFeedersConfigurationPath = "..//Config//ScrewFeedersConfiguration.cfg";
|
|
|
}
|
|
|
|
|
|
private readonly object _sync = new object();
|
|
|
private ObservableCollection<CameraInfo> Cameras { get; set; }
|
|
|
private ObservableCollection<RobotInfo> Robots { get; set; }
|
|
|
private ObservableCollection<FeederInfo> Feeders { get; set; }
|
|
|
+ // 螺丝供料器(物料)集合
|
|
|
+ private ObservableCollection<ScrewFeederInfo> ScrewFeeders { get; set; }
|
|
|
private ObservableCollection<PlcInfo> Plcs { get; set; }
|
|
|
private BgTcpIP BgCommunicate { get; set; }
|
|
|
private BgModbusTcp BgModbusCommunicate { get; set; }
|
|
|
@@ -58,6 +63,39 @@ namespace TeamAAS_VP.Services
|
|
|
private TeamAAS_VP.Models.ScrewDriver.ScrewDriverConfig _screwDriverConfig;
|
|
|
public TeamAAS_VP.Models.ScrewDriver.ScrewDriverConfig ScrewDriverConfig { get { lock(_sync) { return _screwDriverConfig; } } }
|
|
|
|
|
|
+ private ISystemDatabaseService _systemDatabaseService;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 注入系统数据库服务(可选),用于记录螺丝批次变更等记录。
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="svc"></param>
|
|
|
+ public void SetSystemDatabaseService(ISystemDatabaseService svc)
|
|
|
+ {
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ _systemDatabaseService = svc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 将螺丝供料器批次变更记录写入数据库(若已注入 ISystemDatabaseService)。
|
|
|
+ /// </summary>
|
|
|
+ public async System.Threading.Tasks.Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record)
|
|
|
+ {
|
|
|
+ if (record == null) throw new ArgumentNullException(nameof(record));
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (_systemDatabaseService != null)
|
|
|
+ {
|
|
|
+ await _systemDatabaseService.RecordScrewFeederBatchAsync(record);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // 忽略数据库写入错误,记录可添加日志
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 初始化一个新的 <see cref="ConfigService"/> 实例。
|
|
|
/// 构造函数不会自动加载配置文件(避免在构造期间进行 I/O 操作);可调用 <see cref="LoadAll"/> 或 <see cref="LoadAllAsync"/> 显式加载。
|
|
|
@@ -98,6 +136,16 @@ namespace TeamAAS_VP.Services
|
|
|
Feeders = new ObservableCollection<FeederInfo>();
|
|
|
FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
|
|
|
}
|
|
|
+
|
|
|
+ // load screw feeders (material) configuration
|
|
|
+ if (File.Exists(ConfigPaths.ScrewFeedersConfigurationPath))
|
|
|
+ ScrewFeeders = FileHelper.ReadJsonFile<ObservableCollection<ScrewFeederInfo>>(ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ScrewFeeders = new ObservableCollection<ScrewFeederInfo>();
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ }
|
|
|
+
|
|
|
if (File.Exists(ConfigPaths.PlcConfigurationPath))
|
|
|
Plcs = FileHelper.ReadJsonFile<ObservableCollection<PlcInfo>>(ConfigPaths.PlcConfigurationPath);
|
|
|
else
|
|
|
@@ -131,14 +179,14 @@ namespace TeamAAS_VP.Services
|
|
|
}
|
|
|
|
|
|
// 读取光源控制器配置
|
|
|
- if (!File.Exists(ConfigPaths.LightControllersConfigurationPath))
|
|
|
+ if (!File.Exists(ConfigPaths.ScrewDriverConfigurationPath))
|
|
|
{
|
|
|
LightControllers = new ObservableCollection<LightControllerConfig>();
|
|
|
- FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
|
|
|
+ FileHelper.WriteJsonFile(LightControllers, ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- LightControllers = FileHelper.ReadJsonFile<ObservableCollection<LightControllerConfig>>(ConfigPaths.LightControllersConfigurationPath);
|
|
|
+ LightControllers = FileHelper.ReadJsonFile<ObservableCollection<LightControllerConfig>>(ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
}
|
|
|
// load plc addresses
|
|
|
var plcAddressPath = "..//Config//PlcAddressConfiguration.cfg";
|
|
|
@@ -185,10 +233,13 @@ namespace TeamAAS_VP.Services
|
|
|
FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
|
|
|
FileHelper.WriteJsonFile(BgCommunicate, ConfigPaths.BgTcpIpConfigurationPath);
|
|
|
FileHelper.WriteJsonFile(BgModbusCommunicate, ConfigPaths.BgModbusTcpConfigurationPath);
|
|
|
- FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
|
|
|
+ FileHelper.WriteJsonFile(LightControllers, ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
// save screw driver config
|
|
|
if (_screwDriverConfig != null)
|
|
|
FileHelper.WriteJsonFile(_screwDriverConfig, ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
+ // save screw feeders
|
|
|
+ if (ScrewFeeders != null)
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
// save plc addresses
|
|
|
//FileHelper.WriteJsonFile(PlcAddressConfig, "..//Config//PlcAddressConfiguration.cfg");
|
|
|
}
|
|
|
@@ -843,7 +894,7 @@ namespace TeamAAS_VP.Services
|
|
|
}
|
|
|
}
|
|
|
// 持久化更新后的光源控制器配置
|
|
|
- FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
|
|
|
+ FileHelper.WriteJsonFile(LightControllers, ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
return controller;
|
|
|
}
|
|
|
|
|
|
@@ -876,7 +927,7 @@ namespace TeamAAS_VP.Services
|
|
|
LightControllers.Add(c);
|
|
|
}
|
|
|
}
|
|
|
- FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
|
|
|
+ FileHelper.WriteJsonFile(LightControllers, ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
@@ -929,7 +980,7 @@ namespace TeamAAS_VP.Services
|
|
|
var ch = LightControllers.SelectMany(c => c.ChannelConfigs).FirstOrDefault(x => x.GlobalIndex == globalIndex);
|
|
|
if (ch == null) return false;
|
|
|
ch.DefaultBrightness = brightness;
|
|
|
- FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
|
|
|
+ FileHelper.WriteJsonFile(LightControllers, ConfigPaths.ScrewDriverConfigurationPath);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
@@ -977,6 +1028,213 @@ namespace TeamAAS_VP.Services
|
|
|
}
|
|
|
#endregion
|
|
|
|
|
|
+ #region Screw Feeders (material)
|
|
|
+ /// <summary>
|
|
|
+ /// 获取所有螺丝供料器配置的只读集合。
|
|
|
+ /// </summary>
|
|
|
+ public IReadOnlyCollection<ScrewFeederInfo> GetAllScrewFeeders() { lock (_sync) { return ScrewFeeders.ToList().AsReadOnly(); } }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 根据 Id 获取单个螺丝供料器配置。
|
|
|
+ /// </summary>
|
|
|
+ public ScrewFeederInfo GetScrewFeeder(Guid id) { lock (_sync) { return ScrewFeeders.FirstOrDefault(s => s.Id == id); } }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 新增或更新螺丝供料器配置。
|
|
|
+ /// 新增时若未指定 FeederNumber 则分配为当前数量+1。
|
|
|
+ /// </summary>
|
|
|
+ public ScrewFeederInfo AddOrUpdateScrewFeeder(ScrewFeederInfo feeder)
|
|
|
+ {
|
|
|
+ if (feeder == null) return null;
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var exist = ScrewFeeders.FirstOrDefault(f => f.Id == feeder.Id);
|
|
|
+ if (exist != null)
|
|
|
+ {
|
|
|
+ var idx = ScrewFeeders.IndexOf(exist);
|
|
|
+ ScrewFeeders[idx] = feeder;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (feeder.FeederNumber <= 0) feeder.FeederNumber = ScrewFeeders.Count + 1;
|
|
|
+ ScrewFeeders.Add(feeder);
|
|
|
+ }
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ }
|
|
|
+ return feeder;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 移除螺丝供料器配置。
|
|
|
+ /// </summary>
|
|
|
+ public bool RemoveScrewFeeder(Guid id)
|
|
|
+ {
|
|
|
+ bool result = false;
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var e = ScrewFeeders.FirstOrDefault(s => s.Id == id);
|
|
|
+ if (e != null) result = ScrewFeeders.Remove(e);
|
|
|
+ }
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ContainsScrewFeeder(Guid id) { lock (_sync) { return ScrewFeeders.Any(s => s.Id == id); } }
|
|
|
+
|
|
|
+ // event for low level alarm
|
|
|
+ public event Action<ScrewFeederInfo> ScrewFeederLowLevelAlarmTriggered;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取指定供料器编号的剩余螺丝数量(找不到返回 -1)
|
|
|
+ /// </summary>
|
|
|
+ public int GetRemainingCountByFeederNumber(int feederNumber)
|
|
|
+ {
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
|
|
|
+ if (f == null) return -1;
|
|
|
+ return f.RemainingCount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取指定供料器编号的低量报警状态(找不到返回 false)
|
|
|
+ /// </summary>
|
|
|
+ public bool GetLowLevelAlarmStatusByFeederNumber(int feederNumber)
|
|
|
+ {
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
|
|
|
+ if (f == null) return false;
|
|
|
+ return f.IsLowLevelAlarmActive;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // helper: check and raise low level alarm when necessary
|
|
|
+ private void CheckAndTriggerLowLevelAlarm(ScrewFeederInfo f)
|
|
|
+ {
|
|
|
+ if (f == null) return;
|
|
|
+ if (f.LowLevelAlarmEnabled && f.RemainingCount <= f.LowLevelThreshold)
|
|
|
+ {
|
|
|
+ if (!f.IsLowLevelAlarmActive)
|
|
|
+ {
|
|
|
+ f.IsLowLevelAlarmActive = true;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ ScrewFeederLowLevelAlarmTriggered?.Invoke(f.Clone());
|
|
|
+ }
|
|
|
+ catch { }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (f.IsLowLevelAlarmActive)
|
|
|
+ {
|
|
|
+ f.IsLowLevelAlarmActive = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 为指定供料器编号递减指定数量,若数量不足返回 false
|
|
|
+ /// </summary>
|
|
|
+ public bool DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1)
|
|
|
+ {
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
|
|
|
+ if (f == null) return false;
|
|
|
+ if (decrement <= 0) decrement = 1;
|
|
|
+ if (f.RemainingCount < decrement) return false;
|
|
|
+ f.RemainingCount -= decrement;
|
|
|
+ // check alarm
|
|
|
+ CheckAndTriggerLowLevelAlarm(f);
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 清除指定供料器编号的螺丝信息(批次号置空,剩余数量置0)
|
|
|
+ /// </summary>
|
|
|
+ public void ClearScrewFeederByNumber(int feederNumber)
|
|
|
+ {
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
|
|
|
+ if (f == null) return;
|
|
|
+ f.BatchNumber = string.Empty;
|
|
|
+ f.RemainingCount = 0;
|
|
|
+ // check alarm
|
|
|
+ CheckAndTriggerLowLevelAlarm(f);
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 为指定供料器编号设置批次号,并在原有剩余数量上增加一包(PackSize)
|
|
|
+ /// </summary>
|
|
|
+ public void SetBatchForFeederNumber(int feederNumber, string batchNumber)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(batchNumber)) return;
|
|
|
+
|
|
|
+ // capture values under lock, then record to DB outside lock
|
|
|
+ ScrewFeederInfo target = null;
|
|
|
+ int remainingAfter = 0;
|
|
|
+ int packSize = 0;
|
|
|
+ string feederName = null;
|
|
|
+ lock (_sync)
|
|
|
+ {
|
|
|
+ var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
|
|
|
+ if (f == null) return;
|
|
|
+ f.BatchNumber = batchNumber;
|
|
|
+ f.RemainingCount += Math.Max(0, f.PackSize);
|
|
|
+
|
|
|
+ // prepare capture values
|
|
|
+ target = f;
|
|
|
+ remainingAfter = f.RemainingCount;
|
|
|
+ packSize = f.PackSize;
|
|
|
+ feederName = f.Name;
|
|
|
+
|
|
|
+ // check alarm
|
|
|
+ CheckAndTriggerLowLevelAlarm(f);
|
|
|
+ FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var record = new ScrewFeederBatchRecord
|
|
|
+ {
|
|
|
+ BatchNumber = batchNumber,
|
|
|
+ PackSize = packSize,
|
|
|
+ ChangeTime = DateTime.UtcNow,
|
|
|
+ RemainingCount = remainingAfter,
|
|
|
+ FeederNumber = feederNumber,
|
|
|
+ FeederName = feederName
|
|
|
+ };
|
|
|
+
|
|
|
+ // try get operator name from system database service if available
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var user = _systemDatabaseService?.GetCurrentUser();
|
|
|
+ if (user != null)
|
|
|
+ record.OperatorName = user.UserName;
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // ignore getting user failure
|
|
|
+ }
|
|
|
+
|
|
|
+ // fire-and-forget recording (ConfigService will forward to DB service if set)
|
|
|
+ _ = RecordScrewFeederBatchAsync(record);
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // ignore any error creating/recording the batch record
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// 释放资源并保存当前内存中的所有配置到文件。
|
|
|
/// </summary>
|