diff --git a/Library/Api/ISereinIoc.cs b/Library/Api/ISereinIoc.cs index c1aaf32..7f22dcc 100644 --- a/Library/Api/ISereinIoc.cs +++ b/Library/Api/ISereinIoc.cs @@ -30,6 +30,7 @@ namespace Serein.Library.Api /// /// ISereinIOC Register(params object[] parameters) where TImplementation : TService; + ///// ///// 获取或创建并注入目标类型,会记录到IOC容器中。 ///// diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs index a666065..0fa74f9 100644 --- a/Library/Entity/MethodDetails.cs +++ b/Library/Entity/MethodDetails.cs @@ -38,7 +38,7 @@ namespace Serein.Library.Entity /// /// 是否保护参数 /// - public bool IsProtectionParameter { get; set; } = true; + public bool IsProtectionParameter { get; set; } = false; /// /// 作用实例的类型 diff --git a/Library/Ex/FlipflopException.cs b/Library/Ex/FlipflopException.cs index 6742039..503021c 100644 --- a/Library/Ex/FlipflopException.cs +++ b/Library/Ex/FlipflopException.cs @@ -8,11 +8,19 @@ namespace Serein.Library.Ex /// public class FlipflopException: Exception { - + public enum CancelClass + { + // 取消当前分支的继续执行 + Branch, + // 取消整个触发器流程的再次执行 + Flow, + } public bool IsCancel { get; } - public FlipflopException(string message, bool isCancel = true) :base(message) + public CancelClass Clsss { get; } + public FlipflopException(string message, bool isCancel = true,CancelClass clsss = CancelClass.Branch) :base(message) { IsCancel = isCancel; + Clsss = clsss; } } } diff --git a/Library/Web/Attribute.cs b/Library/Network/Http/Attribute.cs similarity index 100% rename from Library/Web/Attribute.cs rename to Library/Network/Http/Attribute.cs diff --git a/Library/Web/ControllerBase.cs b/Library/Network/Http/ControllerBase.cs similarity index 100% rename from Library/Web/ControllerBase.cs rename to Library/Network/Http/ControllerBase.cs diff --git a/Library/Web/QueryStringParser.cs b/Library/Network/Http/QueryStringParser.cs similarity index 100% rename from Library/Web/QueryStringParser.cs rename to Library/Network/Http/QueryStringParser.cs diff --git a/Library/Web/Router.cs b/Library/Network/Http/Router.cs similarity index 98% rename from Library/Web/Router.cs rename to Library/Network/Http/Router.cs index 15c1cfb..d794328 100644 --- a/Library/Web/Router.cs +++ b/Library/Network/Http/Router.cs @@ -341,7 +341,15 @@ namespace Serein.Library.Web { try { - return JsonConvert.DeserializeObject(value.ToString(), targetType); + if (targetType == typeof(string)) + { + return value; + } + else + { + return JsonConvert.DeserializeObject(value.ToString(), targetType); + } + } catch (JsonReaderException ex) { diff --git a/Library/Web/WebServer.cs b/Library/Network/Http/WebServer.cs similarity index 100% rename from Library/Web/WebServer.cs rename to Library/Network/Http/WebServer.cs diff --git a/Library/Network/WebSocket/WebSocketRouter.cs b/Library/Network/WebSocket/WebSocketRouter.cs new file mode 100644 index 0000000..7b56edd --- /dev/null +++ b/Library/Network/WebSocket/WebSocketRouter.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Serein.Library.Network.WebSocketCommunication +{ + public class WebSocketRouter + { + } +} diff --git a/Library/Network/WebSocket/WebSocketServer.cs b/Library/Network/WebSocket/WebSocketServer.cs new file mode 100644 index 0000000..f1e25cf --- /dev/null +++ b/Library/Network/WebSocket/WebSocketServer.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; +using System; +using System.Net; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.Library.Network.WebSocketCommunication +{ + public class WebSocketServer + { + public Func OnReceiveMsg; + + public async Task StartAsync(string url) + { + HttpListener listener = new HttpListener(); + listener.Prefixes.Add(url); + listener.Start(); + + while (true) + { + var context = await listener.GetContextAsync(); + if (context.Request.IsWebSocketRequest) + { + var webSocketContext = await context.AcceptWebSocketAsync(null); //新连接 + _ = HandleWebSocketAsync(webSocketContext.WebSocket); // 处理消息 + } + } + } + + private async Task HandleWebSocketAsync(WebSocket webSocket) + { + var buffer = new byte[1024]; + while (webSocket.State == WebSocketState.Open) + { + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Close) + { + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); + } + else + { + var message = Encoding.UTF8.GetString(buffer, 0, result.Count); + Console.WriteLine($"Received: {message}"); + var action = OnReceiveMsg.Invoke(message); + action?.Invoke(); + + // 回显消息(可选) + //ar echoMessage = Encoding.UTF8.GetBytes(message); + //await webSocket.SendAsync(new ArraySegment(echoMessage, 0, echoMessage.Length), result.MessageType, result.EndOfMessage, CancellationToken.None); + + } + } + } + } +} diff --git a/Library/NodeAttribute.cs b/Library/NodeAttribute.cs index b8e9f9c..91c5647 100644 --- a/Library/NodeAttribute.cs +++ b/Library/NodeAttribute.cs @@ -25,10 +25,12 @@ namespace Serein.Library.Attributes [AttributeUsage(AttributeTargets.Class)] public class DynamicFlowAttribute : Attribute { - public DynamicFlowAttribute(bool scan = true) + public DynamicFlowAttribute(string name = "",bool scan = true) { + Name = name; Scan = scan; } + public string Name { get; set; } public bool Scan { get; set; } = true; } @@ -70,7 +72,7 @@ namespace Serein.Library.Attributes // } //} - [AttributeUsage(AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class BindValueAttribute : Attribute { public object Value { get; } diff --git a/Library/Utils/ChannelFlowTrigger.cs b/Library/Utils/ChannelFlowTrigger.cs index 9c9f0e4..917abce 100644 --- a/Library/Utils/ChannelFlowTrigger.cs +++ b/Library/Utils/ChannelFlowTrigger.cs @@ -33,10 +33,15 @@ namespace Serein.Library.NodeFlow.Tool { // 使用并发字典管理每个枚举信号对应的 Channel private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>(); - - - // 到期后自动触发。短时间内触发频率过高的情况下,请将 outTime 设置位短一些的时间,因为如果超时等待时间过长,会导致非预期的“托管内存泄露”。 - + /// + /// 获取或创建指定信号的 Channel + /// + /// 枚举信号标识符 + /// 对应的 Channel + private Channel GetOrCreateChannel(TSignal signal) + { + return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded()); + } /// /// 创建信号并指定超时时间的Channel. /// @@ -96,6 +101,7 @@ namespace Serein.Library.NodeFlow.Tool Type = TriggerType.External, }; // 手动触发信号 + channel.Writer.TryWrite(triggerData); return true; } @@ -114,14 +120,6 @@ namespace Serein.Library.NodeFlow.Tool _channels.Clear(); } - /// - /// 获取或创建指定信号的 Channel - /// - /// 枚举信号标识符 - /// 对应的 Channel - private Channel GetOrCreateChannel(TSignal signal) - { - return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded()); - } + } } diff --git a/Library/Utils/EnumHelper.cs b/Library/Utils/EnumHelper.cs index 3056f9c..c8cae80 100644 --- a/Library/Utils/EnumHelper.cs +++ b/Library/Utils/EnumHelper.cs @@ -8,6 +8,16 @@ namespace Serein.Library.Utils { public static class EnumHelper { + public static bool TryConvertEnum(this string value, out T result) where T : struct, Enum + { + if (!string.IsNullOrEmpty(value) && Enum.TryParse(value, true, out T tempResult) && Enum.IsDefined(typeof(T), tempResult)) + { + result = tempResult; + return true; + } + result = default; + return false; + } public static TResult GetBoundValue(TEnum enumValue, Func valueSelector) where TEnum : Enum { diff --git a/Net462DllTest/Device/SiemensPlcDevice.cs b/Net462DllTest/Device/SiemensPlcDevice.cs deleted file mode 100644 index 2418fb9..0000000 --- a/Net462DllTest/Device/SiemensPlcDevice.cs +++ /dev/null @@ -1,293 +0,0 @@ -using IoTClient; -using IoTClient.Clients.PLC; -using IoTClient.Enums; -using Net462DllTest.Enums; -using Net462DllTest.Signal; -using Serein.Library.Attributes; -using Serein.Library.NodeFlow.Tool; -using System; - -namespace Net462DllTest.Device -{ - - /// - /// 官方文档:如果没有主动Open,则会每次读写操作的时候自动打开自动和关闭连接,这样会使读写效率大大减低。所以建议手动Open和Close。 - /// - [AutoRegister] - public class SiemensPlcDevice : ChannelFlowTrigger - { - public SiemensClient Client { get; set; } - - public IoTClient.Common.Enums.SiemensVersion Version { get; set; } - public string IP { get; set; } - public int Port { get; set; } - public PlcState State { get; set; } = PlcState.PowerOff; - - - public void Init(IoTClient.Common.Enums.SiemensVersion version,string ip, int port) - { - Client = new SiemensClient(version, ip, port); - Version = version; - IP = ip; - Port = port; - } - - public void ResetDevice() - { - Client?.Close(); - Client = null; - } - - public void Write(PlcVarInfo plcValue, object value) - { - try - { - Client.WriteToPlcValue(plcValue, value); - } - catch (Exception ex) - { - Console.WriteLine($"写入出错:{this}{plcValue}。{ex.Message}"); - throw; - } - } - public object Read(PlcVarInfo plcValue) - { - try - { - return Client.ReadToPlcValue(plcValue); - } - catch (Exception ex) - { - Console.WriteLine($"读取出错:{this}{plcValue}。{ex.Message}"); - throw; - } - - } - - public override string ToString() - { - return $"西门子Plc[{this.Version}-{this.IP}:{this.Port}]"; - } - } - - /// - /// PLC方法 - /// - public static class PlcExtension - { - public static DataTypeEnum ToDataTypeEnum(this PlcVarInfo varInfo) - { - Type dataType = varInfo.DataType; - DataTypeEnum plcDataType; - switch (dataType) - { - case Type _ when dataType == typeof(string): - plcDataType = DataTypeEnum.String; - break; - case Type _ when dataType == typeof(char): - plcDataType = DataTypeEnum.String; - break; - case Type _ when dataType == typeof(bool): - plcDataType = DataTypeEnum.Bool; - break; - case Type _ when dataType == typeof(float): - plcDataType = DataTypeEnum.Float; - break; - case Type _ when dataType == typeof(double): - plcDataType = DataTypeEnum.Double; - break; - case Type _ when dataType == typeof(byte): - plcDataType = DataTypeEnum.Byte; - break; - case Type _ when dataType == typeof(short): - plcDataType = DataTypeEnum.Int16; - break; - case Type _ when dataType == typeof(ushort): - plcDataType = DataTypeEnum.UInt16; - break; - case Type _ when dataType == typeof(int): - plcDataType = DataTypeEnum.Int32; - break; - case Type _ when dataType == typeof(uint): - plcDataType = DataTypeEnum.UInt32; - break; - case Type _ when dataType == typeof(long): - plcDataType = DataTypeEnum.Int64; - break; - case Type _ when dataType == typeof(ulong): - plcDataType = DataTypeEnum.UInt64; - break; - default: - plcDataType = DataTypeEnum.None; - break; - } - - - return plcDataType; - } - - /// - /// 读取设备的值 - /// - /// - /// - /// - /// - public static object ReadToPlcValue(this SiemensClient client, PlcVarInfo varInfo) - { - Type dataType = varInfo.DataType; - object resultvalue; - if (dataType == typeof(string)) - { - var result = client.ReadString(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(char)) - { - var result = client.ReadString(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(bool)) - { - var result = client.ReadBoolean(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(float)) - { - var result = client.ReadFloat(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(double)) - { - var result = client.ReadDouble(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(byte)) - { - var result = client.ReadByte(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(short)) - { - var result = client.ReadInt16(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(ushort)) - { - var result = client.ReadUInt16(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(int)) - { - var result = client.ReadInt32(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(uint)) - { - var result = client.ReadUInt32(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(long)) - { - var result = client.ReadInt64(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(ulong)) - { - var result = client.ReadUInt64(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else - { - resultvalue = default; - } - return resultvalue; - } - - public static void WriteToPlcValue(this SiemensClient client, PlcVarInfo varInfo, object value) - { - if (client == null) throw new ArgumentNullException("client"); - Type dataType = varInfo.DataType; - Result result = null; - if (dataType == typeof(string)) - { - result = client.Write(varInfo.VarAddress, value.ToString()); - } - else if (dataType == typeof(char)) - { - result = client.Write(varInfo.VarAddress, value.ToString()); - } - else if (dataType == typeof(bool)) - { - var @bool = bool.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @bool); - } - else if (dataType == typeof(float)) - { - var @float = float.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @float); - } - else if (dataType == typeof(double)) - { - var @double = double.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @double); - } - else if (dataType == typeof(byte)) - { - var @byte = byte.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @byte); - } - else if (dataType == typeof(short)) - { - var @short = short.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @short); - } - else if (dataType == typeof(ushort)) - { - var @ushort = ushort.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @ushort); - } - else if (dataType == typeof(int)) - { - var @int = int.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @int); - } - else if (dataType == typeof(uint)) - { - var @uint = uint.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @uint); - } - else if (dataType == typeof(long)) - { - var @long = long.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @long); - } - else if (dataType == typeof(ulong)) - { - var @ulong = ulong.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @ulong); - } - if (result is null) - { - throw new Exception($"未定义的数据类型"); - } - if (!result.IsSucceed) - { - throw new Exception(result.Err); - } - } - - } -} diff --git a/Net462DllTest/Enums/FromValue.cs b/Net462DllTest/Enums/FromValue.cs index 4bd6eda..9cefffb 100644 --- a/Net462DllTest/Enums/FromValue.cs +++ b/Net462DllTest/Enums/FromValue.cs @@ -10,7 +10,6 @@ namespace Net462DllTest.Signal { public enum FromValue { - None, [BindValue(typeof(FromWorkBenchView))] FromWorkBenchView, [BindValue(typeof(TestFormView))] diff --git a/Net462DllTest/Enums/PlcVarEnum.cs b/Net462DllTest/Enums/PlcVarName.cs similarity index 53% rename from Net462DllTest/Enums/PlcVarEnum.cs rename to Net462DllTest/Enums/PlcVarName.cs index 9c85f3f..3d4408d 100644 --- a/Net462DllTest/Enums/PlcVarEnum.cs +++ b/Net462DllTest/Enums/PlcVarName.cs @@ -1,10 +1,18 @@ -using Net462DllTest.Signal; +using IoTClient.Clients.PLC; +using IoTClient.Enums; +using Net462DllTest.Trigger; +using Net462DllTest.Signal; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reflection.Emit; +using System.Reflection; using System.Text; using System.Threading.Tasks; -using static Net462DllTest.Signal.PlcValueAttribute; +using static Net462DllTest.Signal.PlcVarInfoAttribute; +using Serein.Library.Attributes; +using static Net462DllTest.Signal.PlcVarInfo; namespace Net462DllTest.Enums { @@ -12,118 +20,122 @@ namespace Net462DllTest.Enums /// - /// PLC变量 + /// PLC变量信息 /// - public enum PlcVarEnum + public enum PlcVarName { - None, /// /// 车位号 /// - [PlcValue(typeof(short), "V100", VarType.Writable)] + [PlcVarInfo(DataTypeEnum.Int16, "V100", OnNotificationType.OnChanged)] SpaceNum, /// /// 上位机指令 /// - [PlcValue(typeof(short), "V102", VarType.Writable)] + [PlcVarInfo(DataTypeEnum.Int16, "V102", OnNotificationType.OnChanged)] CmdForPLC, /// /// PLC当前存取车位号 /// - [PlcValue(typeof(short), "V110", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Int16, "V110", OnNotificationType.OnChanged)] DoingSpaceNum, /// /// 下位机状态 /// - [PlcValue(typeof(short), "V112", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Int16, "V112", OnNotificationType.OnChanged)] PLCState, /// /// 门1正常待机车位号,存车完成地面车位0 /// - [PlcValue(typeof(short), "V114", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Int16, "V114", OnNotificationType.OnChanged)] Door1CurSpaceNum, /// /// 门2正常待机车位号,存车完成地面车位0 /// - [PlcValue(typeof(short), "V124", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Int16, "V124", OnNotificationType.OnChanged)] Door2CurSpaceNum, /// /// 下位机运行模式 /// - [PlcValue(typeof(short), "V116", VarType.Writable)] + [PlcVarInfo(DataTypeEnum.Int16, "V116", OnNotificationType.OnChanged)] PLCRunMode, /// /// 执行的门号 /// - [PlcValue(typeof(short), "V104", VarType.Writable)] + [PlcVarInfo(DataTypeEnum.Int16, "V104", OnNotificationType.OnChanged)] DoorVar, /// /// 门1是否开到位 /// - [PlcValue(typeof(bool), "V207.0", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V207.0", OnNotificationType.OnChanged)] IsDoor1OpenDone, /// /// 门1是否关到位 /// - [PlcValue(typeof(bool), "V207.1", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V207.1", OnNotificationType.OnChanged)] IsDoor1ClosedDone, /// /// 门2是否开到位 /// - [PlcValue(typeof(bool), "V207.3", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V207.3", OnNotificationType.OnChanged)] IsDoor2OpenDone, /// /// 门2是否关到位 /// - [PlcValue(typeof(bool), "V207.4", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V207.4", OnNotificationType.OnChanged)] IsDoor2ClosedDone, /// /// 通道1是否有车 /// - [PlcValue(typeof(bool), "V284.7", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V284.7", OnNotificationType.OnChanged)] HasCarInTone1, /// /// 通道2是否有车 /// - [PlcValue(typeof(bool), "V286.7", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V286.7", OnNotificationType.OnChanged)] HasCarInTone2, /// /// 下位机异常代码 /// - [PlcValue(typeof(short), "V2", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Int16, "V2", OnNotificationType.OnChanged)] ErrorCode, /// /// 2层以上的空板是否在待机 /// - [PlcValue(typeof(bool), "V200.7", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "V200.7", OnNotificationType.OnChanged)] IsOver2FlowStanded, /// /// 1号门指示灯 /// - [PlcValue(typeof(bool), "Q17.0", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "Q17.0", OnNotificationType.OnChanged)] Gate1Light, /// /// 2号门指示灯 /// - [PlcValue(typeof(bool), "Q17.3", VarType.ReadOnly)] + [PlcVarInfo(DataTypeEnum.Bool, "Q17.3", OnNotificationType.OnChanged)] Gate2Light, } + + + + + } diff --git a/Net462DllTest/LogicControl/ParkingLogicControl.cs b/Net462DllTest/LogicControl/ParkingLogicControl.cs index 200309d..37aabaa 100644 --- a/Net462DllTest/LogicControl/ParkingLogicControl.cs +++ b/Net462DllTest/LogicControl/ParkingLogicControl.cs @@ -1,5 +1,6 @@ -using Net462DllTest.Device; + using Net462DllTest.Signal; +using Net462DllTest.Trigger; using Net462DllTest.ViewModel; using Serein.Library.Api; using Serein.Library.Attributes; @@ -22,7 +23,7 @@ namespace Net462DllTest.LogicControl } [AutoRegister] - [DynamicFlow] + [DynamicFlow("[Parking]")] public class ParkingLogicControl { private readonly PrakingDevice PrakingDevice; @@ -38,7 +39,7 @@ namespace Net462DllTest.LogicControl { try { - TriggerData triggerData = await PrakingDevice.CreateChannelWithTimeoutAsync(parkingCommand, TimeSpan.FromMinutes(5), 0); + TriggerData triggerData = await PrakingDevice.CreateChannelWithTimeoutAsync(parkingCommand, TimeSpan.FromMinutes(120), 0); if (triggerData.Type == TriggerType.Overtime) { throw new FlipflopException("超时取消"); @@ -65,7 +66,7 @@ namespace Net462DllTest.LogicControl } - [NodeAction(NodeType.Action, "手动触发模拟调取车位")] + [NodeAction(NodeType.Action, "调取指定车位")] public void Storage(string spaceNum = "101") { if (PrakingDevice.TriggerSignal(ParkingCommand.GetPparkingSpace, spaceNum)) @@ -76,11 +77,7 @@ namespace Net462DllTest.LogicControl else { Console.WriteLine("发送命令失败:调取车位" + spaceNum); - } } - - - } } diff --git a/Net462DllTest/LogicControl/PlcLogicControl.cs b/Net462DllTest/LogicControl/PlcLogicControl.cs index 6624f20..52c8b36 100644 --- a/Net462DllTest/LogicControl/PlcLogicControl.cs +++ b/Net462DllTest/LogicControl/PlcLogicControl.cs @@ -1,8 +1,8 @@ using IoTClient.Clients.PLC; using IoTClient.Common.Enums; -using Net462DllTest.Device; using Net462DllTest.Enums; using Net462DllTest.Signal; +using Net462DllTest.Trigger; using Net462DllTest.Web; using Serein.Library.Api; using Serein.Library.Attributes; @@ -20,8 +20,17 @@ using System.Threading.Tasks; namespace Net462DllTest.LogicControl { + [AttributeUsage(AttributeTargets.Class)] + public sealed class AutoSocketAttribute : Attribute + { + public string BusinessField; + } + + + + [AutoRegister] - [DynamicFlow] + [DynamicFlow("[SiemensPlc]")] public class PlcLogicControl { private readonly SiemensPlcDevice MyPlc; @@ -29,8 +38,6 @@ namespace Net462DllTest.LogicControl public PlcLogicControl(SiemensPlcDevice MyPlc) { this.MyPlc = MyPlc; - - } #region 初始化、初始化完成以及退出的事件 @@ -39,7 +46,9 @@ namespace Net462DllTest.LogicControl { context.Env.IOC.Register(); context.Env.IOC.Register(); - + + //context.Env.IOC.Register(); + //context.Env.IOC.Register(); } [NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作 @@ -47,16 +56,13 @@ namespace Net462DllTest.LogicControl { // 注册控制器 context.Env.IOC.Run((router, web) => { - try - { - router.RegisterController(typeof(CommandController)); - web.Start("http://*:8089/"); // 开启 Web 服务 - } - catch (Exception ex) - { - Console.WriteLine(ex); - } + router.RegisterController(typeof(CommandController)); + web.Start("http://*:8089/"); // 开启 Web 服务 }); + + //context.Env.IOC.Run(server => { + // server.Start(5000); // 开启 Socket 监听 + //}); } [NodeAction(NodeType.Exit)] // 流程结束时自动执行 @@ -66,7 +72,7 @@ namespace Net462DllTest.LogicControl { web?.Stop(); // 关闭 Web 服务 }); - MyPlc.ResetDevice(); + MyPlc.Close(); MyPlc.CancelAllTasks(); } @@ -74,17 +80,17 @@ namespace Net462DllTest.LogicControl #region 触发器节点 - [NodeAction(NodeType.Flipflop, "等待信号触发", ReturnType = typeof(int))] - public async Task WaitTask(CommandSignal order = CommandSignal.Command_1) + [NodeAction(NodeType.Flipflop, "等待变量更新", ReturnType = typeof(int))] + public async Task WaitTask(PlcVarName varName = PlcVarName.ErrorCode) { try { - TriggerData triggerData = await MyPlc.CreateChannelWithTimeoutAsync(order, TimeSpan.FromMinutes(5), 0); + TriggerData triggerData = await MyPlc.CreateChannelWithTimeoutAsync(varName, TimeSpan.FromMinutes(120), 0); if (triggerData.Type == TriggerType.Overtime) { throw new FlipflopException("超时取消"); } - //int.TryParse(triggerData.Value.ToString(),out int result); + await Console.Out.WriteLineAsync($"PLC变量触发器[{varName}]传递数据:{triggerData.Value}"); return new FlipflopContext(FlipflopStateType.Succeed, triggerData.Value); } catch (FlipflopException) @@ -102,11 +108,13 @@ namespace Net462DllTest.LogicControl #region 动作节点 - [NodeAction(NodeType.Action, "初始化")] + [NodeAction(NodeType.Action, "PLC初始化")] public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None, string ip = "192.168.10.100", int port = 102) { + //MyPlc.Model.Set(PlcVarName.DoorVar,1); + //MyPlc.Model.Value.SpaceNum = 1; if (MyPlc.Client is null) { try @@ -129,41 +137,34 @@ namespace Net462DllTest.LogicControl [NodeAction(NodeType.Action, "设置PLC状态")] public SiemensPlcDevice SetState(PlcState state = PlcState.PowerOff) { - if(MyPlc.Client != null) - { - var oldState = MyPlc.State; - MyPlc.State = state; - Console.WriteLine($"PLC状态从[{oldState}]转为[{state}]"); - return MyPlc; - } - else - { - Console.WriteLine($"PLC尚未初始化"); - return MyPlc; - } + var oldState = MyPlc.State; + MyPlc.State = state; + Console.WriteLine($"PLC状态从[{oldState}]转为[{state}]"); + return MyPlc; } [NodeAction(NodeType.Action, "PLC获取变量")] - public object ReadVar(PlcVarEnum plcVarEnum) + public object ReadVar(PlcVarName plcVarEnum) { - var varInfo = ToVarInfo(plcVarEnum); + var varInfo = plcVarEnum.ToVarInfo(); var result = MyPlc.Read(varInfo); Console.WriteLine($"获取变量成功:({varInfo})\t result = {result}"); return result; } [NodeAction(NodeType.Action, "PLC写入变量")] - public SiemensPlcDevice WriteVar2(object value, PlcVarEnum plcVarEnum) + public SiemensPlcDevice WriteVar2(object value, PlcVarName varName) { - var varInfo = ToVarInfo(plcVarEnum); + var varInfo = varName.ToVarInfo(); if (MyPlc.State == PlcState.Runing) { - if (varInfo.IsProtected) + if (varInfo.IsReadOnly) { Console.WriteLine($"PLC变量{varInfo}当前禁止写入"); } else { + MyPlc.Write(varInfo, value); Console.WriteLine($"PLC变量{varInfo}写入数据:{value}"); } @@ -175,30 +176,24 @@ namespace Net462DllTest.LogicControl return MyPlc; } - /// - /// 缓存变量信息 - /// - private readonly Dictionary VarInfoDict = new Dictionary(); - - private PlcVarInfo ToVarInfo(PlcVarEnum plcVarEnum) + + [NodeAction(NodeType.Action, "批量读取")] + public void BatchReadVar() { - if (VarInfoDict.ContainsKey(plcVarEnum)) - { - return VarInfoDict[plcVarEnum]; - } - if (plcVarEnum == PlcVarEnum.None) - { - throw new Exception("非预期枚举值"); - } - var plcValue = EnumHelper.GetBoundValue(plcVarEnum, attr => attr.PlcInfo) - ?? throw new Exception($"获取变量异常:{plcVarEnum},没有标记PlcValueAttribute"); - if (string.IsNullOrEmpty(plcValue.VarAddress)) - { - throw new Exception($"获取变量异常:{plcVarEnum},变量地址为空"); - } - VarInfoDict.Add(plcVarEnum, plcValue); - return plcValue; + MyPlc.BatchRefresh(); } + [NodeAction(NodeType.Action, "开启定时刷新")] + public void OpenTimedRefresh() + { + Task.Run(async () => await MyPlc.OpenTimedRefreshAsync()); + } + + [NodeAction(NodeType.Action, "关闭定时刷新")] + public void CloseTimedRefresh() + { + MyPlc.CloseTimedRefresh(); + } + #endregion } diff --git a/Net462DllTest/LogicControl/ViewLogicControl.cs b/Net462DllTest/LogicControl/ViewLogicControl.cs index 6a47e59..bb09dc8 100644 --- a/Net462DllTest/LogicControl/ViewLogicControl.cs +++ b/Net462DllTest/LogicControl/ViewLogicControl.cs @@ -1,25 +1,29 @@ -using Net462DllTest.Device; + using Net462DllTest.Signal; using Net462DllTest.ViewModel; using Serein.Library.Api; using Serein.Library.Attributes; using Serein.Library.Enums; +using Serein.Library.Ex; +using Serein.Library.Framework.NodeFlow; +using Serein.Library.NodeFlow.Tool; using Serein.Library.Utils; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; -namespace Net462DllTest.LogicControl +namespace Net462DllTest.LogicControl { /// /// 视图管理 /// [AutoRegister] - public class ViewManagement + public class ViewManagement:ChannelFlowTrigger { - private List
forms = new List(); + private readonly List forms = new List(); /// /// 打开窗口 /// @@ -46,7 +50,7 @@ namespace Net462DllTest.LogicControl - [DynamicFlow] + [DynamicFlow("[View]")] public class ViewLogicControl { private readonly ViewManagement ViewManagement; @@ -59,12 +63,36 @@ namespace Net462DllTest.LogicControl public void Init(IDynamicContext context) { context.Env.IOC.Register(); - context.Env.IOC.Register(); } + #region 触发器节点 + [NodeAction(NodeType.Flipflop, "等待信号触发", ReturnType = typeof(int))] + public async Task WaitTask(CommandSignal command = CommandSignal.Command_1) + { + try + { + TriggerData triggerData = await ViewManagement.CreateChannelWithTimeoutAsync(command, TimeSpan.FromMinutes(120), 0); + if (triggerData.Type == TriggerType.Overtime) + { + throw new FlipflopException("超时取消"); + } + return new FlipflopContext(FlipflopStateType.Succeed, triggerData.Value); + } + catch (FlipflopException) + { + throw; + } + catch (Exception) + { + return new FlipflopContext(FlipflopStateType.Error); + } + + } + + #endregion [NodeAction(NodeType.Action, "打开窗体(指定枚举值)")] - public void OpenForm(IDynamicContext context, FromValue fromId = FromValue.None, bool isTop = true) + public void OpenForm(IDynamicContext context, FromValue fromId = FromValue.FromWorkBenchView, bool isTop = true) { var fromType = EnumHelper.GetBoundValue(fromId, attr => attr.Value); if (fromType is null) return; @@ -83,7 +111,7 @@ namespace Net462DllTest.LogicControl [NodeAction(NodeType.Action, "关闭窗体")] - public void CloseForm(IDynamicContext context, FromValue fromId = FromValue.None) + public void CloseForm(IDynamicContext context, FromValue fromId = FromValue.FromWorkBenchView) { var fromType = EnumHelper.GetBoundValue(fromId, attr => attr.Value); if (fromType is null) return; diff --git a/Net462DllTest/Model/PlcVarModel.cs b/Net462DllTest/Model/PlcVarModel.cs new file mode 100644 index 0000000..e0222f7 --- /dev/null +++ b/Net462DllTest/Model/PlcVarModel.cs @@ -0,0 +1,144 @@ +using Net462DllTest.Enums; +using Net462DllTest.Trigger; +using Serein.Library.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Net462DllTest.Model +{ + + [AttributeUsage(AttributeTargets.Property)] + public class PlcValueAttribute : Attribute + { + /// + /// 变量类型 + /// + public PlcVarName PlcVarEnum { get; } + public PlcValueAttribute(PlcVarName plcVarEnum) + { + this.PlcVarEnum = plcVarEnum; + } + } + + + /// + /// PLC变量 + /// + [AutoRegister] + public class PlcVarModel + { + /// + /// 车位号 + /// + [BindValue(PlcVarName.SpaceNum)] + public Int16 SpaceNum { get; set; } + + /// + /// 上位机指令 + /// + [BindValue(PlcVarName.CmdForPLC)] + public Int16 CmdForPLC { get; set; } + + /// + /// PLC当前存取车位号 + /// + [BindValue(PlcVarName.DoingSpaceNum)] + public Int16 DoingSpaceNum { get; set; } + + /// + /// 下位机状态 + /// + [BindValue(PlcVarName.PLCState)] + public Int16 PLCState { get; set; } + + /// + /// 门1正常待机车位号,存车完成地面车位0 + /// + [BindValue(PlcVarName.Door1CurSpaceNum)] + public Int16 Door1CurSpaceNum { get; set; } + + /// + /// 门2正常待机车位号,存车完成地面车位0 + /// + [BindValue(PlcVarName.Door2CurSpaceNum)] + public Int16 Door2CurSpaceNum { get; set; } + + /// + /// 下位机运行模式 + /// + [BindValue(PlcVarName.PLCRunMode)] + public Int16 PLCRunMode { get; set; } + + /// + /// 执行的门号 + /// + [BindValue(PlcVarName.DoorVar)] + public Int16 DoorVar { get; set; } + + /// + /// 门1是否开到位 + /// + [BindValue(PlcVarName.IsDoor1OpenDone)] + public bool IsDoor1OpenDone { get; set; } + + /// + /// 门1是否关到位 + /// + [BindValue(PlcVarName.IsDoor1ClosedDone)] + public bool IsDoor1ClosedDone { get; set; } + + + /// + /// 门2是否开到位 + /// + [BindValue(PlcVarName.IsDoor2OpenDone)] + public bool IsDoor2OpenDone { get; set; } + + /// + /// 门2是否关到位 + /// + [BindValue(PlcVarName.IsDoor2ClosedDone)] + public bool IsDoor2ClosedDone { get; set; } + + /// + /// 通道1是否有车 + /// + [BindValue(PlcVarName.HasCarInTone1)] + public bool HasCarInTone1 { get; set; } + + /// + /// 通道2是否有车 + /// + [BindValue(PlcVarName.HasCarInTone2)] + public bool HasCarInTone2 { get; set; } + + /// + /// 下位机异常代码 + /// + [BindValue(PlcVarName.ErrorCode)] + public Int16 ErrorCode { get; set; } + + /// + /// 2层以上的空板是否在待机 + /// + [BindValue(PlcVarName.IsOver2FlowStanded)] + public bool IsOver2FlowStanded { get; set; } + + /// + /// 1号门指示灯 + /// + [BindValue(PlcVarName.Gate1Light)] + public bool Gate1Light { get; set; } + + /// + /// 2号门指示灯 + /// + [BindValue(PlcVarName.Gate2Light)] + public bool Gate2Light { get; set; } + } + + +} diff --git a/Net462DllTest/Net462DllTest.csproj b/Net462DllTest/Net462DllTest.csproj index 50a2c4a..29a2c0e 100644 --- a/Net462DllTest/Net462DllTest.csproj +++ b/Net462DllTest/Net462DllTest.csproj @@ -60,17 +60,19 @@ - - + + + - + - + + Form @@ -84,7 +86,7 @@ TestFormView.cs - + diff --git a/Net462DllTest/Signal/PLCVarSignal.cs b/Net462DllTest/Signal/PLCVarSignal.cs index e618fc0..03ca5e2 100644 --- a/Net462DllTest/Signal/PLCVarSignal.cs +++ b/Net462DllTest/Signal/PLCVarSignal.cs @@ -1,62 +1,96 @@ -using Serein.Library.Attributes; +using IoTClient.Enums; +using Net462DllTest.Enums; +using Serein.Library.Attributes; using System; -using static Net462DllTest.Signal.PlcValueAttribute; +using static Net462DllTest.Signal.PlcVarInfoAttribute; +using static Net462DllTest.Signal.PlcVarInfo; namespace Net462DllTest.Signal { [AttributeUsage(AttributeTargets.Field)] - public class PlcValueAttribute : Attribute + public class PlcVarInfoAttribute : Attribute { - /// - /// 变量类型 - /// - public enum VarType - { - /// - /// 只读取的值 - /// - ReadOnly, - /// - /// 可写入的值 - /// - Writable, - } - - /// - /// 变量属性 - /// - public PlcVarInfo PlcInfo { get; } + public PlcVarInfo Info { get; } - - public PlcValueAttribute(Type type, - string @var, - VarType varType + /// + /// + /// + /// 数据类型 + /// 变量地址 + /// 通知行为类型 + /// 是否只读 + /// 是否定时刷新 + /// 刷新间隔(ms) + public PlcVarInfoAttribute(DataTypeEnum dateType, + string address, + OnNotificationType notificationType, + bool isReadOnly = false, + bool isTimingRead = true, + int interval = 1000 ) { - PlcInfo = new PlcVarInfo(type, var, varType); + Info = new PlcVarInfo() + { + DataType = dateType, + IsReadOnly = isReadOnly, + Address = address, + Interval = interval, + IsTimingRead= isTimingRead, + NotificationType = notificationType, + }; } } public class PlcVarInfo { - public PlcVarInfo(Type type, - string @var, - VarType varType - ) + public enum OnNotificationType { - DataType = type; - VarAddress = @var; - Type = varType; + /// + /// 刷新时通知(每次写入Model后都会触发相应的触发器) + /// + OnRefresh, + /// + /// 改变时才通知(与Model对应数据不一致时才会触发相应的触发器) + /// + OnChanged, } - public bool IsProtected { get; } - public Type DataType { get; } - public string VarAddress { get; } - public VarType Type { get; } + /// + /// 变量类型 + /// + public PlcVarName Name { get; set; } + /// + /// 数据类型 + /// + public DataTypeEnum DataType { get; set; } + /// + /// 变量地址 + /// + public string Address { get; set; } + /// + /// 变量是否只读 + /// + public bool IsReadOnly { get; set; } + /// + /// 是否定时刷新 + /// + public bool IsTimingRead { get; set; } + /// + /// 刷新间隔(ms) + /// + public int Interval { get; set; } = 100; // 100ms + public OnNotificationType NotificationType { get; set; } = OnNotificationType.OnChanged; public override string ToString() { - return $"数据类型:{DataType} 地址:{VarAddress}"; + if (IsTimingRead) + { + return $"数据:{Name},类型:{DataType},地址:{Address},只读:{IsReadOnly},自动刷新:{IsTimingRead},刷新间隔:{Interval}"; + } + else + { + return $"数据:{Name},类型:{DataType},地址:{Address},只读:{IsReadOnly}"; + } } } diff --git a/Net462DllTest/Device/PrakingDevice.cs b/Net462DllTest/Trigger/PrakingDevice.cs similarity index 91% rename from Net462DllTest/Device/PrakingDevice.cs rename to Net462DllTest/Trigger/PrakingDevice.cs index a76cf8b..9da1b17 100644 --- a/Net462DllTest/Device/PrakingDevice.cs +++ b/Net462DllTest/Trigger/PrakingDevice.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Net462DllTest.Device +namespace Net462DllTest.Trigger { [AutoRegister] public class PrakingDevice : ChannelFlowTrigger diff --git a/Net462DllTest/Trigger/SiemensPlcDevice.cs b/Net462DllTest/Trigger/SiemensPlcDevice.cs new file mode 100644 index 0000000..e2b075a --- /dev/null +++ b/Net462DllTest/Trigger/SiemensPlcDevice.cs @@ -0,0 +1,465 @@ +using IoTClient; +using IoTClient.Clients.PLC; +using IoTClient.Common.Enums; +using IoTClient.Enums; +using Net462DllTest.Enums; +using Net462DllTest.Signal; +using Net462DllTest.Utils; +using Serein.Library.Attributes; +using Serein.Library.NodeFlow.Tool; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; +using System.Reflection; +using Net462DllTest.Model; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar; +using System.Linq; + +namespace Net462DllTest.Trigger +{ + + + [AutoRegister] + public class SiemensPlcDevice : ChannelFlowTrigger + { + public SiemensClient Client { get; set; } + public SiemensVersion Version { get; set; } + public string IP { get; set; } = "127.0.0.1"; + public int Port { get; set; } = 102; + public PlcState State { get; set; } = PlcState.PowerOff; + public bool IsTimedRefresh { get; set; } = false; // 是否定时刷新 + public IGSModel Model { get; } + + + private readonly object _lockObj = new object(); // 防止多次初始化读取任务 + private readonly ConcurrentBag TimedRefreshTask = new ConcurrentBag(); // 定时读取任务 + private readonly ConcurrentBag VarInfos = new ConcurrentBag(); // 所有变量信息 + private readonly ConcurrentBag OnRefreshs = new ConcurrentBag(); // 数据变更后需要通知触发器的变量信息 + private readonly ConcurrentBag OnChangeds = new ConcurrentBag(); // 读取读取后需要通知触发器的变量信息 + + public SiemensPlcDevice(PlcVarModel model) + { + this.Model = new GSModel(model); + LoadVarInfos(); + } + + /// + /// 加载变量信息 + /// + private void LoadVarInfos() + { + foreach (var property in typeof(PlcVarModel).GetProperties()) + { + var attribute = property.GetCustomAttribute(); + if (attribute?.Value is PlcVarName varName) + { + var varInfo = varName.ToVarInfo(); + VarInfos.Add(varInfo); + if (varInfo.IsTimingRead) // 是否定时刷新 + { + switch (varInfo.NotificationType) + { + case PlcVarInfo.OnNotificationType.OnRefresh: + OnRefreshs.Add(varInfo); // 变量读取时通知触发器的变量 + break; + case PlcVarInfo.OnNotificationType.OnChanged: + OnChangeds.Add(varInfo); // 变量变更时才需要通知触发器的变量 + break; + } + } + } + } + } + + public void Init(SiemensVersion version, string ip, int port) + { + Client = new SiemensClient(version, ip, port); + Version = version; + IP = ip; + Port = port; + Client.Open(); + } + + public void Close() + { + Client?.Close(); + Client = null; + } + + public void Write(PlcVarInfo varInfo, object value) + { + try + { + Client.WriteVar(varInfo, value); // 尝试写入PLC + Model.Set(varInfo.Name, value); // 新数据 + } + catch (Exception ex) + { + Console.WriteLine($"写入出错:{this}{varInfo}。{ex.Message}"); + throw; + } + } + + public object Read(PlcVarInfo varInfo) + { + try + { + var result = Client.ReadVar(varInfo);// 尝试读取数据 + Model.Set(varInfo.Name, result);// 缓存读取的数据 + return result; + } + catch (Exception ex) + { + Console.WriteLine($"读取出错:{this}{varInfo}。{ex.Message}"); + throw; + } + + } + + public void BatchRefresh() + { + foreach(var varInfo in VarInfos) + { + Read(varInfo); // 无条件批量读取 + } + } + + + /// + /// 开启定时批量读取任务 + /// + /// + public async Task OpenTimedRefreshAsync() + { + if (TimedRefreshTask.IsEmpty) + { + InitTimedRefreshTask(); + } + IsTimedRefresh = true; + await Task.WhenAll(TimedRefreshTask.ToArray()); + } + + /// + /// 关闭定时任务 + /// + public void CloseTimedRefresh() => IsTimedRefresh = false; + + /// + /// 初始化定时刷新任务 + /// + public void InitTimedRefreshTask() + { + Console.WriteLine("开始初始化刷新任务"); + lock (_lockObj) + { + foreach (var varInfo in OnChangeds) + { + Console.WriteLine($"添加监视任务(OnChanged):{varInfo.Name}"); + ScheduleTask(varInfo); + } + foreach (var varInfo in OnRefreshs) + { + Console.WriteLine($"添加监视任务(OnRefresh):{varInfo.Name}"); + ScheduleTask(varInfo); + } + } + } + + /// + /// 定时读取PLC变量,并刷新Model的值 + /// + /// + private void ScheduleTask(PlcVarInfo varInfo) + { + var task = Task.Run(async () => + { + var signal = varInfo.Name; + object oldData; + object newData; + bool isNotification; + while (true) + { + await Task.Delay(varInfo.Interval); + if (!IsTimedRefresh || Client is null) break; + + oldData = Model.Get(signal); // 暂存旧数据 + newData = Read(varInfo); // 获取新数据 + if (varInfo.NotificationType == PlcVarInfo.OnNotificationType.OnRefresh) + { + isNotification = true; // 无条件触发通知 + } + else + { + isNotification = !oldData.Equals(newData); // 变更时才会触发通知 + } + + if (isNotification) + { + Console.WriteLine($"VarName: {signal}\t\tOld Data: {oldData}\tNew Data: {newData}"); + TriggerSignal(signal, newData); + } + + + } + }); + TimedRefreshTask.Add(task); + } + + public override string ToString() + { + return $"西门子Plc[{this.Version}-{this.IP}:{this.Port}]"; + } + + } + + + + + /// + /// 拓展方法 + /// + public static class MyPlcExtension + { + /// + /// 缓存变量信息 + /// + private static readonly Dictionary VarInfoDict = new Dictionary(); + + /// + /// 获取变量信息 + /// + /// + /// + /// + public static PlcVarInfo ToVarInfo(this PlcVarName plcVarEnum) + { + if (VarInfoDict.ContainsKey(plcVarEnum)) + { + return VarInfoDict[plcVarEnum]; + } + var plcValue = EnumHelper.GetBoundValue(plcVarEnum, attr => attr.Info) + ?? throw new Exception($"获取变量异常:{plcVarEnum},没有标记PlcValueAttribute"); + if (string.IsNullOrEmpty(plcValue.Address)) + { + throw new Exception($"获取变量异常:{plcVarEnum},变量地址为空"); + } + VarInfoDict.Add(plcVarEnum, plcValue); + plcValue.Name = plcVarEnum; + return plcValue; + } + + /// + /// 读取PLC + /// + public static object ReadVar(this SiemensClient client, PlcVarInfo varInfo) + { + if (client is null) + { + throw new ArgumentNullException($"PLC尚未初始化"); + } + object resultvalue; + switch (varInfo.DataType) + { + case DataTypeEnum.String: + var resultString = client.ReadString(varInfo.Address); + if (!resultString.IsSucceed) throw new Exception(resultString.Err); + resultvalue = resultString.Value; + break; + case DataTypeEnum.Bool: + var resultBool = client.ReadBoolean(varInfo.Address); + if (!resultBool.IsSucceed) throw new Exception(resultBool.Err); + resultvalue = resultBool.Value; + break; + case DataTypeEnum.Float: + var resultFloat = client.ReadFloat(varInfo.Address); + if (!resultFloat.IsSucceed) throw new Exception(resultFloat.Err); + resultvalue = resultFloat.Value; + break; + case DataTypeEnum.Double: + var resultDouble = client.ReadDouble(varInfo.Address); + if (!resultDouble.IsSucceed) throw new Exception(resultDouble.Err); + resultvalue = resultDouble.Value; + break; + case DataTypeEnum.Byte: + var resultByte = client.ReadByte(varInfo.Address); + if (!resultByte.IsSucceed) throw new Exception(resultByte.Err); + resultvalue = resultByte.Value; + break; + case DataTypeEnum.Int16: + var resultInt16 = client.ReadInt16(varInfo.Address); + if (!resultInt16.IsSucceed) throw new Exception(resultInt16.Err); + resultvalue = resultInt16.Value; + break; + case DataTypeEnum.UInt16: + var resultUint16 = client.ReadUInt16(varInfo.Address); + if (!resultUint16.IsSucceed) throw new Exception(resultUint16.Err); + resultvalue = resultUint16.Value; + break; + case DataTypeEnum.Int32: + var resultInt32 = client.ReadInt32(varInfo.Address); + if (!resultInt32.IsSucceed) throw new Exception(resultInt32.Err); + resultvalue = resultInt32.Value; + break; + case DataTypeEnum.UInt32: + var resultUInt32 = client.ReadUInt32(varInfo.Address); + if (!resultUInt32.IsSucceed) throw new Exception(resultUInt32.Err); + resultvalue = resultUInt32.Value; + break; + case DataTypeEnum.Int64: + var resultInt64 = client.ReadInt64(varInfo.Address); + if (!resultInt64.IsSucceed) throw new Exception(resultInt64.Err); + resultvalue = resultInt64.Value; + break; + case DataTypeEnum.UInt64: + var resultUInt64 = client.ReadUInt64(varInfo.Address); + if (!resultUInt64.IsSucceed) throw new Exception(resultUInt64.Err); + resultvalue = resultUInt64.Value; + break; + default: + throw new NotImplementedException($"变量为指定数据类型,或是非预期的数据类型{varInfo}"); + } + return resultvalue; + } + /// + /// 转换数据类型,写入PLC + /// + public static object WriteVar(this SiemensClient client, PlcVarInfo varInfo, object value) + { + if (client is null) + { + throw new ArgumentNullException($"PLC尚未初始化"); + } + DataTypeEnum dataType = varInfo.DataType; + object convertValue; + Result result; + switch (dataType) + { + case DataTypeEnum.String: + var @string = value.ToString(); + convertValue = @string; + result = client.Write(varInfo.Address, @string); + break; + case DataTypeEnum.Bool: + var @bool = bool.Parse(value.ToString()); + convertValue = @bool; + result = client.Write(varInfo.Address, @bool); + break; + case DataTypeEnum.Float: + var @float = float.Parse(value.ToString()); + convertValue = @float; + result = client.Write(varInfo.Address, @float); + break; + case DataTypeEnum.Double: + var @double = double.Parse(value.ToString()); + convertValue = @double; + result = client.Write(varInfo.Address, @double); + break; + case DataTypeEnum.Byte: + var @byte = byte.Parse(value.ToString()); + convertValue = @byte; + result = client.Write(varInfo.Address, @byte); + break; + case DataTypeEnum.Int16: + var @short = short.Parse(value.ToString()); + convertValue = @short; + result = client.Write(varInfo.Address, @short); + break; + case DataTypeEnum.UInt16: + var @ushort = ushort.Parse(value.ToString()); + convertValue = @ushort; + result = client.Write(varInfo.Address, @ushort); + break; + case DataTypeEnum.Int32: + var @int = int.Parse(value.ToString()); + convertValue = @int; + result = client.Write(varInfo.Address, @int); + break; + case DataTypeEnum.UInt32: + var @uint = uint.Parse(value.ToString()); + convertValue = @uint; + result = client.Write(varInfo.Address, @uint); + break; + case DataTypeEnum.Int64: + var @long = long.Parse(value.ToString()); + convertValue = @long; + result = client.Write(varInfo.Address, @long); + break; + case DataTypeEnum.UInt64: + var @ulong = ulong.Parse(value.ToString()); + convertValue = @ulong; + result = client.Write(varInfo.Address, @ulong); + break; + default: + throw new NotImplementedException($"变量为指定数据类型,或是非预期的数据类型{varInfo}"); + } + + if (result.IsSucceed) + { + return convertValue; + } + else + { + throw new Exception(result.Err); + } + } + + + + + /// + /// 类型转换 + /// + /// + /// + /// + //public static Type ToDataType(this DataTypeEnum dataType) + //{ + // Type plcDataType; + // switch (dataType) + // { + // case DataTypeEnum.String: + // plcDataType = typeof(string); + // break; + // case DataTypeEnum.Bool: + // plcDataType = typeof(bool); + // break; + // case DataTypeEnum.Float: + // plcDataType = typeof(float); + // break; + // case DataTypeEnum.Double: + // plcDataType = typeof(double); + // break; + // case DataTypeEnum.Byte: + // plcDataType = typeof(byte); + // break; + // case DataTypeEnum.Int16: + // plcDataType = typeof(short); + // break; + // case DataTypeEnum.UInt16: + // plcDataType = typeof(ushort); + // break; + // case DataTypeEnum.Int32: + // plcDataType = typeof(int); + // break; + // case DataTypeEnum.UInt32: + // plcDataType = typeof(uint); + // break; + // case DataTypeEnum.Int64: + // plcDataType = typeof(long); + // break; + // case DataTypeEnum.UInt64: + // plcDataType = typeof(ulong); + // break; + // default: + // throw new NotImplementedException(); + // } + // return plcDataType; + //} + + } + + +} diff --git a/Net462DllTest/Utils/GSModel.cs b/Net462DllTest/Utils/GSModel.cs new file mode 100644 index 0000000..a94919c --- /dev/null +++ b/Net462DllTest/Utils/GSModel.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Serein.Library.Attributes; + +namespace Net462DllTest.Utils +{ + public interface IGSModel + { + TModel Value { get; set; } + void Set(TKey tEnum, object value); + object Get(TKey tEnum); + } + + /// + /// 通过 Emit 创建 set/get 委托 + /// + public class GSModel : IGSModel + where TKey : struct, Enum + where TModel : class + { + public TModel Value { get; set; } + public GSModel(TModel Model) + { + this.Value = Model; + } + // 缓存创建好的setter和getter委托 + private readonly Dictionary> _setterCache = new Dictionary>(); + private readonly Dictionary> _getterCache = new Dictionary>(); + + // 动态创建调用Setter方法 + private Action CreateSetter(PropertyInfo property) + { + var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true); + var il = method.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue) + il.Emit(OpCodes.Ldarg_1); // 加载值(object) + + if (property.PropertyType.IsValueType) + { + il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型 + } + else + { + il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换 + } + + il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法 + il.Emit(OpCodes.Ret); // 返回 + + return (Action)method.CreateDelegate(typeof(Action)); + } + + /// + /// 动态创建调用Getter方法 + /// + /// + /// + private Func CreateGetter(PropertyInfo property) + { + var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true); + var il = method.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue) + il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法 + + if (property.PropertyType.IsValueType) + { + il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱 + } + + il.Emit(OpCodes.Ret); // 返回 + + return (Func)method.CreateDelegate(typeof(Func)); + } + + public void Set(TKey tEnum, object value) + { + if (!_setterCache.TryGetValue(tEnum, out var setter)) + { + PropertyInfo property = GetPropertyByEnum(tEnum); + if (property == null) + { + _setterCache[tEnum] = (s, o) => throw new ArgumentException($"没有对应的Model属性{{{tEnum}"); + //throw new ArgumentException($"Property not found for {plcVarEnum}"); + } + else + { + // 创建并缓存setter委托 + setter = CreateSetter(property); + _setterCache[tEnum] = setter; + } + } + + // 使用缓存的setter委托设置值 + setter(Value, value); + } + + public object Get(TKey tEnum) + { + if (!_getterCache.TryGetValue(tEnum, out var getter)) + { + PropertyInfo property = GetPropertyByEnum(tEnum); + if (property == null) + { + _setterCache[tEnum] = (s, o) => throw new ArgumentException($"没有对应的Model属性{tEnum}"); + } + else + { + // 创建并缓存getter委托 + getter = CreateGetter(property); + _getterCache[tEnum] = getter; + } + + } + + // 使用缓存的getter委托获取值 + return getter(Value); + } + + private PropertyInfo GetPropertyByEnum(TKey tEnum) + { + foreach (var property in typeof(TModel).GetProperties()) + { + var attribute = property.GetCustomAttribute(); + if (attribute?.Value?.GetType()?.IsEnum == true) + { + if (attribute.Value is TKey @enum && @enum.Equals(tEnum)) + { + return property; + } + } + + } + return null; + } + } +} diff --git a/Net462DllTest/Utils/RelayCommand.cs b/Net462DllTest/Utils/RelayCommand.cs new file mode 100644 index 0000000..e3a77f3 --- /dev/null +++ b/Net462DllTest/Utils/RelayCommand.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Net462DllTest.Utils +{ + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute; + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter = null) + { + return _canExecute == null || _canExecute(parameter); + } + + public void Execute(object parameter = null) + { + _execute(parameter); + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Net462DllTest/Utils/ToValue.cs b/Net462DllTest/Utils/ToValue.cs deleted file mode 100644 index aea18bd..0000000 --- a/Net462DllTest/Utils/ToValue.cs +++ /dev/null @@ -1,231 +0,0 @@ -using IoTClient; -using IoTClient.Clients.PLC; -using IoTClient.Enums; -using Net462DllTest.Signal; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace Net462DllTest.Utils -{ - internal static class MyPlcExtension - { - public static DataTypeEnum ToDataTypeEnum(this PlcVarInfo varInfo) - { - Type dataType = varInfo.DataType; - DataTypeEnum plcDataType; - switch (dataType) - { - case Type _ when dataType == typeof(string): - plcDataType = DataTypeEnum.String; - break; - case Type _ when dataType == typeof(char): - plcDataType = DataTypeEnum.String; - break; - case Type _ when dataType == typeof(bool): - plcDataType = DataTypeEnum.Bool; - break; - case Type _ when dataType == typeof(float): - plcDataType = DataTypeEnum.Float; - break; - case Type _ when dataType == typeof(double): - plcDataType = DataTypeEnum.Double; - break; - case Type _ when dataType == typeof(byte): - plcDataType = DataTypeEnum.Byte; - break; - case Type _ when dataType == typeof(short): - plcDataType = DataTypeEnum.Int16; - break; - case Type _ when dataType == typeof(ushort): - plcDataType = DataTypeEnum.UInt16; - break; - case Type _ when dataType == typeof(int): - plcDataType = DataTypeEnum.Int32; - break; - case Type _ when dataType == typeof(uint): - plcDataType = DataTypeEnum.UInt32; - break; - case Type _ when dataType == typeof(long): - plcDataType = DataTypeEnum.Int64; - break; - case Type _ when dataType == typeof(ulong): - plcDataType = DataTypeEnum.UInt64; - break; - default: - plcDataType = DataTypeEnum.None; - break; - } - - - return plcDataType; - } - - /// - /// 读取设备的值 - /// - /// - /// - /// - /// - public static object ReadToPlcValue(this SiemensClient client,PlcVarInfo varInfo) - { - Type dataType = varInfo.DataType; - object resultvalue; - if (dataType == typeof(string)) - { - var result = client.ReadString(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(char)) - { - var result = client.ReadString(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(bool)) - { - var result = client.ReadBoolean(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(float)) - { - var result = client.ReadFloat(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(double)) - { - var result = client.ReadDouble(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(byte)) - { - var result = client.ReadByte(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(short)) - { - var result = client.ReadInt16(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(ushort)) - { - var result = client.ReadUInt16(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(int)) - { - var result = client.ReadInt32(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(uint)) - { - var result = client.ReadUInt32(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(long)) - { - var result = client.ReadInt64(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else if (dataType == typeof(ulong)) - { - var result = client.ReadUInt64(varInfo.VarAddress); - if (!result.IsSucceed) throw new Exception(result.Err); - resultvalue = result.Value; - } - else - { - resultvalue = default; - } - return resultvalue; - } - - public static void WriteToPlcValue(this SiemensClient client, PlcVarInfo varInfo ,object value) - { - if(client == null) throw new ArgumentNullException("client"); - Type dataType = varInfo.DataType; - Result result = null; - if (dataType == typeof(string)) - { - result = client.Write(varInfo.VarAddress, value.ToString()); - } - else if (dataType == typeof(char)) - { - result = client.Write(varInfo.VarAddress, value.ToString()); - } - else if (dataType == typeof(bool)) - { - var @bool = bool.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @bool); - } - else if (dataType == typeof(float)) - { - var @float = float.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @float); - } - else if (dataType == typeof(double)) - { - var @double = double.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @double); - } - else if (dataType == typeof(byte)) - { - var @byte = byte.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @byte); - } - else if (dataType == typeof(short)) - { - var @short = short.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @short); - } - else if (dataType == typeof(ushort)) - { - var @ushort = ushort.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @ushort); - } - else if (dataType == typeof(int)) - { - var @int = int.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @int); - } - else if (dataType == typeof(uint)) - { - var @uint = uint.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @uint); - } - else if (dataType == typeof(long)) - { - var @long = long.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @long); - } - else if (dataType == typeof(ulong)) - { - var @ulong = ulong.Parse(value.ToString()); - result = client.Write(varInfo.VarAddress, @ulong); - } - if (result is null) - { - throw new Exception($"未定义的数据类型"); - } - if(!result.IsSucceed) - { - throw new Exception(result.Err); - } - } - - } -} diff --git a/Net462DllTest/View/FromWorkBenchView.Designer.cs b/Net462DllTest/View/FromWorkBenchView.Designer.cs index 451cd10..1480924 100644 --- a/Net462DllTest/View/FromWorkBenchView.Designer.cs +++ b/Net462DllTest/View/FromWorkBenchView.Designer.cs @@ -31,7 +31,7 @@ this.button1 = new System.Windows.Forms.Button(); this.textBoxPlcInfo = new System.Windows.Forms.TextBox(); this.button2 = new System.Windows.Forms.Button(); - this.listBox1 = new System.Windows.Forms.ListBox(); + this.listBoxCommand = new System.Windows.Forms.ListBox(); this.textBoxSpaceNum = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // @@ -43,7 +43,6 @@ this.button1.TabIndex = 0; this.button1.Text = "查看状态"; this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); // // textBoxPlcInfo // @@ -61,16 +60,15 @@ this.button2.TabIndex = 2; this.button2.Text = "触发"; this.button2.UseVisualStyleBackColor = true; - this.button2.Click += new System.EventHandler(this.button2_Click); // - // listBox1 + // listBoxCommand // - this.listBox1.FormattingEnabled = true; - this.listBox1.ItemHeight = 12; - this.listBox1.Location = new System.Drawing.Point(35, 85); - this.listBox1.Name = "listBox1"; - this.listBox1.Size = new System.Drawing.Size(250, 88); - this.listBox1.TabIndex = 6; + this.listBoxCommand.FormattingEnabled = true; + this.listBoxCommand.ItemHeight = 12; + this.listBoxCommand.Location = new System.Drawing.Point(35, 85); + this.listBoxCommand.Name = "listBoxCommand"; + this.listBoxCommand.Size = new System.Drawing.Size(250, 88); + this.listBoxCommand.TabIndex = 6; // // textBoxSpaceNum // @@ -86,12 +84,13 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(341, 251); this.Controls.Add(this.textBoxSpaceNum); - this.Controls.Add(this.listBox1); + this.Controls.Add(this.listBoxCommand); this.Controls.Add(this.button2); this.Controls.Add(this.textBoxPlcInfo); this.Controls.Add(this.button1); this.Name = "FromWorkBenchView"; this.Text = "Form1"; + this.Load += new System.EventHandler(this.FromWorkBenchView_Load); this.ResumeLayout(false); this.PerformLayout(); @@ -102,7 +101,7 @@ private System.Windows.Forms.Button button1; private System.Windows.Forms.TextBox textBoxPlcInfo; private System.Windows.Forms.Button button2; - private System.Windows.Forms.ListBox listBox1; + private System.Windows.Forms.ListBox listBoxCommand; private System.Windows.Forms.TextBox textBoxSpaceNum; } } \ No newline at end of file diff --git a/Net462DllTest/View/FromWorkBenchView.cs b/Net462DllTest/View/FromWorkBenchView.cs index ad95f4b..feef255 100644 --- a/Net462DllTest/View/FromWorkBenchView.cs +++ b/Net462DllTest/View/FromWorkBenchView.cs @@ -1,4 +1,4 @@ -using Net462DllTest.Device; + using Net462DllTest.Signal; using Net462DllTest.ViewModel; using Serein.Library.Api; @@ -19,44 +19,36 @@ namespace Net462DllTest { private FromWorkBenchViewModel ViewModel; + public FromWorkBenchView(IFlowEnvironment env) { - ViewModel = env.IOC.Instantiate(); // 创建对象并注入依赖项 InitializeComponent(); - Init(); + ViewModel = env.IOC.Get(); // 获取对象 + if(ViewModel is null) + { + Console.WriteLine("创建对象并注入依赖项"); + ViewModel = env.IOC.Instantiate(); + } + BindData(); } - public void Init() + private void BindData() { - listBox1.Items.Clear(); - var enumValues = Enum.GetValues(typeof(CommandSignal)).Cast(); - foreach (var value in enumValues) - { - listBox1.Items.Add(value.ToString()); - } + textBoxPlcInfo.DataBindings.Add(nameof(textBoxPlcInfo.Text), ViewModel, nameof(ViewModel.DeviceInfo), false, DataSourceUpdateMode.OnPropertyChanged); + textBoxSpaceNum.DataBindings.Add(nameof(textBoxSpaceNum.Text), ViewModel, nameof(ViewModel.SpcaeNumber), false, DataSourceUpdateMode.OnPropertyChanged); + + listBoxCommand.DataSource = Enum.GetValues(typeof(CommandSignal)); + listBoxCommand.DataBindings.Add(nameof(listBoxCommand.SelectedItem), ViewModel, nameof(ViewModel.SelectedSignal), false, DataSourceUpdateMode.OnPropertyChanged); + listBoxCommand.SelectedIndexChanged += (s, e) => listBoxCommand.DataBindings[nameof(listBoxCommand.SelectedItem)].WriteValue(); + + button1.Click += (s, e) => ViewModel.CommandViewPlcInfo.Execute(); + button2.Click += (s, e) => ViewModel.CommandGetParkingSpace.Execute(); + } + private void FromWorkBenchView_Load(object sender, EventArgs e) + { + } - private void button1_Click(object sender, EventArgs e) - { - textBoxPlcInfo.Text = ViewModel.GetDeviceInfo(); - } - private void button2_Click(object sender, EventArgs e) - { - if(listBox1.SelectedItem is null) - { - return; - } - string type = listBox1.SelectedItem.ToString(); - - if (!string.IsNullOrEmpty(type) && Enum.TryParse(type, out CommandSignal signal) && Enum.IsDefined(typeof(CommandSignal), signal)) - { - Console.WriteLine($"Trigger : {type}"); - ViewModel.Trigger(signal,textBoxSpaceNum.Text); - } - - } - - } } diff --git a/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs b/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs index 38f3456..fe14029 100644 --- a/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs +++ b/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs @@ -1,37 +1,116 @@ -using Net462DllTest.Device; +using IoTClient; +using Net462DllTest.Trigger; using Net462DllTest.Signal; +using Net462DllTest.Utils; using Serein.Library.Attributes; +using Serein.Library.Utils; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; +using Net462DllTest.LogicControl; namespace Net462DllTest.ViewModel { - public class FromWorkBenchViewModel + public class FromWorkBenchViewModel : INotifyPropertyChanged { - public FromWorkBenchViewModel(SiemensPlcDevice Device) + private readonly SiemensPlcDevice Device; + private readonly ViewManagement viewManagement; + public FromWorkBenchViewModel(SiemensPlcDevice Device,ViewManagement viewManagement) { this.Device = Device; - } - private SiemensPlcDevice Device; - - public string Name { get; set; } - - public string GetDeviceInfo() - { - return Device?.ToString(); + this.viewManagement = viewManagement; + InitCommand(); + } - public void Trigger(CommandSignal signal,string spcaeNumber) + + #region 属性绑定 + private string _spcaeNumber; + public string SpcaeNumber { - _ = Task.Run(() => + get { return _spcaeNumber; } + set { - Device.TriggerSignal(signal, spcaeNumber); - }); + if (_spcaeNumber != value) + { + _spcaeNumber = value; + OnPropertyChanged(nameof(SpcaeNumber)); + } + } } + private CommandSignal _selectedSignal; + public CommandSignal SelectedSignal + { + get { return _selectedSignal; } + set + { + if (_selectedSignal != value) + { + _selectedSignal = value; + OnPropertyChanged(nameof(SelectedSignal)); + } + } + } + + private string _deviceInfo; + public string DeviceInfo + { + get { return _deviceInfo; } + set + { + if (_deviceInfo != value) + { + _deviceInfo = value; + OnPropertyChanged(nameof(DeviceInfo)); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + + #endregion + + #region 操作绑定 + + /// + /// 查看PLC信息 + /// + public RelayCommand CommandViewPlcInfo { get; private set; } + /// + /// 调取车位 + /// + public RelayCommand CommandGetParkingSpace { get; private set; } + + public void InitCommand() + { + CommandViewPlcInfo = new RelayCommand((p) => + { + DeviceInfo = Device?.ToString(); + }); + CommandGetParkingSpace = new RelayCommand((p) => + { + viewManagement.TriggerSignal(SelectedSignal, SpcaeNumber); + }); + + } + + #endregion + } + + + + } diff --git a/Net462DllTest/Web/CommandController.cs b/Net462DllTest/Web/CommandController.cs index ac37c59..b22f362 100644 --- a/Net462DllTest/Web/CommandController.cs +++ b/Net462DllTest/Web/CommandController.cs @@ -1,6 +1,9 @@ -using Net461DllTest.Device; -using Net461DllTest.Signal; + +using Net462DllTest.Enums; +using Net462DllTest.Signal; +using Net462DllTest.Trigger; using Serein.Library.Attributes; +using Serein.Library.Utils; using Serein.Library.Web; using System; using System.Collections.Generic; @@ -8,19 +11,18 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Net461DllTest.Web +namespace Net462DllTest.Web { [AutoHosting] public class CommandController : ControllerBase { - private SiemensPlcDevice PlcDevice; + private readonly SiemensPlcDevice plcDevice; - public CommandController(SiemensPlcDevice PlcDevice) + public CommandController(SiemensPlcDevice plcDevice) { - this.PlcDevice = PlcDevice; + this.plcDevice = plcDevice; } - /* * 类型 :POST * url : http://127.0.0.1:8089/command/trigger?command= @@ -29,19 +31,26 @@ namespace Net461DllTest.Web * { * "value":0, * } - * */ [WebApi(API.POST)] - public dynamic Trigger([Url] string command, int value) + public dynamic Trigger([Url] string var, int value) { - if (Enum.TryParse(command, out CommandSignal signal) && Enum.IsDefined(typeof(CommandSignal), signal)) + if (EnumHelper.TryConvertEnum(var,out var signal)) { Console.WriteLine($"外部触发 {signal} 信号,信号内容 : {value} "); - PlcDevice.TriggerSignal(signal, value);// 通过 Web Api 模拟外部输入信号 + plcDevice.TriggerSignal(signal, value);// 通过 Web Api 模拟外部输入信号 return new { state = "succeed" }; } - return new { state = "fail" }; + else + { + return new { state = "fail" }; + } + } } + + + + } diff --git a/Net462DllTest/Web/CommandController_1.cs b/Net462DllTest/Web/CommandController_1.cs deleted file mode 100644 index c840e00..0000000 --- a/Net462DllTest/Web/CommandController_1.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Net462DllTest.Device; -using Net462DllTest.Signal; -using Serein.Library.Attributes; -using Serein.Library.Web; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Net462DllTest.Web -{ - [AutoHosting] - public class CommandController : ControllerBase - { - [AutoInjection] - public SiemensPlcDevice PlcDevice { get; set; } - - /* - * 类型 :POST - * url : http://127.0.0.1:8089/command/trigger?command= - * body :[JSON] - * - * { - * "value":0, - * } - * - */ - [WebApi(API.POST)] - public dynamic Trigger([Url] string command, int value) - { - if (Enum.TryParse(command, out CommandSignal signal) && Enum.IsDefined(typeof(CommandSignal), signal)) - { - Console.WriteLine($"外部触发 {signal} 信号,信号内容 : {value} "); - PlcDevice.TriggerSignal(signal, value);// 通过 Web Api 模拟外部输入信号 - return new { state = "succeed" }; - } - return new { state = "fail" }; - } - } - -} diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index c788f90..df3210d 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -231,7 +231,7 @@ namespace Serein.NodeFlow.Base } catch (Exception ex) { - await Console.Out.WriteLineAsync(ex.ToString()); + await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex.Message); NextOrientation = ConnectionType.IsError; RuningException = ex; return null; diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index e7b2597..bf8c683 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -956,7 +956,17 @@ namespace Serein.NodeFlow List autoRegisterTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute() is not null).ToList(); - List scanTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute()?.Scan == true).ToList(); + List<(Type, string)> scanTypes = assembly.GetTypes().Select(t => { + if (t.GetCustomAttribute() is DynamicFlowAttribute dynamicFlowAttribute + && dynamicFlowAttribute.Scan == true) + { + return (t, dynamicFlowAttribute.Name); + } + else + { + return (null, null); + } + }).Where(it => it.t is not null) .ToList(); if (scanTypes.Count == 0) { return (null, [], []); @@ -964,7 +974,7 @@ namespace Serein.NodeFlow List methodDetails = new List(); // 遍历扫描的类型 - foreach (var type in scanTypes) + foreach ((var type,var flowName ) in scanTypes) { // 加载DLL,创建 MethodDetails、实例作用对象、委托方法 var assemblyName = type.Assembly.GetName().Name; @@ -981,6 +991,7 @@ namespace Serein.NodeFlow Console.WriteLine($"无法加载方法信息:{assemblyName}-{type}-{method}"); continue; } + md.MethodTips = flowName + md.MethodTips; if (MethodDelegates.TryAdd(md.MethodName, del)) { methodDetails.Add(md); diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 40ada7d..5676333 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -2,6 +2,7 @@ using Serein.Library.Core.NodeFlow; using Serein.Library.Entity; using Serein.Library.Enums; +using Serein.Library.Ex; using Serein.Library.Utils; using Serein.Library.Web; using Serein.NodeFlow.Base; @@ -385,9 +386,17 @@ namespace Serein.NodeFlow } } } + catch(FlipflopException ex) + { + await Console.Out.WriteLineAsync($"触发器[{singleFlipFlopNode.MethodDetails.MethodName}]因非预期异常终止。"+ex.Message); + if (ex.Clsss == FlipflopException.CancelClass.Flow) + { + break; + } + } catch (Exception ex) { - await Console.Out.WriteLineAsync(ex.ToString()); + await Console.Out.WriteLineAsync(ex.Message); } } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index f530e74..86695aa 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -37,24 +37,31 @@ namespace Serein.NodeFlow.Model throw new Exception("不存在对应委托"); } object instance = md.ActingInstance; - // Task? flipflopTask = null; try { - // 调用委托并获取结果 - //Task flipflopTask = md.ExplicitDatas.Length switch - //{ - // 0 => ((Func>)del).Invoke(md.ActingInstance), - // _ => ((Func>)del).Invoke(md.ActingInstance, GetParameters(context, this, md)), // 执行流程中的触发器方法时获取入参参数 - //}; Task flipflopTask; if (md.ExplicitDatas.Length == 0) { - flipflopTask = ((Func>)del).Invoke(md.ActingInstance); + if (del is Func> function) + { + flipflopTask = function.Invoke(md.ActingInstance); + } + else + { + throw new FlipflopException("触发节点非预期的返回类型", true, FlipflopException.CancelClass.Flow); + } } else { var parameters = GetParameters(context, this, md); - flipflopTask = ((Func>)del).Invoke(md.ActingInstance, parameters); + if(del is Func> function) + { + flipflopTask = function.Invoke(md.ActingInstance, parameters); + } + else + { + throw new FlipflopException("触发节点非预期的返回类型", true,FlipflopException.CancelClass.Flow); + } } IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文"); @@ -67,14 +74,18 @@ namespace Serein.NodeFlow.Model } catch (FlipflopException ex) { - await Console.Out.WriteLineAsync(ex.ToString()); + if(ex.Clsss == FlipflopException.CancelClass.Flow) + { + throw; + } + await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex.Message); NextOrientation = ConnectionType.None; RuningException = ex; return null; } catch (Exception ex) { - await Console.Out.WriteLineAsync(ex.ToString()); + await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex.Message); NextOrientation = ConnectionType.IsError; RuningException = ex; return null; diff --git a/NodeFlow/Tool/MethodDetailsHelper.cs b/NodeFlow/Tool/MethodDetailsHelper.cs index 28b4610..c600e5f 100644 --- a/NodeFlow/Tool/MethodDetailsHelper.cs +++ b/NodeFlow/Tool/MethodDetailsHelper.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.ComponentModel; using System.Reflection; +using System.Text.RegularExpressions; namespace Serein.NodeFlow.Tool; @@ -54,13 +55,14 @@ public static class MethodDetailsHelperTmp /// public static (MethodDetails?,Delegate?) CreateMethodDetails(Type type, MethodInfo method, string assemblyName) { - - var methodName = method.Name; var attribute = method.GetCustomAttribute(); if(attribute is null) { return (null, null); } + //var dllTypeName = $"{assemblyName}.{type.Name}"; + var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}"; + var explicitDataOfParameters = GetExplicitDataOfParameters(method.GetParameters()); //// 生成委托 var methodDelegate = GenerateMethodDelegate(type, // 方法所在的对象类型 @@ -68,20 +70,38 @@ public static class MethodDetailsHelperTmp method.GetParameters(),// 方法参数 method.ReturnType);// 返回值 + Type returnType; - if (attribute?.MethodDynamicType == Library.Enums.NodeType.Flipflop) + bool isTask = IsGenericTask(method.ReturnType, out var taskResult); + + if (attribute.MethodDynamicType == Library.Enums.NodeType.Flipflop) { - // 触发器节点 returnType = attribute.ReturnType; + if (!isTask || taskResult != typeof(IFlipflopContext)) + { + Console.WriteLine($"触发器节点的返回类型非预期类型,可能会导致流程异常。[{dllTypeMethodName}]当前返回类型为[{method.ReturnType}],而预期的返回类型应为[Task]"); + } + + } + else if(isTask) + { + returnType = taskResult; } else { returnType = method.ReturnType; } - var dllTypeName = $"{assemblyName}.{type.Name}"; - // object instance = Activator.CreateInstance(type); - var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}"; + if (string.IsNullOrEmpty(attribute.MethodTips)){ + attribute.MethodTips = method.Name; + } + + + + var asyncPrefix = "[异步]"; // IsGenericTask(returnType) ? "[async]" : ; + var methodTips = isTask ? asyncPrefix + attribute.MethodTips : attribute.MethodTips; + + var md = new MethodDetails { @@ -90,7 +110,7 @@ public static class MethodDetailsHelperTmp MethodName = dllTypeMethodName, MethodDynamicType = attribute.MethodDynamicType, MethodLockName = attribute.LockName, - MethodTips = attribute.MethodTips, + MethodTips = methodTips, ExplicitDatas = explicitDataOfParameters, ReturnType = returnType, }; @@ -99,6 +119,29 @@ public static class MethodDetailsHelperTmp } + public static bool IsGenericTask(Type returnType, out Type taskResult) + { + // 判断是否为 Task 类型或泛型 Task + if (returnType == typeof(Task)) + { + taskResult = returnType; + return true; + } + else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) + { + // 获取泛型参数类型 + Type genericArgument = returnType.GetGenericArguments()[0]; + taskResult = genericArgument; + return true; + } + else + { + taskResult = null; + return false; + + } + } + private static ConcurrentDictionary ConvertorInstance =[]; diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 63eaad1..b1c393f 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -36,9 +36,6 @@ namespace Serein.WorkBench public App() { - - - #if false //测试 操作表达式,条件表达式 #region 测试数据 string expression = ""; diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 2b07d51..60a7df7 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -2352,7 +2352,7 @@ namespace Serein.WorkBench await FlowEnvironment.StartAsync(); // 快 - //await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失 + //await Task.Run(FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失 //await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5,定时器精度丢失 }