diff --git a/src/DebugTool/ThingsGateway.Debug.Photino/Configuration/App.json b/src/DebugTool/ThingsGateway.Debug.Photino/Configuration/App.json index a88b59ea7..0f550cfb2 100644 --- a/src/DebugTool/ThingsGateway.Debug.Photino/Configuration/App.json +++ b/src/DebugTool/ThingsGateway.Debug.Photino/Configuration/App.json @@ -10,8 +10,8 @@ // nuget动态加载的程序集 "SupportPackageNamePrefixs": [ - "ThingsGateway.Foundation.Razor", - "ThingsGateway.Debug.Razor", + "ThingsGateway.Admin.Application", + "ThingsGateway.Admin.Razor", "ThingsGateway.Core", "ThingsGateway.Razor" diff --git a/src/Directory.Build.props b/src/Directory.Build.props index dac7088a4..bf637d10a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,8 +2,8 @@ 7.0.1.0 - 9.0.1.0 - 9.0.1.0 + 9.0.1.1 + 9.0.1.1 diff --git a/src/Foundation/ThingsGateway.CSScript/CSharpScriptEngineExtension.cs b/src/Foundation/ThingsGateway.CSScript/CSharpScriptEngineExtension.cs new file mode 100644 index 000000000..b1ec988a8 --- /dev/null +++ b/src/Foundation/ThingsGateway.CSScript/CSharpScriptEngineExtension.cs @@ -0,0 +1,134 @@ +//------------------------------------------------------------------------------ +// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 +// 此代码版权(除特别声明外的代码)归作者本人Diego所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议 +// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway +// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway +// 使用文档:https://thingsgateway.cn/ +// QQ群:605534569 +//------------------------------------------------------------------------------ + +using System.Reflection; +using System.Text; + +using CSScripting; + +using CSScriptLib; + +using ThingsGateway.NewLife.Caching; +using ThingsGateway.NewLife.Threading; + +namespace ThingsGateway.Gateway.Application; + + +/// +/// 脚本扩展方法 +/// +public static class CSharpScriptEngineExtension +{ + private static string CacheKey = $"{nameof(CSharpScriptEngineExtension)}-{nameof(Do)}"; + + private static object m_waiterLock = new object(); + + /// 清理计时器 + private static TimerX? _clearTimer; + static CSharpScriptEngineExtension() + { + if (_clearTimer == null) + { + _clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true }; + } + } + + private static void RemoveNotAlive(Object? state) + { + //检测缓存 + try + { + var data = Instance.GetAll(); + lock (m_waiterLock) + { + + foreach (var item in data) + { + if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000) + { + Instance.Remove(item.Key); + item.Value?.Value?.GetType().Assembly.Unload(); + GC.Collect(); + } + } + } + } + catch + { + } + + } + + private static MemoryCache Instance { get; set; } = new MemoryCache(); + + /// + /// 执行脚本获取返回值 + /// + public static T Do(string source, params Assembly[] assemblies) where T : class + { + var field = $"{CacheKey}-{source}"; + var runScript = Instance.Get(field); + if (runScript == null) + { + lock (m_waiterLock) + { + runScript = Instance.Get(field); + if (runScript == null) + { + + var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + var _using = new StringBuilder(); + var _body = new StringBuilder(); + src.ToList().ForEach(l => + { + if (l.StartsWith("using ")) + { + _using.AppendLine(l); + } + else + { + _body.AppendLine(l); + } + + }); + var evaluator = CSScript.Evaluator; + foreach (var item in assemblies) + { + evaluator = evaluator.ReferenceAssembly(item.Location); + } + // 动态加载并执行代码 + runScript = evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode( + $@" + using System; + using System.Linq; + using System.Collections.Generic; + using ThingsGateway.Gateway.Application; + using ThingsGateway.NewLife; + using ThingsGateway.NewLife.Extension; + using ThingsGateway.Gateway.Application.Extensions; + {_using} + {_body} + "); + GC.Collect(); + Instance.Set(field, runScript); + } + } + + } + Instance.SetExpire(field, TimeSpan.FromHours(1)); + + return runScript; + } + + + +} + + diff --git a/src/Foundation/ThingsGateway.CSScript/ExpressionEvaluatorExtension.cs b/src/Foundation/ThingsGateway.CSScript/ExpressionEvaluatorExtension.cs new file mode 100644 index 000000000..6a1ac5b02 --- /dev/null +++ b/src/Foundation/ThingsGateway.CSScript/ExpressionEvaluatorExtension.cs @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 +// 此代码版权(除特别声明外的代码)归作者本人Diego所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议 +// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway +// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway +// 使用文档:https://thingsgateway.cn/ +// QQ群:605534569 +//------------------------------------------------------------------------------ + +using CSScripting; + +using CSScriptLib; + +using System.Text; + +using ThingsGateway.NewLife.Caching; +using ThingsGateway.NewLife.Threading; + +namespace ThingsGateway.Gateway.Application.Extensions; + +/// +/// 读写表达式脚本 +/// +public interface ReadWriteExpressions +{ + /// + /// 获取新值 + /// + /// + /// + object GetNewValue(object a); +} + +/// +/// 表达式扩展 +/// +public static class ExpressionEvaluatorExtension +{ + private static string CacheKey = $"{nameof(ExpressionEvaluatorExtension)}-{nameof(GetReadWriteExpressions)}"; + + private static object m_waiterLock = new object(); + + /// 清理计时器 + private static TimerX? _clearTimer; + static ExpressionEvaluatorExtension() + { + if (_clearTimer == null) + { + _clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true }; + } + } + + private static void RemoveNotAlive(Object? state) + { + //检测缓存 + try + { + var data = Instance.GetAll(); + lock (m_waiterLock) + { + + foreach (var item in data) + { + if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000) + { + Instance.Remove(item.Key); + item.Value?.Value?.GetType().Assembly.Unload(); + GC.Collect(); + } + } + } + } + catch + { + } + + } + + private static MemoryCache Instance { get; set; } = new MemoryCache(); + + /// + /// 添加或获取脚本,非线程安全 + /// + /// + /// + public static ReadWriteExpressions GetOrAddScript(string source) + { + var field = $"{CacheKey}-{source}"; + var runScript = Instance.Get(field); + if (runScript == null) + { + if (!source.Contains("return")) + { + source = $"return {source}";//只判断简单脚本中可省略return字符串 + } + + var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + var _using = new StringBuilder(); + var _body = new StringBuilder(); + src.ToList().ForEach(l => + { + if (l.StartsWith("using ")) + { + _using.AppendLine(l); + } + else + { + _body.AppendLine(l); + } + + }); + // 动态加载并执行代码 + runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode( + $@" + using System; + using System.Linq; + using System.Collections.Generic; + using ThingsGateway.Gateway.Application; + using ThingsGateway.NewLife; + using ThingsGateway.NewLife.Extension; + using ThingsGateway.Gateway.Application.Extensions; + {_using} + public class Script:ReadWriteExpressions + {{ + public object GetNewValue(object raw) + {{ + {_body}; + }} + }} + "); + GC.Collect(); + Instance.Set(field, runScript); + } + return runScript; + } + + /// + /// 计算表达式:例如:(int)raw*100,raw为原始值 + /// + public static object GetExpressionsResult(this string expressions, object rawvalue) + { + if (string.IsNullOrWhiteSpace(expressions)) + { + return rawvalue; + } + var readWriteExpressions = GetReadWriteExpressions(expressions); + var value = readWriteExpressions.GetNewValue(rawvalue); + return value; + } + + /// + /// 执行脚本获取返回值ReadWriteExpressions + /// + public static ReadWriteExpressions GetReadWriteExpressions(string source) + { + var field = $"{CacheKey}-{source}"; + var runScript = Instance.Get(field); + if (runScript == null) + { + lock (m_waiterLock) + { + runScript = GetOrAddScript(source); + } + } + Instance.SetExpire(field, TimeSpan.FromHours(1)); + + return runScript; + } +} diff --git a/src/Foundation/ThingsGateway.CSScript/ThingsGateway.CSScript.csproj b/src/Foundation/ThingsGateway.CSScript/ThingsGateway.CSScript.csproj new file mode 100644 index 000000000..333f841ec --- /dev/null +++ b/src/Foundation/ThingsGateway.CSScript/ThingsGateway.CSScript.csproj @@ -0,0 +1,16 @@ + + + + + + netstandard2.0;net8.0;net6.0; + + + + + + + + + + diff --git a/src/Foundation/ThingsGateway.Foundation.Razor/DebugPages/AdapterDebugBase.cs b/src/Foundation/ThingsGateway.Foundation.Razor/DebugPages/AdapterDebugBase.cs new file mode 100644 index 000000000..90ac6e40b --- /dev/null +++ b/src/Foundation/ThingsGateway.Foundation.Razor/DebugPages/AdapterDebugBase.cs @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 +// 此代码版权(除特别声明外的代码)归作者本人Diego所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议 +// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway +// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway +// 使用文档:https://thingsgateway.cn/ +// QQ群:605534569 +//------------------------------------------------------------------------------ + +namespace ThingsGateway.Debug; + +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; + +using ThingsGateway.Foundation; +using ThingsGateway.Foundation.Json.Extension; + +using TouchSocket.Core; + +/// +/// 调试UI +/// +public abstract class AdapterDebugBase : ComponentBase, IDisposable +{ + /// + ~AdapterDebugBase() + { + this.SafeDispose(); + } + + /// + /// 长度 + /// + public int ArrayLength { get; set; } = 1; + + /// + /// 默认读写设备 + /// + [Parameter] + public IProtocol Plc { get; set; } + + /// + /// 变量地址 + /// + public string RegisterAddress { get; set; } = "400001"; + + /// + /// 写入值 + /// + public string WriteValue { get; set; } + + /// + /// 数据类型 + /// + protected DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16; + + [Inject] + private IStringLocalizer Localizer { get; set; } + + /// + public void Dispose() + { + Plc?.SafeDispose(); + GC.SuppressFinalize(this); + } + + /// + public virtual async Task ReadAsync() + { + if (Plc != null) + { + try + { + var data = await Plc.ReadAsync(RegisterAddress, ArrayLength, DataType); + if (data.IsSuccess) + { + Plc.Logger?.LogInformation(data.Content.ToJsonNetString()); + } + else + { + Plc.Logger?.Warning(data.ToString()); + } + } + catch (Exception ex) + { + Plc.Logger?.Exception(ex); + } + } + } + + /// + public virtual async Task WriteAsync() + { + if (Plc != null) + { + try + { + var data = await Plc.WriteAsync(RegisterAddress, WriteValue.GetJTokenFromString(), DataType); + if (data.IsSuccess) + { + Plc.Logger?.LogInformation($" {WriteValue.GetJTokenFromString()} {Localizer["WriteSuccess"]}"); + } + else + { + Plc.Logger?.Warning(data.ToString()); + } + } + catch (Exception ex) + { + Plc.Logger?.Exception(ex); + } + } + } +} diff --git a/src/Foundation/ThingsGateway.Foundation.Razor/DebugPages/AdapterDebugComponent.razor b/src/Foundation/ThingsGateway.Foundation.Razor/DebugPages/AdapterDebugComponent.razor new file mode 100644 index 000000000..c368b6d64 --- /dev/null +++ b/src/Foundation/ThingsGateway.Foundation.Razor/DebugPages/AdapterDebugComponent.razor @@ -0,0 +1,120 @@ +@using Microsoft.AspNetCore.Components.Web; +@using Microsoft.JSInterop; +@using ThingsGateway.Extension +@using ThingsGateway.Foundation +@using ThingsGateway.Foundation.Json.Extension +@using BootstrapBlazor.Components +@namespace ThingsGateway.Debug +@inherits AdapterDebugBase + +
+ +
+ + + @if (ShowDefaultReadWriteContent) + { + + + + +
+ +
+ +
+ + +