Admin.NET/WCS.Application/Entity/WcsPlc.cs
@@ -15,6 +15,12 @@ public string IP { get; set; } /// <summary> /// PLC端口号 /// </summary> [SugarColumn(ColumnName = "Port", ColumnDescription = "PLC端口号")] public int Port { get; set; } /// <summary> /// PLC类型 /// </summary> [Required] Admin.NET/WCS.Application/Enum/PLCEnum.cs
@@ -17,10 +17,10 @@ [Description("输送线")] ConveyorLine = 1, /// <summary> /// RGV小车 /// AGV小车 /// </summary> [Description("RGV小车")] RGV = 2, [Description("AGV小车")] AGV = 2, /// <summary> /// 叠拆托机 /// </summary> @@ -106,28 +106,31 @@ //Byte [Description("Byte")] Byte = 2, //Char [Description("Char")] Char = 3, //Word [Description("Word")] Word = 4, //DWord [Description("DWord")] DWord = 5, //Short [Description("Short")] Short = 3, //UShort [Description("UShort")] UShort = 4, //Int [Description("Int")] Int = 6, Int = 5, //DInt [Description("DInt")] DInt = 7, //Real [Description("Real")] Real = 8, //LReal [Description("LReal")] LReal = 9, [Description("UInt")] UInt = 6, //Long [Description("Long")] Long = 7, //ULong [Description("ULong")] ULong = 8, //Float [Description("Float")] Float = 9, //Double [Description("Double")] Double = 10, //String [Description("String")] String = 10, String = 11, } Admin.NET/WCS.Application/PLC/PLCJob.cs
New file @@ -0,0 +1,40 @@ using Furion.Schedule; using Microsoft.Extensions.Logging; namespace WCS.Application; /// <summary> /// PLC作业任务 /// </summary> [JobDetail("job_plc", Description = "PLC作业任务", GroupName = "default", Concurrent = false)] [Daily(TriggerId = "trigger_plc", Description = "PLC作业任务")] public class PLCJob : IJob { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; public PLCJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory) { _scopeFactory = scopeFactory; _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName); } public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { //using var serviceScope = _scopeFactory.CreateScope(); //var rep = serviceScope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysOnlineUser>>(); //await rep.CopyNew().AsDeleteable().ExecuteCommandAsync(stoppingToken); PLCTaskAction.Init(); string msg = $"【{DateTime.Now}】PLC服务启动..."; var originColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(msg); Console.ForegroundColor = originColor; // 自定义日志 _logger.LogInformation(msg); } } Admin.NET/WCS.Application/PLC/PLCService.cs
New file @@ -0,0 +1,60 @@ using IoTClient; namespace WCS.Application; public static class PLCService { public static void OnChangeEvent(object sender, EventArgs e) { var mod = sender as WcsDeviceDto; Console.WriteLine("PLC值为" + mod.Value); switch (mod.Type) { case PLCTypeEnum.StackingMachine: if (mod.Value == 820) { //测试写入830 var result = mod.PLCUtil.SetPlcDBValue(mod.PosType.Value, mod.DbNumber, mod.PlcPos, "830"); //写入是否成功 if (result.IsSucceed) { } } else if (mod.Value == 840) { //测试批量读取 Dictionary<string, PLCDataTypeEnum> listaddress = new Dictionary<string, PLCDataTypeEnum>(); foreach (var modStation in mod.listStation) { listaddress.Add(modStation.PlcPos, modStation.PosType.Value); } var result = mod.PLCUtil.GetPlcBatchDBValue(listaddress); if (!result.IsSucceed) { if (result.Value.Count > 0)//有错误的也有成功的 { } } } else if (mod.Value == 860) { } break; case PLCTypeEnum.ConveyorLine: break; case PLCTypeEnum.AGV: break; case PLCTypeEnum.PalletMachine: break; default: break; } } } Admin.NET/WCS.Application/PLC/PLCTaskAction.cs
New file @@ -0,0 +1,126 @@ using Admin.NET.Core.Service; namespace WCS.Application; public static class PLCTaskAction { private static readonly ISqlSugarClient _db = SqlSugarSetup.ITenant.GetConnectionScope(SqlSugarConst.MainConfigId); private static List<WcsPlc> listPlc; private static List<WcsDevice> listPlcDevice; private static List<WcsStation> listPlcStation; private static List<PLCUtil> listPlcUtil = new List<PLCUtil>(); private static CancellationTokenSource cts = new CancellationTokenSource();//取消线程标识 public static event EventHandler DeviceValueChangeEvent; static PLCTaskAction() { //订阅事件 DeviceValueChangeEvent += PLCService.OnChangeEvent; listPlc = _db.Queryable<WcsPlc>().ToList(); listPlcDevice = _db.Queryable<WcsDevice>().ToList(); listPlcStation = _db.Queryable<WcsStation>().ToList(); } /// <summary> /// 初始化PLC连接 /// </summary> public static void Init() { if (listPlcUtil.Count != 0) { cts.Cancel(); listPlc = _db.Queryable<WcsPlc>().ToList(); listPlcDevice = _db.Queryable<WcsDevice>().ToList(); listPlcStation = _db.Queryable<WcsStation>().ToList(); //等待几秒钟,把已有线程取消掉再连接 Thread.Sleep(5000); foreach (var modPlcUtil in listPlcUtil) { modPlcUtil.Close(); } listPlcUtil.Clear(); } foreach (var modPlc in listPlc) { var plc = new PLCUtil(modPlc); listPlcUtil.Add(plc); } cts = new CancellationTokenSource(); StartRead(); } /// <summary> /// 开启读取plc线程 /// </summary> public static void StartRead() { foreach (var modPlc in listPlc) { Task.Run(() => { var _modplc = modPlc; while (true) { //取消线程 关闭PLC连接 if (cts.Token.IsCancellationRequested) { foreach (var modPlcUtil in listPlcUtil) { modPlcUtil.Close(); } throw new OperationCanceledException(); } try { var modPlcUtil = listPlcUtil.FirstOrDefault(s => s.PlcId == modPlc.Id); if (modPlcUtil == null) { modPlcUtil = new PLCUtil(modPlc); listPlcUtil.Add(modPlcUtil); } var listDevice = listPlcDevice.Where(s => s.PlcId == _modplc.Id).ToList(); //循环读设备 foreach (var modDevice in listDevice) { var b = modPlcUtil.Connected(); var (result, value) = modPlcUtil.GetPlcDBValue(modDevice.PosType.Value, modDevice.DbNumber, modDevice.PlcPos); var c = modPlcUtil.Connected(); if (result.IsSucceed) { if (value != 0) { var dto = modDevice.Adapt<WcsDeviceDto>(); dto.Value = value; dto.Type = _modplc.Type; dto.PLCUtil = modPlcUtil; dto.listStation = listPlcStation.Where(s => s.DeviceId == modDevice.Id).ToList(); //这里触发值变更事件 DeviceValueChangeEvent?.Invoke(dto, EventArgs.Empty); } } else { //删除当前连接 下一次循环重新连接 modPlcUtil.Close(); listPlcUtil.Remove(modPlcUtil); } } Thread.Sleep(100); } catch (Exception ex) { } } }, cts.Token); } } /// <summary> /// 停止服务 /// </summary> public static void Stop() { cts.Cancel(); } } Admin.NET/WCS.Application/PLC/PLCUtil.cs
New file @@ -0,0 +1,246 @@ using DocumentFormat.OpenXml.Bibliography; using Elastic.Clients.Elasticsearch; using IoTClient; using IoTClient.Clients.Modbus; using IoTClient.Clients.PLC; using IoTClient.Common.Enums; using IoTClient.Enums; using IoTClient.Interfaces; namespace WCS.Application; public class PLCUtil { public readonly long PlcId; private SiemensClient _client; private WcsPlc _modPlc; public PLCUtil(WcsPlc modPlc) { PlcId = modPlc.Id; _modPlc = modPlc; _client = new SiemensClient((SiemensVersion)modPlc.PLCType, modPlc.IP, modPlc.Port); _client.Open(); } public bool Connected() { return _client.Connected; } public IoTClient.Result Open() { return _client.Open(); } public IoTClient.Result Close() { return _client.Close(); } /// <summary> /// 读取PLC值 /// </summary> /// <param name="DbNumber">DB区指定值</param> /// <param name="PosType">字符类型</param> /// <param name="Pos">偏移量/地址</param> /// <returns></returns> public (IoTClient.Result, dynamic value) GetPlcDBValue(PLCDataTypeEnum PosType, string DbNumber, string Pos) { string address; if (DbNumber.StartsWith("DB")) address = DbNumber + "." + Pos; else address = DbNumber + Pos; dynamic result = null; switch (PosType) { case PLCDataTypeEnum.Bit: result = _client.ReadBoolean(address); break; case PLCDataTypeEnum.Byte: result = _client.ReadByte(address); break; case PLCDataTypeEnum.Short: result = _client.ReadInt16(address); break; case PLCDataTypeEnum.UShort: result = _client.ReadUInt16(address); break; case PLCDataTypeEnum.Int: result = _client.ReadInt32(address); break; case PLCDataTypeEnum.UInt: result = _client.ReadUInt32(address); break; case PLCDataTypeEnum.Long: result = _client.ReadInt64(address); break; case PLCDataTypeEnum.ULong: result = _client.ReadUInt64(address); break; case PLCDataTypeEnum.Float: result = _client.ReadFloat(address); break; case PLCDataTypeEnum.Double: result = _client.ReadDouble(address); break; default: result = new IoTClient.Result<object>(); break; } return (result, result.Value); } /// <summary> /// 批量读取PLC值 /// </summary> /// <returns></returns> public Result<Dictionary<string, object>> GetPlcBatchDBValue(Dictionary<string, PLCDataTypeEnum> listaddress) { Dictionary<string, DataTypeEnum> addresses = new Dictionary<string, DataTypeEnum>(); foreach (var address in listaddress) { switch (address.Value) { case PLCDataTypeEnum.Bit: addresses.Add(address.Key, DataTypeEnum.Bool); break; case PLCDataTypeEnum.Byte: addresses.Add(address.Key, DataTypeEnum.Byte); break; case PLCDataTypeEnum.Short: addresses.Add(address.Key, DataTypeEnum.Int16); break; case PLCDataTypeEnum.UShort: addresses.Add(address.Key, DataTypeEnum.UInt16); break; case PLCDataTypeEnum.Int: addresses.Add(address.Key, DataTypeEnum.Int32); break; case PLCDataTypeEnum.UInt: addresses.Add(address.Key, DataTypeEnum.UInt32); break; case PLCDataTypeEnum.Long: addresses.Add(address.Key, DataTypeEnum.Int64); break; case PLCDataTypeEnum.ULong: addresses.Add(address.Key, DataTypeEnum.UInt64); break; case PLCDataTypeEnum.Float: addresses.Add(address.Key, DataTypeEnum.Float); break; case PLCDataTypeEnum.Double: addresses.Add(address.Key, DataTypeEnum.Double); break; case PLCDataTypeEnum.String: addresses.Add(address.Key, DataTypeEnum.String); break; default: break; } } return _client.BatchRead(addresses); } /// <summary> /// 写入PLC值 /// </summary> public IoTClient.Result SetPlcDBValue(PLCDataTypeEnum PosType, string DbNumber, string Pos, string Value) { string address; if (DbNumber.StartsWith("DB")) address = DbNumber + "." + Pos; else address = DbNumber + Pos; switch (PosType) { case PLCDataTypeEnum.Bit: if (!bool.TryParse(Value, out bool bit)) { if (Value == "0") bit = false; else if (Value == "1") bit = true; else { throw new Exception("写入值错误"); } } return _client.Write(address, bit); case PLCDataTypeEnum.Byte: return _client.Write(address, byte.Parse(Value)); case PLCDataTypeEnum.Short: return _client.Write(address, short.Parse(Value)); case PLCDataTypeEnum.UShort: return _client.Write(address, ushort.Parse(Value)); case PLCDataTypeEnum.Int: return _client.Write(address, int.Parse(Value)); case PLCDataTypeEnum.UInt: return _client.Write(address, uint.Parse(Value)); case PLCDataTypeEnum.Long: return _client.Write(address, long.Parse(Value)); case PLCDataTypeEnum.ULong: return _client.Write(address, ulong.Parse(Value)); case PLCDataTypeEnum.Float: return _client.Write(address, float.Parse(Value)); case PLCDataTypeEnum.Double: return _client.Write(address, float.Parse(Value)); default: return new IoTClient.Result(); } } /// <summary> /// 写入PLC值 /// </summary> public IoTClient.Result SetPlcBatchDBValue(Dictionary<string, KeyValuePair<string, PLCDataTypeEnum>> listaddress) { Dictionary<string, object> addresses = new Dictionary<string, object>(); foreach (var address in listaddress) { switch (address.Value.Value) { case PLCDataTypeEnum.Bit: if (!bool.TryParse(address.Value.Key, out bool bit)) { if (address.Value.Key == "0") bit = false; else if (address.Value.Key == "1") bit = true; else { throw new Exception("写入值错误"); } } addresses.Add(address.Key, bit); break; case PLCDataTypeEnum.Byte: addresses.Add(address.Key, byte.Parse(address.Value.Key)); break; case PLCDataTypeEnum.Short: addresses.Add(address.Key, short.Parse(address.Value.Key)); break; case PLCDataTypeEnum.UShort: addresses.Add(address.Key, ushort.Parse(address.Value.Key)); break; case PLCDataTypeEnum.Int: addresses.Add(address.Key, int.Parse(address.Value.Key)); break; case PLCDataTypeEnum.UInt: addresses.Add(address.Key, uint.Parse(address.Value.Key)); break; case PLCDataTypeEnum.Long: addresses.Add(address.Key, long.Parse(address.Value.Key)); break; case PLCDataTypeEnum.ULong: addresses.Add(address.Key, ulong.Parse(address.Value.Key)); break; case PLCDataTypeEnum.Float: addresses.Add(address.Key, float.Parse(address.Value.Key)); break; case PLCDataTypeEnum.Double: addresses.Add(address.Key, double.Parse(address.Value.Key)); break; case PLCDataTypeEnum.String: addresses.Add(address.Key, address.Value.Key); break; default: break; } } return _client.BatchWrite(addresses); } } Admin.NET/WCS.Application/Service/WcsDevice/Dto/WcsDeviceDto.cs
@@ -1,109 +1,119 @@ namespace WCS.Application; /// <summary> /// 设备信息输出参数 /// </summary> public class WcsDeviceDto { /// <summary> /// 设备信息输出参数 /// PlcId /// </summary> public class WcsDeviceDto { /// <summary> /// PlcId /// </summary> public string PlcIdIP { get; set; } /// <summary> /// 主键Id /// </summary> public long Id { get; set; } /// <summary> /// PlcId /// </summary> public long PlcId { get; set; } /// <summary> /// 设备级别 /// </summary> public DeviceLevelEnum Level { get; set; } /// <summary> /// DB区域 /// </summary> public string? DbNumber { get; set; } /// <summary> /// 工位号 /// </summary> public string? StationNum { get; set; } /// <summary> /// PLC偏移量 /// </summary> public string? PlcPos { get; set; } /// <summary> /// WCS偏移量 /// </summary> public string? WcsPos { get; set; } /// <summary> /// 流程字类型 /// </summary> public PLCDataTypeEnum? PosType { get; set; } /// <summary> /// 显示屏ip地址 /// </summary> public string? LedIP { get; set; } /// <summary> /// 描述 /// </summary> public string? Text { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime? CreateTime { get; set; } /// <summary> /// 更新时间 /// </summary> public DateTime? UpdateTime { get; set; } /// <summary> /// 创建者Id /// </summary> public long? CreateUserId { get; set; } /// <summary> /// 创建者姓名 /// </summary> public string? CreateUserName { get; set; } /// <summary> /// 修改者Id /// </summary> public long? UpdateUserId { get; set; } /// <summary> /// 修改者姓名 /// </summary> public string? UpdateUserName { get; set; } /// <summary> /// 创建者部门Id /// </summary> public long? CreateOrgId { get; set; } /// <summary> /// 创建者部门名称 /// </summary> public string? CreateOrgName { get; set; } /// <summary> /// 软删除 /// </summary> public bool IsDelete { get; set; } } public string PlcIdIP { get; set; } /// <summary> /// 主键Id /// </summary> public long Id { get; set; } /// <summary> /// PlcId /// </summary> public long PlcId { get; set; } /// <summary> /// 设备级别 /// </summary> public DeviceLevelEnum Level { get; set; } /// <summary> /// 设备类型 /// </summary> public PLCTypeEnum Type { get; set; } /// <summary> /// DB区域 /// </summary> public string? DbNumber { get; set; } /// <summary> /// 工位号 /// </summary> public string? StationNum { get; set; } /// <summary> /// PLC偏移量 /// </summary> public string? PlcPos { get; set; } /// <summary> /// WCS偏移量 /// </summary> public string? WcsPos { get; set; } /// <summary> /// 流程字类型 /// </summary> public PLCDataTypeEnum? PosType { get; set; } /// <summary> /// 显示屏ip地址 /// </summary> public string? LedIP { get; set; } /// <summary> /// 描述 /// </summary> public string? Text { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime? CreateTime { get; set; } /// <summary> /// 更新时间 /// </summary> public DateTime? UpdateTime { get; set; } /// <summary> /// 创建者Id /// </summary> public long? CreateUserId { get; set; } /// <summary> /// 创建者姓名 /// </summary> public string? CreateUserName { get; set; } /// <summary> /// 修改者Id /// </summary> public long? UpdateUserId { get; set; } /// <summary> /// 修改者姓名 /// </summary> public string? UpdateUserName { get; set; } /// <summary> /// 创建者部门Id /// </summary> public long? CreateOrgId { get; set; } /// <summary> /// 创建者部门名称 /// </summary> public string? CreateOrgName { get; set; } /// <summary> /// 软删除 /// </summary> public bool IsDelete { get; set; } public dynamic Value { get; set; } public PLCUtil PLCUtil { get; set; } public List<WcsStation> listStation { get; set; } } Admin.NET/WCS.Application/Service/WcsPlc/Dto/WcsPlcDto.cs
@@ -1,9 +1,4 @@ // Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! namespace WCS.Application; /// <summary> @@ -22,6 +17,11 @@ public string IP { get; set; } /// <summary> /// PLC端口号 /// </summary> public int Port { get; set; } /// <summary> /// 设备类型 /// </summary> public PLCEnum PLCType { get; set; } Admin.NET/WCS.Application/Service/WcsPlc/Dto/WcsPlcInput.cs
@@ -12,6 +12,11 @@ public virtual string IP { get; set; } /// <summary> /// PLC端口号 /// </summary> public virtual int Port { get; set; } /// <summary> /// PLC类型 /// </summary> public virtual PLCEnum PLCType { get; set; } Admin.NET/WCS.Application/Service/WcsPlc/Dto/WcsPlcOutput.cs
@@ -17,6 +17,11 @@ public string IP { get; set; } /// <summary> /// PLC端口号 /// </summary> public int Port { get; set; } /// <summary> /// PLC类型 /// </summary> public PLCEnum PLCType { get; set; } Admin.NET/WCS.Application/Service/WcsPlc/WcsPlcService.cs
@@ -107,6 +107,4 @@ } Admin.NET/WCS.Application/Util/PLCUtil.cs
File was deleted Web/src/views/wcs/wcsPlc/component/editDialog.vue
@@ -21,6 +21,14 @@ </el-col> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-form-item label="PLC端口" prop="port"> <el-input v-model="ruleForm.port" placeholder="请输入PLC端口" maxlength="20" show-word-limit clearable /> </el-form-item> </el-col> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-form-item label="PLC类型" prop="plcType"> <el-select clearable v-model="ruleForm.plcType" placeholder="请选择PLC类型"> <el-option v-for="(item, index) in dl('PLCEnum')" :key="index" Web/src/views/wcs/wcsPlc/index.vue
@@ -59,6 +59,7 @@ @sort-change="sortChange" border=""> <el-table-column type="index" label="序号" width="55" align="center" /> <el-table-column prop="ip" label="PLCIP地址" show-overflow-tooltip="" /> <el-table-column prop="port" label="PLC端口" show-overflow-tooltip="" /> <el-table-column prop="plcType" label="PLC类型" show-overflow-tooltip=""> <template #default="scope"> <el-tag :type="dv('PLCEnum', scope.row.plcType)?.tagType"> {{ dv('PLCEnum',