Compare commits
	
		
			30 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6291ce8617 | ||
|   | c76b1b50a0 | ||
|   | cc45e2aec0 | ||
|   | 17efebb8e8 | ||
|   | 5c94c733ee | ||
|   | 156b89dd9c | ||
|   | 34ba9f67e7 | ||
|   | 5ddaa6b872 | ||
|   | 9043fa7f56 | ||
|   | 4c8e487dc9 | ||
|   | d3b87179aa | ||
|   | 2166de8331 | ||
|   | f0bc3f001f | ||
|   | 50448e7085 | ||
|   | cd1d42353e | ||
|   | 5e588bf737 | ||
|   | 7ff777d178 | ||
|   | 861621189a | ||
|   | dcc00e08fd | ||
|   | a1b8a47d4b | ||
|   | 1fd6b5e239 | ||
|   | f91e45bf44 | ||
|   | 0675a45592 | ||
|   | 0c7c6ae451 | ||
|   | cf089e8c4c | ||
|   | 90928ac679 | ||
|   | 3bbcf71784 | ||
|   | 222734775d | ||
|   | f23ee7a6e0 | ||
|   | 5b075aa6d5 | 
| @@ -2,11 +2,10 @@ | ||||
| 	<PropertyGroup> | ||||
| 		<NoWarn>CS8618;CS8625;CS8600;CS8601;CS8604;CS8714;CS8602;CS8603;CS8619;CS8621</NoWarn> | ||||
| 		<TargetFrameworks>net6.0;</TargetFrameworks> | ||||
| 		<!--<TargetFrameworks>net6.0;</TargetFrameworks>--> | ||||
| 		<LangVersion>11.0</LangVersion> | ||||
| 		<ImplicitUsings>enable</ImplicitUsings> | ||||
| 		<Nullable>enable</Nullable> | ||||
| 		<Version>5.0.1.0</Version> | ||||
| 		<Version>5.0.1.17</Version> | ||||
| 		<Authors>Diego</Authors> | ||||
| 		<Company>Diego</Company> | ||||
| 		<Product>Diego</Product> | ||||
|   | ||||
| @@ -14,7 +14,11 @@ | ||||
| 		<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> | ||||
| 		<OutputPath></OutputPath> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<PropertyGroup Condition="'$(Configuration)' == 'Release'"> | ||||
| 		<DebugSymbols>True</DebugSymbols> | ||||
| 		<DebugType>Embedded</DebugType> | ||||
| 		<EmbedAllSources>True</EmbedAllSources> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<None Include="$(SolutionDir)Foundation.props" Pack="true" PackagePath="\" /> | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| 		<PackageReference Include="SharpZipLib" Version="1.4.2" /> | ||||
| 		<PackageReference Include="SqlSugarCore.Dm" Version="1.3.0" /> | ||||
| 		<PackageReference Include="SqlSugarCore.Kdbndp" Version="7.4.0" /> | ||||
| 		<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.142" /> | ||||
| 		<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.143" /> | ||||
| 		<PackageReference Include="System.Data.Common" Version="4.3.0" /> | ||||
| 		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||||
| 		<PackageReference Include="System.Text.Json" Version="8.0.2" /> | ||||
|   | ||||
| @@ -23,6 +23,7 @@ using NewLife; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Core.Extension; | ||||
| using ThingsGateway.Core.Extension.Json; | ||||
|  | ||||
| namespace ThingsGateway.Components; | ||||
|  | ||||
| @@ -520,7 +521,7 @@ public partial class AppDataTable<TItem, SearchItem, AddItem, EditItem> : IAppDa | ||||
|                     { | ||||
|                         value = dt2.ToDefaultDateTimeFormat(AppService.TimezoneOffset); | ||||
|                     } | ||||
|                     keyValuePairs.Add(item, value?.ToString()); | ||||
|                     keyValuePairs.Add(item, value?.ToJsonString(true)); | ||||
|                 } | ||||
|             } | ||||
|             DetailModelPairs = keyValuePairs; | ||||
|   | ||||
| @@ -13,41 +13,18 @@ using ThingsGateway.Foundation; | ||||
| namespace ThingsGateway.Demo; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public class VariableDemo : IVariable | ||||
| public class VariableDemo : VariableClass | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public int? IntervalTime { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public string? RegisterAddress { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public int Index { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public IThingsGatewayBitConverter ThingsGatewayBitConverter { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public DataTypeEnum DataType { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public object? Value { get; set; } | ||||
|  | ||||
|     public override object? Value => _value; | ||||
|     private object _value; | ||||
|     public bool IsOnline { get; set; } | ||||
|  | ||||
|     public string? LastErrorMessage => VariableSource?.LastErrorMessage; | ||||
|  | ||||
|     public IVariableSource VariableSource { get; set; } | ||||
|  | ||||
|     public OperResult SetValue(object value, DateTime dateTime = default, bool isOnline = false) | ||||
|     public override OperResult SetValue(object value, DateTime dateTime = default, bool isOnline = false) | ||||
|     { | ||||
|         Value = value ?? "null"; | ||||
|         _value = value ?? "null"; | ||||
|         IsOnline = isOnline; | ||||
|         return new(); | ||||
|     } | ||||
|  | ||||
|     public Task<OperResult> SetValueToDeviceAsync(string value, string? executive = null, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return Task.FromResult(new OperResult()); | ||||
|     } | ||||
| } | ||||
| @@ -13,28 +13,6 @@ using ThingsGateway.Foundation; | ||||
| namespace ThingsGateway.Demo; | ||||
|  | ||||
| /// <inheritdoc/> | ||||
| public class VariableSourceDemo : IVariableSourceT<IVariable> | ||||
| public class VariableSourceDemo : VariableSourceClass | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public TimeTick IntervalTimeTick { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public string RegisterAddress { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public int Length { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public string? LastErrorMessage { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public ICollection<IVariable> VariableRunTimes { get; set; } = new List<IVariable>(); | ||||
|  | ||||
|     public TimeTick TimeTick { get; set; } | ||||
|  | ||||
|     public void AddVariable(IVariable variable) | ||||
|     { | ||||
|         variable.VariableSource = this; | ||||
|         VariableRunTimes.Add(variable); | ||||
|     } | ||||
| } | ||||
| @@ -12,7 +12,6 @@ using BlazorComponent; | ||||
|  | ||||
| using Microsoft.AspNetCore.Components; | ||||
|  | ||||
| using ThingsGateway.Components; | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| @@ -25,7 +24,7 @@ public partial class AdapterDebugPage : AdapterDebugBase | ||||
|     /// <summary> | ||||
|     /// VariableRunTimes | ||||
|     /// </summary> | ||||
|     public List<IVariable> VariableRunTimes; | ||||
|     public List<VariableDemo> VariableRunTimes; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// MaxPack | ||||
|   | ||||
| @@ -308,5 +308,75 @@ | ||||
|             </summary> | ||||
|             <param name="pluginName">插件名称,一般建议使用nameof()解决。</param> | ||||
|         </member> | ||||
|         <member name="T:TouchSocket.Rpc.GeneratorRpcProxyAttribute"> | ||||
|             <summary> | ||||
|             标识该接口将使用源生成自动生成调用的代理类 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.Prefix"> | ||||
|             <summary> | ||||
|             调用键的前缀,包括服务的命名空间,类名,不区分大小写。格式:命名空间.类名 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.GenericConstraintTypes"> | ||||
|             <summary> | ||||
|             生成泛型方法的约束 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.MethodInvoke"> | ||||
|             <summary> | ||||
|             是否仅以函数名调用,当为True是,调用时仅需要传入方法名即可。 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.Namespace"> | ||||
|             <summary> | ||||
|             生成代码的命名空间 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.ClassName"> | ||||
|             <summary> | ||||
|             生成的类名,不要包含“I”,生成接口时会自动添加。 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.GeneratorFlag"> | ||||
|             <summary> | ||||
|             生成代码 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcProxyAttribute.InheritedInterface"> | ||||
|             <summary> | ||||
|             继承接口 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:TouchSocket.Rpc.GeneratorRpcServerAttribute"> | ||||
|             <summary> | ||||
|             标识将通过源生成器生成Rpc服务的调用委托。 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute"> | ||||
|             <summary> | ||||
|             标识将通过源生成器生成Rpc服务的注册代码。 | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute.MethodName"> | ||||
|             <summary> | ||||
|             方法名称。默认是“RegisterAllFrom+AssemblyName” | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute.ClassName"> | ||||
|             <summary> | ||||
|             扩展类类名,默认是“RegisterRpcServerFrom+AssemblyName+Extension” | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute.Accessibility"> | ||||
|             <summary> | ||||
|             访问修饰。 | ||||
|             <para> | ||||
|             如果为<see cref="F:TouchSocket.Rpc.Accessibility.Both"/>,将生成注册公共Rpc服务与非公共服务两个方法。其中非公共方法会在<see cref="P:TouchSocket.Rpc.GeneratorRpcServerRegisterAttribute.MethodName"/>之前以Internal开头。 | ||||
|             如果为<see cref="F:TouchSocket.Rpc.Accessibility.Internal"/>,将只生成注册非公共Rpc服务。 | ||||
|             如果为<see cref="F:TouchSocket.Rpc.Accessibility.Public"/>,将只生成注册公共Rpc服务。 | ||||
|             </para> | ||||
|             </summary> | ||||
|         </member> | ||||
|     </members> | ||||
| </doc> | ||||
|   | ||||
| @@ -0,0 +1,17 @@ | ||||
| { | ||||
|   "Management": { | ||||
|     "RemoteUri": "127.0.0.1:7778", //主(备)站IP | ||||
|     "Port": 7777, //监听端口 | ||||
|     "VerifyToken": "ThingsGateway", //登录token,双方一致 | ||||
|     "HeartbeatInterval": 3000, //心跳间隔 | ||||
|     "MaxErrorCount": 3, //最大错误次数 | ||||
|     "Redundancy": { | ||||
|       "Enable": false, //启用冗余 | ||||
|       "IsPrimary": false, //是否主站 | ||||
|       "IsStartBusinessDevice": true //是否启用备用站点的业务设备 | ||||
|       //主从站的采集配置必须一致 | ||||
|       //默认主站优先,当主站恢复后,从站切换回备用模式。 | ||||
|       //主从站都完成对采集的初始化,但从站的数据都来自主站的数据同步 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| { | ||||
|   "Management": { | ||||
|     "RemoteUri": "127.0.0.1:7778", //主(备)站IP | ||||
|     "Port": 7777, //监听端口 | ||||
|     "VerifyToken": "ThingsGateway", //登录token,双方一致 | ||||
|     "HeartbeatInterval": 3000, //心跳间隔 | ||||
|     "MaxErrorCount": 3, //最大错误次数 | ||||
|     "Redundancy": { | ||||
|       "Enable": false, //启用冗余 | ||||
|       "IsPrimary": false, //是否主站 | ||||
|       "IsStartBusinessDevice": true //是否启用备用站点的业务设备 | ||||
|       //主从站的采集配置必须一致 | ||||
|       //默认主站优先,当主站恢复后,从站切换回备用模式。 | ||||
|       //主从站都完成对采集的初始化,但从站的数据都来自主站的数据同步 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -65,4 +65,20 @@ public class DeviceBasicData : DeviceData | ||||
|     /// <inheritdoc cref="Device.Description"/> | ||||
|     [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | ||||
|     public string? Description { get; set; } | ||||
| } | ||||
|  | ||||
| public class DeviceDataWithValue : IPrimaryIdEntity | ||||
| { | ||||
|     /// <inheritdoc cref="PrimaryIdEntity.Id"/> | ||||
|     public long Id { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="DeviceRunTime.ActiveTime"/> | ||||
|     public DateTime ActiveTime { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="DeviceRunTime.DeviceStatus"/> | ||||
|     public DeviceStatusEnum DeviceStatus { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="DeviceRunTime.LastErrorMessage"/> | ||||
|     [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | ||||
|     public string LastErrorMessage { get; set; } | ||||
| } | ||||
| @@ -45,7 +45,7 @@ public class DeviceRunTime : Device | ||||
|     /// 设备活跃时间 | ||||
|     /// </summary> | ||||
|     [Description("活跃时间")] | ||||
|     public DateTime? ActiveTime { get; private set; } | ||||
|     public DateTime? ActiveTime { get; internal set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设备状态 | ||||
| @@ -60,7 +60,7 @@ public class DeviceRunTime : Device | ||||
|             else | ||||
|                 return DeviceStatusEnum.Pause; | ||||
|         } | ||||
|         protected set | ||||
|         internal set | ||||
|         { | ||||
|             if (_deviceStatus != value) | ||||
|             { | ||||
| @@ -123,7 +123,7 @@ public class DeviceRunTime : Device | ||||
|         { | ||||
|             return _lastErrorMessage; | ||||
|         } | ||||
|         protected set | ||||
|         internal set | ||||
|         { | ||||
|             _lastErrorMessage = DateTimeUtil.TimerXNow.ToDefaultDateTimeFormat() + " - " + value; | ||||
|         } | ||||
|   | ||||
| @@ -81,4 +81,23 @@ public class VariableBasicData : VariableData | ||||
|  | ||||
|     /// <inheritdoc cref="Variable.DataType"/> | ||||
|     public DataTypeEnum DataType { get; set; } | ||||
| } | ||||
|  | ||||
| internal class VariableDataWithValue : IPrimaryIdEntity | ||||
| { | ||||
|     /// <inheritdoc cref="PrimaryIdEntity.Id"/> | ||||
|     public long Id { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="VariableRunTime.Value"/> | ||||
|     public object RawValue { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="VariableRunTime.CollectTime"/> | ||||
|     public DateTime CollectTime { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="VariableRunTime.IsOnline"/> | ||||
|     public bool IsOnline { get; set; } | ||||
|  | ||||
|     /// <inheritdoc cref="VariableRunTime.LastErrorMessage"/> | ||||
|     [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | ||||
|     public string? LastErrorMessage { get; set; } | ||||
| } | ||||
| @@ -127,21 +127,21 @@ public class VariableRunTime : Variable, IVariable | ||||
|     /// </summary> | ||||
|     [Description("上次值")] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = false, CellClass = " table-text-truncate ")] | ||||
|     public object? LastSetValue { get; private set; } | ||||
|     public object? LastSetValue { get; internal set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 原始值 | ||||
|     /// </summary> | ||||
|     [Description("原始值")] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = false, CellClass = " table-text-truncate ")] | ||||
|     public object? RawValue { get; private set; } | ||||
|     public object? RawValue { get; internal set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 实时值 | ||||
|     /// </summary> | ||||
|     [Description("实时值")] | ||||
|     [DataTable(Order = 3, IsShow = true, Sortable = false, CellClass = " table-text-truncate ")] | ||||
|     public object? Value { get => _value; private set => _value = value; } | ||||
|     public object? Value { get => _value; internal set => _value = value; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 设置变量值与时间/质量戳 | ||||
| @@ -202,6 +202,12 @@ public class VariableRunTime : Variable, IVariable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal void SetErrorMessage(string value) | ||||
|     { | ||||
|         if (VariableSource != null) | ||||
|             VariableSource.LastErrorMessage = value; | ||||
|     } | ||||
|  | ||||
|     private IRpcService? _rpcService { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -13,12 +13,8 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// <summary> | ||||
| /// 连读报文信息 | ||||
| /// </summary> | ||||
| public class VariableSourceRead : IVariableSourceT<IVariable> | ||||
| public class VariableSourceRead : IVariableSource | ||||
| { | ||||
|     public VariableSourceRead() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 读取地址,传入时需要去除额外信息 | ||||
|     /// </summary> | ||||
| @@ -27,7 +23,9 @@ public class VariableSourceRead : IVariableSourceT<IVariable> | ||||
|     /// <summary> | ||||
|     /// 需分配的变量列表 | ||||
|     /// </summary> | ||||
|     public ICollection<IVariable> VariableRunTimes { get; private set; } = new List<IVariable>(); | ||||
|     public IEnumerable<IVariable> VariableRunTimes => _variableRunTimes; | ||||
|  | ||||
|     private List<IVariable> _variableRunTimes = new List<IVariable>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 间隔时间实现 | ||||
| @@ -37,7 +35,7 @@ public class VariableSourceRead : IVariableSourceT<IVariable> | ||||
|     public void AddVariable(IVariable variable) | ||||
|     { | ||||
|         variable.VariableSource = this; | ||||
|         VariableRunTimes.Add(variable); | ||||
|         _variableRunTimes.Add(variable); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -48,11 +48,6 @@ public abstract class BusinessBase : DriverBase | ||||
|  | ||||
|     public override async Task AfterStopAsync() | ||||
|     { | ||||
|         //去除全局设备变量 | ||||
|         lock (GlobalData.BusinessDevices) | ||||
|         { | ||||
|             GlobalData.BusinessDevices.RemoveWhere(it => it.Id == DeviceId); | ||||
|         } | ||||
|         await base.AfterStopAsync(); | ||||
|     } | ||||
| } | ||||
| @@ -63,10 +63,11 @@ public class ChannelThread | ||||
|  | ||||
|     public string LogPath { get; } | ||||
|     private Channel ChannelTable; | ||||
|  | ||||
|     private GlobalData GlobalData; | ||||
|     public ChannelThread(Channel channel, Func<TouchSocketConfig, IChannel> getChannel) | ||||
|     { | ||||
|         Logger = App.GetService<ILoggerFactory>().CreateLogger($"通道:{channel.Name}"); | ||||
|         GlobalData = App.GetService<GlobalData>(); | ||||
|         ChannelTable = channel; | ||||
|         ChannelId = channel.Id; | ||||
|         //底层配置 | ||||
| @@ -130,7 +131,9 @@ public class ChannelThread | ||||
|     /// </summary> | ||||
|     private ConcurrentDictionary<long, CancellationTokenSource> CancellationTokenSources { get; set; } = new(); | ||||
|  | ||||
|     public void AddDriver(DriverBase driverBase) | ||||
|     public bool IsCollect { get; private set; } | ||||
|  | ||||
|     internal void AddDriver(DriverBase driverBase) | ||||
|     { | ||||
|         DriverBases.Add(driverBase); | ||||
|         driverBase.ChannelThread = this; | ||||
| @@ -151,6 +154,7 @@ public class ChannelThread | ||||
|         } | ||||
|         var token = CancellationTokenSources.GetOrAdd(0, new CancellationTokenSource()); | ||||
|         CancellationTokenSources.TryAdd(driverBase.DeviceId, CancellationTokenSource.CreateLinkedTokenSource(token.Token)); | ||||
|         IsCollect = driverBase.IsCollect; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -158,7 +162,7 @@ public class ChannelThread | ||||
|     /// </summary> | ||||
|     /// <param name="deviceId"></param> | ||||
|     /// <returns></returns> | ||||
|     public async Task RemoveDriverAsync(long deviceId) | ||||
|     internal async Task RemoveDriverAsync(long deviceId) | ||||
|     { | ||||
|         var driverBase = DriverBases.FirstOrDefault(a => a.DeviceId == deviceId); | ||||
|         if (driverBase != null) | ||||
| @@ -177,24 +181,41 @@ public class ChannelThread | ||||
|             { | ||||
|                 await Task.Delay(500); | ||||
|             } | ||||
|             //去除全局设备 | ||||
|             if (IsCollect) | ||||
|             { | ||||
|                 lock (GlobalData.CollectDevices) | ||||
|                 { | ||||
|                     GlobalData.CollectDevices.RemoveWhere(it => it.Id == driverBase.DeviceId); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             else | ||||
|             { | ||||
|                 lock (GlobalData.BusinessDevices) | ||||
|                 { | ||||
|                     GlobalData.BusinessDevices.RemoveWhere(it => it.Id == driverBase.DeviceId); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             DriverBases.Remove(driverBase); | ||||
|             CancellationTokenSources.Remove(deviceId); | ||||
|             token?.Dispose(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public DriverBase GetDriver(long deviceId) | ||||
|     internal DriverBase GetDriver(long deviceId) | ||||
|     { | ||||
|         var driverBase = DriverBases.FirstOrDefault(a => a.DeviceId == deviceId); | ||||
|         return driverBase; | ||||
|     } | ||||
|  | ||||
|     public IEnumerable<DriverBase> GetDriverEnumerable() | ||||
|     internal IEnumerable<DriverBase> GetDriverEnumerable() | ||||
|     { | ||||
|         return DriverBases; | ||||
|     } | ||||
|  | ||||
|     public bool Has(long deviceId) | ||||
|     internal bool Has(long deviceId) | ||||
|     { | ||||
|         return DriverBases.Any(a => a.DeviceId == deviceId); | ||||
|     } | ||||
| @@ -222,7 +243,7 @@ public class ChannelThread | ||||
|     /// <summary> | ||||
|     /// 停止插件前,执行取消传播 | ||||
|     /// </summary> | ||||
|     public virtual void BeforeStopThread() | ||||
|     internal virtual void BeforeStopThread() | ||||
|     { | ||||
|         CancellationTokenSources.ParallelForEach(cancellationToken => | ||||
|         { | ||||
| @@ -245,7 +266,7 @@ public class ChannelThread | ||||
|     /// <summary> | ||||
|     /// 开始 | ||||
|     /// </summary> | ||||
|     public virtual async Task StartThreadAsync() | ||||
|     internal virtual async Task StartThreadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -278,7 +299,7 @@ public class ChannelThread | ||||
|     /// <summary> | ||||
|     /// 停止 | ||||
|     /// </summary> | ||||
|     public virtual async Task StopThreadAsync() | ||||
|     internal virtual async Task StopThreadAsync(bool isRemoveDevice) | ||||
|     { | ||||
|         if (DriverTask == null) | ||||
|         { | ||||
| @@ -298,6 +319,24 @@ public class ChannelThread | ||||
|                 } | ||||
|             }); | ||||
|             try { await DriverTask.WaitAsync(TimeSpan.FromMinutes(3)); } catch (OperationCanceledException) { } | ||||
|             if (isRemoveDevice) | ||||
|             { | ||||
|                 //去除全局设备 | ||||
|                 if (IsCollect) | ||||
|                 { | ||||
|                     lock (GlobalData.CollectDevices) | ||||
|                     { | ||||
|                         GlobalData.CollectDevices.RemoveWhere(it => DriverBases.Any(a => a.DeviceId == it.Id)); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     lock (GlobalData.BusinessDevices) | ||||
|                     { | ||||
|                         GlobalData.BusinessDevices.RemoveWhere(it => DriverBases.Any(a => a.DeviceId == it.Id)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             CancellationTokenSources.Clear(); | ||||
|             DriverTask?.SafeDispose(); | ||||
|             DriverTask = null; | ||||
|   | ||||
| @@ -42,11 +42,6 @@ public abstract class CollectBase : DriverBase | ||||
|  | ||||
|     public override async Task AfterStopAsync() | ||||
|     { | ||||
|         //去除全局设备变量 | ||||
|         lock (GlobalData.CollectDevices) | ||||
|         { | ||||
|             GlobalData.CollectDevices.RemoveWhere(it => it.Id == DeviceId); | ||||
|         } | ||||
|         await base.AfterStopAsync(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -46,6 +46,8 @@ public class Startup : AppStartup | ||||
|         services.AddHostedService<CollectDeviceWorker>(); | ||||
|         services.AddHostedService<BusinessDeviceWorker>(); | ||||
|         services.AddHostedService<AlarmWorker>(); | ||||
|         services.AddConfigurableOptions<ManagementOptions>(); | ||||
|         services.AddHostedService<ManagementWoker>(); | ||||
|  | ||||
|         TypeExtension.DefaultDisplayNameFuncs.Add(a => a.GetCustomAttribute<DynamicPropertyAttribute>()?.Description); | ||||
|     } | ||||
|   | ||||
| @@ -25,11 +25,18 @@ | ||||
| 		<PackageReference Include="CS-Script" Version="4.8.14" /> | ||||
| 		<!--CS-Script与Furion冲突,直接安装覆盖版本--> | ||||
| 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="2.0.0-beta.278" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|  | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <None Update="Config\Management.Production.json"> | ||||
| 	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
| 	  </None> | ||||
| 	  <None Update="Config\Management.Development.json"> | ||||
| 	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
| 	  </None> | ||||
| 	  <None Update="Config\Gateway.Development.json"> | ||||
| 	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
| 	  </None> | ||||
|   | ||||
| @@ -83,6 +83,7 @@ public class AlarmWorker : BackgroundService | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             foreach (var item in GlobalData.CollectDevices) | ||||
|             { | ||||
|                 item.VariableRunTimes?.ForEach(v => { v.VariableCollectChange += DeviceVariableChange; }); | ||||
| @@ -446,7 +447,7 @@ public class AlarmWorker : BackgroundService | ||||
|  | ||||
|     private EasyLock _easyLock = new(false); | ||||
|  | ||||
|     private async Task CollectDeviceWorker_Starting() | ||||
|     private async Task CollectDeviceWorker_Started() | ||||
|     { | ||||
|         await StartAsync(); | ||||
|     } | ||||
| @@ -459,7 +460,6 @@ public class AlarmWorker : BackgroundService | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("报警服务启动"); | ||||
|         _appLifetime.ApplicationStarted.Register(() => { _easyLock.Release(); _easyLock = null; }); | ||||
|         await base.StartAsync(cancellationToken); | ||||
|     } | ||||
| @@ -467,7 +467,6 @@ public class AlarmWorker : BackgroundService | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("报警服务停止"); | ||||
|         return base.StopAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
| @@ -475,8 +474,9 @@ public class AlarmWorker : BackgroundService | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|         WorkerUtil.GetWoker<CollectDeviceWorker>().Starting += CollectDeviceWorker_Starting; | ||||
|         WorkerUtil.GetWoker<CollectDeviceWorker>().Stoping += CollectDeviceWorker_Stoping; | ||||
|         var collectDeviceWorker = WorkerUtil.GetWoker<CollectDeviceWorker>(); | ||||
|         collectDeviceWorker.Started += CollectDeviceWorker_Started; | ||||
|         collectDeviceWorker.Stoping += CollectDeviceWorker_Stoping; | ||||
|         GlobalData = _serviceScope.ServiceProvider.GetService<GlobalData>(); | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|   | ||||
| @@ -8,8 +8,6 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| using Furion.Logging.Extensions; | ||||
| 
 | ||||
| using Mapster; | ||||
| 
 | ||||
| using Microsoft.Extensions.Hosting; | ||||
| @@ -26,135 +24,27 @@ public class BusinessDeviceWorker : DeviceWorker | ||||
|     { | ||||
|         _logger = _serviceScope.ServiceProvider.GetService<ILoggerFactory>().CreateLogger("业务设备服务"); | ||||
|     } | ||||
| 
 | ||||
|     #region public 设备创建更新结束 | ||||
| 
 | ||||
|     public async Task RestartAsync() | ||||
|     private async Task CollectDeviceWorker_Starting() | ||||
|     { | ||||
|         await StopAsync(); | ||||
|         if (started) | ||||
|         { | ||||
|             await StopAsync(true); | ||||
|         } | ||||
|         await CreatThreadsAsync(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private async Task CollectDeviceWorker_Started() | ||||
|     { | ||||
|         await Task.Delay(1000); | ||||
|         await StartAsync(); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 开始 | ||||
|     /// </summary> | ||||
|     public async Task StartAsync() | ||||
|     private async Task CollectDeviceWorker_Stoping() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             if (ChannelThreads.Count == 0) | ||||
|             { | ||||
|                 await CreatAllChannelThreadsAsync(); | ||||
|                 await ProtectedStarting(); | ||||
|             } | ||||
|             await StartAllChannelThreadsAsync(); | ||||
|             await ProtectedStarted(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "启动发生错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|         await StopAsync(true); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 开始 | ||||
|     /// </summary> | ||||
|     public async Task CreatThreadsAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             if (ChannelThreads.Count == 0) | ||||
|             { | ||||
|                 await CreatAllChannelThreadsAsync(); | ||||
|                 await ProtectedStarting(); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "启动发生错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 停止 | ||||
|     /// </summary> | ||||
|     public async Task StopAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             await BeforeRemoveAllChannelThreadAsync(); | ||||
|             await ProtectedStoping(); | ||||
| 
 | ||||
|             await RemoveAllChannelThreadAsync(); | ||||
|             await ProtectedStoped(); | ||||
| 
 | ||||
|             //清空内存列表 | ||||
|             GlobalData.BusinessDevices.Clear(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "停止错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #endregion public 设备创建更新结束 | ||||
| 
 | ||||
|     #region public 设备创建更新结束 | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 创建业务设备线程 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected async Task CreatAllChannelThreadsAsync() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             _logger.LogInformation("正在获取业务设备组态信息"); | ||||
|             var deviceRunTimes = await _serviceScope.ServiceProvider.GetService<IDeviceService>().GetBusinessDeviceRuntimeAsync(); | ||||
|             _logger.LogInformation("获取业务设备组态信息完成"); | ||||
|             var idSet = deviceRunTimes.ToDictionary(a => a.Id); | ||||
|             var result = deviceRunTimes.Where(a => !idSet.ContainsKey(a.RedundantDeviceId) && !a.IsRedundant).ToList(); | ||||
|             result.ForEach(collectDeviceRunTime => | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         DriverBase driverBase = collectDeviceRunTime.CreatDriver(PluginService); | ||||
|                         GetChannelThread(driverBase); | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         _logger.LogError(ex, $"{collectDeviceRunTime.Name}初始化错误!"); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #endregion public 设备创建更新结束 | ||||
| 
 | ||||
|     #region 设备信息获取 | ||||
| 
 | ||||
|     /// <summary> | ||||
| @@ -187,22 +77,6 @@ public class BusinessDeviceWorker : DeviceWorker | ||||
|         await base.StartAsync(cancellationToken); | ||||
|     } | ||||
| 
 | ||||
|     private async Task CollectDeviceWorker_Starting() | ||||
|     { | ||||
|         await CreatThreadsAsync(); | ||||
|     } | ||||
| 
 | ||||
|     private async Task CollectDeviceWorker_Started() | ||||
|     { | ||||
|         await Task.Delay(1000); | ||||
|         await StartAsync(); | ||||
|     } | ||||
| 
 | ||||
|     private async Task CollectDeviceWorker_Stoping() | ||||
|     { | ||||
|         await StopAsync(); | ||||
|     } | ||||
| 
 | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
| @@ -215,18 +89,55 @@ public class BusinessDeviceWorker : DeviceWorker | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|         WorkerUtil.GetWoker<CollectDeviceWorker>().Starting += CollectDeviceWorker_Starting; | ||||
|         WorkerUtil.GetWoker<CollectDeviceWorker>().Started += CollectDeviceWorker_Started; | ||||
|         WorkerUtil.GetWoker<CollectDeviceWorker>().Stoping += CollectDeviceWorker_Stoping; | ||||
|         ManagementWoker = WorkerUtil.GetWoker<ManagementWoker>(); | ||||
|         var collectDeviceWorker = WorkerUtil.GetWoker<CollectDeviceWorker>(); | ||||
|         collectDeviceWorker.Starting += CollectDeviceWorker_Starting; | ||||
|         collectDeviceWorker.Started += CollectDeviceWorker_Started; | ||||
|         collectDeviceWorker.Stoping += CollectDeviceWorker_Stoping; | ||||
|         PluginService = _serviceScope.ServiceProvider.GetService<IPluginService>(); | ||||
|         GlobalData = _serviceScope.ServiceProvider.GetService<GlobalData>(); | ||||
|         await WhileExecuteAsync(stoppingToken); | ||||
|     } | ||||
| 
 | ||||
|     #endregion worker服务 | ||||
| 
 | ||||
|     #region 重写 | ||||
| 
 | ||||
|     protected override async Task<IEnumerable<DeviceRunTime>> GetDeviceRunTimeAsync(long deviceId) | ||||
|     { | ||||
|         return await _serviceScope.ServiceProvider.GetService<IDeviceService>().GetBusinessDeviceRuntimeAsync(deviceId); | ||||
|     } | ||||
| 
 | ||||
|     #endregion worker服务 | ||||
|     /// <summary> | ||||
|     /// 读取数据库,创建全部设备 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected override async Task CreatAllChannelThreadsAsync() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             _logger.LogInformation("正在获取业务设备组态信息"); | ||||
|             var deviceRunTimes = await _serviceScope.ServiceProvider.GetService<IDeviceService>().GetBusinessDeviceRuntimeAsync(); | ||||
|             _logger.LogInformation("获取业务设备组态信息完成"); | ||||
|             var idSet = deviceRunTimes.ToDictionary(a => a.Id); | ||||
|             var result = deviceRunTimes.Where(a => !idSet.ContainsKey(a.RedundantDeviceId) && !a.IsRedundant).ToList(); | ||||
|             result.ForEach(collectDeviceRunTime => | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         DriverBase driverBase = collectDeviceRunTime.CreatDriver(PluginService); | ||||
|                         GetChannelThread(driverBase); | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         _logger.LogError(ex, $"{collectDeviceRunTime.Name}初始化错误!"); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #endregion 重写 | ||||
| } | ||||
| @@ -25,56 +25,45 @@ public class CollectDeviceWorker : DeviceWorker | ||||
|         _logger = _serviceScope.ServiceProvider.GetService<ILoggerFactory>().CreateLogger("采集设备服务"); | ||||
|     } | ||||
| 
 | ||||
|     #region public 设备创建更新结束 | ||||
|     #region worker服务 | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 重启采集服务 | ||||
|     /// </summary> | ||||
|     public async Task RestartAsync() | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
| 
 | ||||
|             //停止采集服务 | ||||
|             await BeforeRemoveAllChannelThreadAsync(); | ||||
|             await ProtectedStoping(); | ||||
|             //完全停止全部采集线程 | ||||
|             await RemoveAllChannelThreadAsync(); | ||||
|             await ProtectedStoped(); | ||||
| 
 | ||||
|             //清空内存列表 | ||||
|             GlobalData.CollectDevices.Clear(); | ||||
| 
 | ||||
|             //创建全部采集线程 | ||||
|             await CreatAllChannelThreadsAsync(); | ||||
|             await ProtectedStarting(); | ||||
| 
 | ||||
|             //开始全部采集线程 | ||||
|             await StartAllChannelThreadsAsync(); | ||||
|             await ProtectedStarted(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "重启错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|         using var stoppingToken = new CancellationTokenSource(); | ||||
|         _stoppingToken = stoppingToken.Token; | ||||
|         stoppingToken.Cancel(); | ||||
|         await StopThreadAsync(true); | ||||
|         await base.StopAsync(cancellationToken); | ||||
|     } | ||||
| 
 | ||||
|     #endregion public 设备创建更新结束 | ||||
|     #endregion worker服务 | ||||
| 
 | ||||
|     #region Private | ||||
|     #region 重写 | ||||
| 
 | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|         ManagementWoker = WorkerUtil.GetWoker<ManagementWoker>(); | ||||
|         PluginService = _serviceScope.ServiceProvider.GetService<IPluginService>(); | ||||
|         GlobalData = _serviceScope.ServiceProvider.GetService<GlobalData>(); | ||||
|         //重启采集线程,会启动其他后台服务 | ||||
|         await ManagementWoker.StartLock.WaitAsync(); | ||||
|         //await RestartAsync(); | ||||
|         await WhileExecuteAsync(stoppingToken); | ||||
|     } | ||||
| 
 | ||||
|     protected override async Task<IEnumerable<DeviceRunTime>> GetDeviceRunTimeAsync(long deviceId) | ||||
|     { | ||||
|         return await _serviceScope.ServiceProvider.GetService<DeviceService>().GetCollectDeviceRuntimeAsync(deviceId); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 创建设备采集线程 | ||||
|     /// 读取数据库,创建全部设备 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected virtual async Task CreatAllChannelThreadsAsync() | ||||
|     protected override async Task CreatAllChannelThreadsAsync() | ||||
|     { | ||||
|         if (!_stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
| @@ -101,41 +90,5 @@ public class CollectDeviceWorker : DeviceWorker | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected override async Task<IEnumerable<DeviceRunTime>> GetDeviceRunTimeAsync(long deviceId) | ||||
|     { | ||||
|         return await _serviceScope.ServiceProvider.GetService<DeviceService>().GetCollectDeviceRuntimeAsync(deviceId); | ||||
|     } | ||||
| 
 | ||||
|     #endregion Private | ||||
| 
 | ||||
|     #region worker服务 | ||||
| 
 | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         using var stoppingToken = new CancellationTokenSource(); | ||||
|         _stoppingToken = stoppingToken.Token; | ||||
|         stoppingToken.Cancel(); | ||||
|         await BeforeRemoveAllChannelThreadAsync(); | ||||
|         //停止其他后台服务 | ||||
|         await ProtectedStoping(); | ||||
|         //停止全部采集线程 | ||||
|         await RemoveAllChannelThreadAsync(); | ||||
|         //停止其他后台服务 | ||||
|         await ProtectedStoped(); | ||||
|         await base.StopAsync(cancellationToken); | ||||
|     } | ||||
| 
 | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|         PluginService = _serviceScope.ServiceProvider.GetService<IPluginService>(); | ||||
|         GlobalData = _serviceScope.ServiceProvider.GetService<GlobalData>(); | ||||
|         //重启采集线程,会启动其他后台服务 | ||||
|         await RestartAsync(); | ||||
|         await WhileExecuteAsync(stoppingToken); | ||||
|     } | ||||
| 
 | ||||
|     #endregion worker服务 | ||||
|     #endregion 重写 | ||||
| } | ||||
| @@ -41,6 +41,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
| 
 | ||||
|     protected IPluginService PluginService; | ||||
|     protected GlobalData GlobalData; | ||||
|     protected ManagementWoker ManagementWoker; | ||||
|     protected IServiceScope _serviceScope; | ||||
|     private readonly IHostApplicationLifetime _appLifetime; | ||||
| 
 | ||||
| @@ -61,8 +62,6 @@ public abstract class DeviceWorker : BackgroundService | ||||
|     /// </summary> | ||||
|     protected ConcurrentList<ChannelThread> ChannelThreads { get; set; } = new(); | ||||
| 
 | ||||
|     #region public 设备创建更新结束 | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 控制设备线程启停 | ||||
|     /// </summary> | ||||
| @@ -77,9 +76,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
|             DriverBases.FirstOrDefault(it => it.DeviceId == deviceId)?.PasueThread(isStart); | ||||
|     } | ||||
| 
 | ||||
|     #endregion public 设备创建更新结束 | ||||
| 
 | ||||
|     #region Private | ||||
|     #region protected | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 根据设备生成/获取通道线程管理器 | ||||
| @@ -94,6 +91,11 @@ public abstract class DeviceWorker : BackgroundService | ||||
|             var channelThread = ChannelThreads.FirstOrDefault(t => t.ChannelId == channelId); | ||||
|             if (channelThread != null) | ||||
|             { | ||||
|                 if (channelThread.IsCollect != driverBase.IsCollect) | ||||
|                 { | ||||
|                     _logger.LogWarning($"设备{driverBase.DeviceName}与通道{channelId}的其他设备类型不相同,不能选择同一个通道"); | ||||
|                     return null; | ||||
|                 } | ||||
|                 channelThread.AddDriver(driverBase); | ||||
|                 return channelThread; | ||||
|             } | ||||
| @@ -129,7 +131,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
|     /// <summary> | ||||
|     /// 删除通道线程,并且释放资源 | ||||
|     /// </summary> | ||||
|     protected async Task RemoveAllChannelThreadAsync() | ||||
|     protected async Task RemoveAllChannelThreadAsync(bool isRemoveDevice) | ||||
|     { | ||||
|         await BeforeRemoveAllChannelThreadAsync(); | ||||
| 
 | ||||
| @@ -137,15 +139,15 @@ public abstract class DeviceWorker : BackgroundService | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await channelThread.StopThreadAsync(); | ||||
|                 await channelThread.StopThreadAsync(isRemoveDevice); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger?.LogError(ex, channelThread.ToString()); | ||||
|             } | ||||
|         }, Environment.ProcessorCount / 2); | ||||
| 
 | ||||
|         ChannelThreads.Clear(); | ||||
|         if (isRemoveDevice) | ||||
|             ChannelThreads.Clear(); | ||||
|     } | ||||
| 
 | ||||
|     protected async Task BeforeRemoveAllChannelThreadAsync() | ||||
| @@ -180,12 +182,55 @@ public abstract class DeviceWorker : BackgroundService | ||||
|             { | ||||
|                 if (!_stoppingToken.IsCancellationRequested) | ||||
|                 { | ||||
|                     await StartChannelThreadAsync(item); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async Task StartChannelThreadAsync(ChannelThread item) | ||||
|     { | ||||
|         if (item.IsCollect) | ||||
|         { | ||||
|             if (ManagementWoker.IsStart) | ||||
|             { | ||||
|                 //采集设备启动 | ||||
|                 await item.StartThreadAsync(); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (ManagementWoker.IsStart) | ||||
|             { | ||||
|                 //业务设备启动 | ||||
|                 await item.StartThreadAsync(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (ManagementWoker.Options.Redundancy.IsStartBusinessDevice) | ||||
|                 { | ||||
|                     //业务设备启动 | ||||
|                     await item.StartThreadAsync(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #endregion protected | ||||
| 
 | ||||
|     #region 单个重启 | ||||
| 
 | ||||
|     private void SetRedundantDevice(DeviceRunTime? dev, Device? newDev) | ||||
|     { | ||||
|         dev.DevicePropertys = newDev.DevicePropertys; | ||||
|         dev.Description = newDev.Description; | ||||
|         dev.ChannelId = newDev.ChannelId; | ||||
|         dev.Enable = newDev.Enable; | ||||
|         dev.IntervalTime = newDev.IntervalTime; | ||||
|         dev.Name = newDev.Name; | ||||
|         dev.PluginName = newDev.PluginName; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 更新设备线程 | ||||
|     /// </summary> | ||||
| @@ -203,7 +248,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
| 
 | ||||
|                 var dev = isChanged ? (await GetDeviceRunTimeAsync(deviceId)).FirstOrDefault() : channelThread.GetDriver(deviceId).CurrentDevice; | ||||
| 
 | ||||
|                 //这里先停止采集,操作会使线程取消,需要重新恢复线程 | ||||
|                 //先停止采集,操作会使线程取消,需要重新恢复线程 | ||||
|                 await channelThread.RemoveDriverAsync(deviceId); | ||||
|                 if (isChanged) | ||||
|                     await ProtectedStoped(); | ||||
| @@ -218,7 +263,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
|                             await ProtectedStarting(); | ||||
|                         try | ||||
|                         { | ||||
|                             await newChannelThread.StartThreadAsync(); | ||||
|                             await StartChannelThreadAsync(newChannelThread); | ||||
|                             if (isChanged) | ||||
|                                 await ProtectedStarted(); | ||||
|                         } | ||||
| @@ -312,7 +357,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
|                 var newChannelThread = GetChannelThread(newDriverBase); | ||||
|                 if (newChannelThread != null && newChannelThread.DriverTask == null) | ||||
|                 { | ||||
|                     await newChannelThread.StartThreadAsync(); | ||||
|                     await StartChannelThreadAsync(newChannelThread); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -322,18 +367,7 @@ public abstract class DeviceWorker : BackgroundService | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void SetRedundantDevice(DeviceRunTime? dev, Device? newDev) | ||||
|     { | ||||
|         dev.DevicePropertys = newDev.DevicePropertys; | ||||
|         dev.Description = newDev.Description; | ||||
|         dev.ChannelId = newDev.ChannelId; | ||||
|         dev.Enable = newDev.Enable; | ||||
|         dev.IntervalTime = newDev.IntervalTime; | ||||
|         dev.Name = newDev.Name; | ||||
|         dev.PluginName = newDev.PluginName; | ||||
|     } | ||||
| 
 | ||||
|     #endregion Private | ||||
|     #endregion 单个重启 | ||||
| 
 | ||||
|     #region 设备信息获取 | ||||
| 
 | ||||
| @@ -361,10 +395,6 @@ public abstract class DeviceWorker : BackgroundService | ||||
|         return driverPlugin?.DriverUIType; | ||||
|     } | ||||
| 
 | ||||
|     #endregion 设备信息获取 | ||||
| 
 | ||||
|     #region 设备信息获取 | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 获取设备方法 | ||||
|     /// </summary> | ||||
| @@ -439,10 +469,22 @@ public abstract class DeviceWorker : BackgroundService | ||||
| 
 | ||||
|     public event RestartEventHandler Starting; | ||||
| 
 | ||||
|     private volatile bool otherstarted = false; | ||||
| 
 | ||||
|     protected async Task ProtectedStarted() | ||||
|     { | ||||
|         if (Started != null) | ||||
|             await Started.Invoke(); | ||||
|         try | ||||
|         { | ||||
|             if (!otherstarted) | ||||
|                 if (ManagementWoker.IsStart || ManagementWoker.IsStartBusinessDevice) | ||||
|                     if (Started != null) | ||||
|                         await Started.Invoke(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             otherstarted = true; | ||||
|             otherstoped = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected async Task ProtectedStarting() | ||||
| @@ -453,8 +495,17 @@ public abstract class DeviceWorker : BackgroundService | ||||
| 
 | ||||
|     protected async Task ProtectedStoped() | ||||
|     { | ||||
|         if (Stoped != null) | ||||
|             await Stoped.Invoke(); | ||||
|         try | ||||
|         { | ||||
|             if (!otherstoped) | ||||
|                 if (Stoped != null) | ||||
|                     await Stoped.Invoke(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             otherstoped = true; | ||||
|             otherstarted = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected async Task ProtectedStoping() | ||||
| @@ -465,15 +516,6 @@ public abstract class DeviceWorker : BackgroundService | ||||
| 
 | ||||
|     protected abstract Task<IEnumerable<DeviceRunTime>> GetDeviceRunTimeAsync(long deviceId); | ||||
| 
 | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|         PluginService = _serviceScope.ServiceProvider.GetService<IPluginService>(); | ||||
|         GlobalData = _serviceScope.ServiceProvider.GetService<GlobalData>(); | ||||
|         await WhileExecuteAsync(stoppingToken); | ||||
|     } | ||||
| 
 | ||||
|     protected virtual async Task WhileExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
| @@ -544,6 +586,147 @@ public abstract class DeviceWorker : BackgroundService | ||||
|     } | ||||
| 
 | ||||
|     #endregion worker服务 | ||||
| 
 | ||||
|     #region 全部重启 | ||||
| 
 | ||||
|     private EasyLock publicRestartLock = new(); | ||||
| 
 | ||||
|     public async Task RestartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await publicRestartLock.WaitAsync(); | ||||
| 
 | ||||
|             await StopAsync(true); | ||||
|             await StartAsync(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             publicRestartLock.Release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected volatile bool started; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 启动全部设备,如果没有找到设备会创建 | ||||
|     /// </summary> | ||||
|     public async Task StartAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             if (stoped) | ||||
|             { | ||||
|                 ChannelThreads.Clear(); | ||||
|                 await CreatAllChannelThreadsAsync(); | ||||
|                 await ProtectedStarting(); | ||||
|             } | ||||
|             await StartAllChannelThreadsAsync(); | ||||
|             await ProtectedStarted(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "启动发生错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             started = true; | ||||
|             stoped = false; | ||||
|             otherstoped = false; | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 初始化,如果没有找到设备会创建 | ||||
|     /// </summary> | ||||
|     public async Task CreatThreadsAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             if (stoped) | ||||
|             { | ||||
|                 ChannelThreads.Clear(); | ||||
|                 await CreatAllChannelThreadsAsync(); | ||||
|                 await ProtectedStarting(); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "启动发生错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             started = true; | ||||
|             stoped = false; | ||||
|             otherstoped = false; | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 停止 | ||||
|     /// </summary> | ||||
|     public async Task StopAsync(bool isRemoveDevice) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await restartLock.WaitAsync(); | ||||
|             await singleRestartLock.WaitAsync(); | ||||
|             await StopThreadAsync(isRemoveDevice); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "停止错误"); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             singleRestartLock.Release(); | ||||
|             restartLock.Release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected async Task StopThreadAsync(bool isRemoveDevice = true) | ||||
|     { | ||||
|         if (started) | ||||
|         { | ||||
|             //取消全部采集线程 | ||||
|             await BeforeRemoveAllChannelThreadAsync(); | ||||
|             if (isRemoveDevice) | ||||
|                 //取消其他后台服务 | ||||
|                 await ProtectedStoping(); | ||||
|             //停止全部采集线程 | ||||
|             await RemoveAllChannelThreadAsync(isRemoveDevice); | ||||
|             if (isRemoveDevice) | ||||
|                 //停止其他后台服务 | ||||
|                 await ProtectedStoped(); | ||||
|             //清空内存列表 | ||||
|         } | ||||
|         started = false; | ||||
|         otherstarted = false; | ||||
|         stoped = true; | ||||
|     } | ||||
| 
 | ||||
|     private volatile bool otherstoped = true; | ||||
|     protected volatile bool stoped = true; | ||||
| 
 | ||||
|     #endregion 全部重启 | ||||
| 
 | ||||
|     #region 读取数据库 | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 读取数据库,创建全部设备 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     protected abstract Task CreatAllChannelThreadsAsync(); | ||||
| 
 | ||||
|     #endregion 读取数据库 | ||||
| } | ||||
| 
 | ||||
| public delegate Task RestartEventHandler(); | ||||
| @@ -0,0 +1,347 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Furion.ConfigurableOptions; | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Dmtp; | ||||
| using TouchSocket.Dmtp.FileTransfer; | ||||
| using TouchSocket.Dmtp.Rpc; | ||||
| using TouchSocket.Rpc; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public class ManagementOptions : IConfigurableOptions | ||||
| { | ||||
|     public string RemoteUri { get; set; } | ||||
|     public string ServerStandbyUri { get; set; } | ||||
|     public int Port { get; set; } | ||||
|     public string VerifyToken { get; set; } | ||||
|     public int HeartbeatInterval { get; set; } | ||||
|     public int MaxErrorCount { get; set; } | ||||
|     public Redundancy Redundancy { get; set; } | ||||
| } | ||||
|  | ||||
| public class Redundancy | ||||
| { | ||||
|     public bool Enable { get; set; } | ||||
|     public bool IsPrimary { get; set; } | ||||
|  | ||||
|     public bool IsStartBusinessDevice { get; set; } | ||||
| } | ||||
|  | ||||
| public class ManagementWoker : BackgroundService | ||||
| { | ||||
|     protected IServiceScope _serviceScope; | ||||
|     private readonly IHostApplicationLifetime _appLifetime; | ||||
|     private readonly ILogger _logger; | ||||
|     private CollectDeviceWorker CollectDeviceWorker; | ||||
|  | ||||
|     /// <inheritdoc cref="ManagementWoker"/> | ||||
|     public ManagementWoker(IServiceScopeFactory serviceScopeFactory, IHostApplicationLifetime appLifetime) | ||||
|     { | ||||
|         _serviceScope = serviceScopeFactory.CreateScope(); | ||||
|         _logger = _serviceScope.ServiceProvider.GetService<ILoggerFactory>().CreateLogger("网关管理服务"); | ||||
|         _appLifetime = appLifetime; | ||||
|     } | ||||
|  | ||||
|     #region worker服务 | ||||
|  | ||||
|     private EasyLock _easyLock = new(); | ||||
|  | ||||
|     internal bool IsStart | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             return isStart; | ||||
|         } | ||||
|         set | ||||
|         { | ||||
|             if (isStart != value) | ||||
|             { | ||||
|                 isStart = value; | ||||
|                 //TODO:触发启动事件 | ||||
|                 if (IsStart) | ||||
|                 { | ||||
|                     //启动采集 | ||||
|                     _ = CollectDeviceWorker.StartAsync(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _ = CollectDeviceWorker.StopAsync(!IsStartBusinessDevice); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private volatile bool isStart = false; | ||||
|  | ||||
|     internal volatile bool IsStartBusinessDevice = true; | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("网关管理服务启动"); | ||||
|         await _easyLock.WaitAsync(); | ||||
|         _appLifetime.ApplicationStarted.Register(() => { _easyLock.Release(); _easyLock = null; }); | ||||
|         await base.StartAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("网关管理服务停止"); | ||||
|         return base.StopAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     internal ManagementOptions Options; | ||||
|     internal GlobalData GlobalData; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 启动锁 | ||||
|     /// </summary> | ||||
|     internal EasyLock StartLock = new(true); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|         CollectDeviceWorker = WorkerUtil.GetWoker<CollectDeviceWorker>(); | ||||
|         GlobalData = _serviceScope.ServiceProvider.GetService<GlobalData>(); | ||||
|         Options = App.GetOptions<ManagementOptions>(); | ||||
|         IsStartBusinessDevice = Options.Redundancy.Enable ? Options.Redundancy.IsStartBusinessDevice : true; | ||||
|         //初始化直接启动 | ||||
|         IsStart = true; | ||||
|         StartLock.Release(); | ||||
|         if (Options.Redundancy.Enable) | ||||
|         { | ||||
|             var udpDmtp = GetUdpDmtp(Options); | ||||
|             await udpDmtp.StartAsync();//启动 | ||||
|  | ||||
|             if (Options.Redundancy.IsPrimary) | ||||
|             { | ||||
|                 //初始化时,主站直接启动 | ||||
|                 IsStart = true; | ||||
|                 StartLock.Release(); | ||||
|             } | ||||
|  | ||||
|             while (!stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     bool online = false; | ||||
|                     var waitInvoke = new DmtpInvokeOption(millisecondsTimeout: 5000) | ||||
|                     { | ||||
|                         FeedbackType = FeedbackType.WaitInvoke, | ||||
|                         Token = stoppingToken, | ||||
|                         Timeout = 3000, | ||||
|                         SerializationType = SerializationType.Json | ||||
|                     }; | ||||
|                     try | ||||
|                     { | ||||
|                         GatewayState? gatewayState = null; | ||||
|                         online = await udpDmtp.PingAsync(3000); | ||||
|                         if (online) | ||||
|                         { | ||||
|                             var readErrorCount = 0; | ||||
|                             while (readErrorCount < Options.MaxErrorCount) | ||||
|                             { | ||||
|                                 try | ||||
|                                 { | ||||
|                                     gatewayState = await udpDmtp.GetDmtpRpcActor().InvokeTAsync<GatewayState>(nameof(ReverseCallbackServer.GetGatewayStateAsync), waitInvoke, IsStart); | ||||
|                                     break; | ||||
|                                 } | ||||
|                                 catch | ||||
|                                 { | ||||
|                                     readErrorCount++; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         if (gatewayState != null) | ||||
|                         { | ||||
|                             if (gatewayState.IsPrimary == Options.Redundancy.IsPrimary) | ||||
|                             { | ||||
|                                 if (!IsStart) | ||||
|                                 { | ||||
|                                     _logger.LogInformation("主备站设置重复!"); | ||||
|                                     IsStart = true; | ||||
|                                 } | ||||
|                                 await Task.Delay(1000); | ||||
|                                 continue; | ||||
|                             } | ||||
|                         } | ||||
|                         if (gatewayState == null) | ||||
|                         { | ||||
|                             //无法获取状态,启动本机 | ||||
|                             if (!IsStart) | ||||
|                             { | ||||
|                                 _logger.LogInformation("无法连接冗余站点,本机将切换到正常状态"); | ||||
|                                 IsStart = true; | ||||
|                             } | ||||
|                         } | ||||
|                         else if (gatewayState.IsPrimary) | ||||
|                         { | ||||
|                             //主站已经启动 | ||||
|                             if (gatewayState.IsStart) | ||||
|                             { | ||||
|                                 if (IsStart) | ||||
|                                 { | ||||
|                                     _logger.LogInformation("主站已恢复,本机(从站)将切换到备用状态"); | ||||
|                                     IsStart = false; | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 //等待主站切换到正常后,再停止从站 | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             //从站已经启动 | ||||
|                             if (gatewayState.IsStart) | ||||
|                             { | ||||
|                                 //等待从站切换到备用后,再启动主站 | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 if (!IsStart) | ||||
|                                 { | ||||
|                                     _logger.LogInformation("本机(主站)将切换到正常状态"); | ||||
|                                     IsStart = true; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         StartLock.Release(); | ||||
|                     } | ||||
|                     if (Options.Redundancy.IsPrimary) | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             if (online) | ||||
|                                 await udpDmtp.GetDmtpRpcActor().InvokeAsync(nameof(ReverseCallbackServer.UpdateGatewayDataAsync), waitInvoke, GlobalData.CollectDevices.Adapt<List<DeviceDataWithValue>>(), GlobalData.AllVariables.Adapt<List<VariableDataWithValue>>()); | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             _logger.LogWarning(ex, "同步数据到从站时,发生错误"); | ||||
|                         } | ||||
|                     } | ||||
|                     await Task.Delay(1000, stoppingToken); | ||||
|                 } | ||||
|                 catch (TaskCanceledException) | ||||
|                 { | ||||
|                 } | ||||
|                 catch (ObjectDisposedException) | ||||
|                 { | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _logger.LogWarning(ex, "循环线程出错"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             //直接启动 | ||||
|             IsStart = true; | ||||
|             //无冗余,直接启动采集服务 | ||||
|             _logger.LogInformation("不启用网关冗余站点"); | ||||
|             StartLock.Release(); | ||||
|         } | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogWarning(ex, "循环线程出错"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion worker服务 | ||||
|  | ||||
|     #region | ||||
|  | ||||
|     private void LogOut(TouchSocket.Core.LogLevel logLevel, object source, string message, Exception exception) | ||||
|     { | ||||
|         _logger?.Log_Out(logLevel, source, message, exception); | ||||
|     } | ||||
|  | ||||
|     private UdpDmtp GetUdpDmtp(ManagementOptions options) | ||||
|     { | ||||
|         var udpDmtp = new UdpDmtp(); | ||||
|         var config = new TouchSocketConfig() | ||||
|                .SetRemoteIPHost(options.RemoteUri) | ||||
|                .SetBindIPHost(options.Port) | ||||
|                .SetDmtpOption( | ||||
|             new DmtpOption() { VerifyToken = options.VerifyToken }) | ||||
|                .ConfigureContainer(a => | ||||
|                { | ||||
|                    a.RegisterSingleton<GlobalData>(GlobalData); | ||||
|                    a.AddEasyLogger(LogOut); | ||||
|                    a.AddRpcStore(store => | ||||
|                    { | ||||
|                        store.RegisterServer<ReverseCallbackServer>(); | ||||
|                    }); | ||||
|                }) | ||||
|                .ConfigurePlugins(a => | ||||
|                { | ||||
|                    a.UseDmtpFileTransfer();//必须添加文件传输插件 | ||||
|                                            //a.Add<FilePlugin>(); | ||||
|                    a.UseDmtpHeartbeat()//使用Dmtp心跳 | ||||
|                    .SetTick(TimeSpan.FromMilliseconds(options.HeartbeatInterval)) | ||||
|                    .SetMaxFailCount(options.MaxErrorCount); | ||||
|                    a.UseDmtpRpc(); | ||||
|                }); | ||||
|         if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||||
|         { | ||||
|             config.UseUdpConnReset(); | ||||
|         } | ||||
|         udpDmtp.Setup(config); | ||||
|         return udpDmtp; | ||||
|     } | ||||
|  | ||||
|     #endregion | ||||
| } | ||||
|  | ||||
| internal class GatewayState | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 是否启动 | ||||
|     /// </summary> | ||||
|     public bool IsStart { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 是否主站 | ||||
|     /// </summary> | ||||
|     public bool IsPrimary { get; set; } | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Dmtp.Rpc; | ||||
| using TouchSocket.Rpc; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| internal class ReverseCallbackServer : RpcServer | ||||
| { | ||||
|     private ILog _logger; | ||||
|     private ManagementWoker managementWoker; | ||||
|     private GlobalData globalData; | ||||
|  | ||||
|     public ReverseCallbackServer(ILog log, GlobalData globalData) | ||||
|     { | ||||
|         _logger = log; | ||||
|         managementWoker = WorkerUtil.GetWoker<ManagementWoker>(); | ||||
|         this.globalData = globalData; | ||||
|     } | ||||
|  | ||||
|     private EasyLock easyLock = new(); | ||||
|  | ||||
|     [DmtpRpc(true)]//使用方法名作为调用键 | ||||
|     public async Task<GatewayState> GetGatewayStateAsync(bool isStart) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await easyLock.WaitAsync(); | ||||
|  | ||||
|             //冗余双方站点可能存在同时执行冗余切换的情况 | ||||
|             { | ||||
|                 GatewayState result = new(); | ||||
|                 result.IsStart = managementWoker.IsStart; | ||||
|                 result.IsPrimary = managementWoker.Options.Redundancy.IsPrimary; | ||||
|                 return result; | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             easyLock.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [DmtpRpc(true)]//使用方法名作为调用键 | ||||
|     public async Task UpdateGatewayDataAsync(List<DeviceDataWithValue> deviceDatas, List<VariableDataWithValue> variableDatas) | ||||
|     { | ||||
|         //TODO:获取主站数据 | ||||
|         await Task.CompletedTask; | ||||
|         foreach (var deviceData in deviceDatas) | ||||
|         { | ||||
|             var dev = globalData.CollectDevices.FirstOrDefault(a => a.Id == deviceData.Id); | ||||
|             if (dev != null) | ||||
|             { | ||||
|                 dev.ActiveTime = deviceData.ActiveTime; | ||||
|                 dev.DeviceStatus = deviceData.DeviceStatus; | ||||
|                 dev.LastErrorMessage = deviceData.LastErrorMessage; | ||||
|             } | ||||
|         } | ||||
|         foreach (var variableData in variableDatas) | ||||
|         { | ||||
|             var variableRunTime = globalData.AllVariables.FirstOrDefault(a => a.Id == variableData.Id); | ||||
|             if (variableRunTime != null) | ||||
|             { | ||||
|                 variableRunTime.SetValue(variableData.RawValue, variableData.CollectTime, variableData.IsOnline); | ||||
|                 variableRunTime.SetErrorMessage(variableData.LastErrorMessage); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,78 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// TODO:网关远程管理服务,使用client模式,适用于端口要求严格的网络环境 | ||||
| /// </summary> | ||||
| public class ManagementSlaveWoker : BackgroundService | ||||
| { | ||||
|     protected IServiceScope _serviceScope; | ||||
|     private readonly IHostApplicationLifetime _appLifetime; | ||||
|     private readonly ILogger _logger; | ||||
|  | ||||
|     /// <inheritdoc cref="AlarmWorker"/> | ||||
|     public ManagementSlaveWoker(IServiceScopeFactory serviceScopeFactory, IHostApplicationLifetime appLifetime) | ||||
|     { | ||||
|         _serviceScope = serviceScopeFactory.CreateScope(); | ||||
|         _logger = _serviceScope.ServiceProvider.GetService<ILoggerFactory>().CreateLogger("网关远程管理Slave服务"); | ||||
|         _appLifetime = appLifetime; | ||||
|     } | ||||
|  | ||||
|     #region worker服务 | ||||
|  | ||||
|     private EasyLock _easyLock = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("网关远程管理Slave服务启动"); | ||||
|         await _easyLock.WaitAsync(); | ||||
|         _appLifetime.ApplicationStarted.Register(() => { _easyLock.Release(); _easyLock = null; }); | ||||
|         await base.StartAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("网关远程管理Slave服务停止"); | ||||
|         return base.StopAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion worker服务 | ||||
| } | ||||
| @@ -1,78 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// TODO:网关远程管理服务,使用client模式,适用于端口要求严格的网络环境 | ||||
| /// </summary> | ||||
| public class ManagementMasterWoker : BackgroundService | ||||
| { | ||||
|     protected IServiceScope _serviceScope; | ||||
|     private readonly IHostApplicationLifetime _appLifetime; | ||||
|     private readonly ILogger _logger; | ||||
|  | ||||
|     /// <inheritdoc cref="AlarmWorker"/> | ||||
|     public ManagementMasterWoker(IServiceScopeFactory serviceScopeFactory, IHostApplicationLifetime appLifetime) | ||||
|     { | ||||
|         _serviceScope = serviceScopeFactory.CreateScope(); | ||||
|         _logger = _serviceScope.ServiceProvider.GetService<ILoggerFactory>().CreateLogger("网关远程管理Master服务"); | ||||
|         _appLifetime = appLifetime; | ||||
|     } | ||||
|  | ||||
|     #region worker服务 | ||||
|  | ||||
|     private EasyLock _easyLock = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("网关远程管理Master服务启动"); | ||||
|         await _easyLock.WaitAsync(); | ||||
|         _appLifetime.ApplicationStarted.Register(() => { _easyLock.Release(); _easyLock = null; }); | ||||
|         await base.StartAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("网关远程管理Master服务停止"); | ||||
|         return base.StopAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion worker服务 | ||||
| } | ||||
| @@ -1,78 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Furion.Logging.Extensions; | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 网关更新服务 | ||||
| /// </summary> | ||||
| public class UpdatesWorker : BackgroundService | ||||
| { | ||||
|     protected IServiceScope _serviceScope; | ||||
|     private readonly IHostApplicationLifetime _appLifetime; | ||||
|     private readonly ILogger _logger; | ||||
|  | ||||
|     /// <inheritdoc cref="AlarmWorker"/> | ||||
|     public UpdatesWorker(IServiceScopeFactory serviceScopeFactory, IHostApplicationLifetime appLifetime) | ||||
|     { | ||||
|         _serviceScope = serviceScopeFactory.CreateScope(); | ||||
|         _logger = _serviceScope.ServiceProvider.GetService<ILoggerFactory>().CreateLogger("网关更新服务"); | ||||
|         _appLifetime = appLifetime; | ||||
|     } | ||||
|  | ||||
|     #region worker服务 | ||||
|  | ||||
|     private EasyLock _easyLock = new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override async Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("更新服务启动"); | ||||
|         await _easyLock.WaitAsync(); | ||||
|         _appLifetime.ApplicationStarted.Register(() => { _easyLock.Release(); _easyLock = null; }); | ||||
|         await base.StartAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _logger?.LogInformation("更新服务停止"); | ||||
|         return base.StopAsync(cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await _easyLock?.WaitAsync(); | ||||
|  | ||||
|         while (!stoppingToken.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await Task.Delay(60000, stoppingToken); | ||||
|             } | ||||
|             catch (TaskCanceledException) | ||||
|             { | ||||
|             } | ||||
|             catch (ObjectDisposedException) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion worker服务 | ||||
| } | ||||
| @@ -5,6 +5,7 @@ | ||||
| @using Microsoft.AspNetCore.Authorization; | ||||
| @using Microsoft.Extensions.DependencyInjection | ||||
| @using System.Collections.Concurrent | ||||
| @using Newtonsoft.Json.Linq | ||||
| @using ThingsGateway.Admin.Application; | ||||
| @using ThingsGateway.Admin.Blazor | ||||
| @using ThingsGateway.Core.Extension | ||||
| @@ -76,6 +77,22 @@ | ||||
|     <ItemColTemplate> | ||||
|         @switch (context.Header.Value) | ||||
|         { | ||||
|             case nameof(context.Item.Value): | ||||
|             case nameof(context.Item.RawValue): | ||||
|             case nameof(context.Item.LastSetValue): | ||||
|                 @if (context.Header.CellClass?.Contains("text-truncate") == true) | ||||
|                 { | ||||
|                     var jToken = context.Value == null ? null : JToken.FromObject(context.Value); | ||||
|                     <span title=@jToken> | ||||
|                         @jToken | ||||
|                     </span> | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     @context.Value | ||||
|                 } | ||||
|                 break; | ||||
|  | ||||
|             case nameof(context.Item.IsOnline): | ||||
|                 <EnableChip Value="context.Item.IsOnline" DisabledLabel=@AppService.I18n.T("离线") EnabledLabel=@AppService.I18n.T("在线")> | ||||
|                 </EnableChip> | ||||
| @@ -103,6 +120,7 @@ | ||||
|         } | ||||
|  | ||||
|     </ItemColTemplate> | ||||
|  | ||||
| </AppDataTable> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -108,6 +108,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.OpcUa" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.Dlt645", "plugin\ThingsGateway.Plugin.Dlt645\ThingsGateway.Plugin.Dlt645.csproj", "{96E46443-1F30-4CEB-BBD7-920DD59F1BB7}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.SourceGenerator", "foundation\ThingsGateway.Foundation.SourceGenerator\src\ThingsGateway.Foundation.SourceGenerator.csproj", "{D87C13E8-F5E8-493B-9137-4B5B6FBC46D7}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| @@ -266,6 +268,10 @@ Global | ||||
| 		{96E46443-1F30-4CEB-BBD7-920DD59F1BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{96E46443-1F30-4CEB-BBD7-920DD59F1BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{96E46443-1F30-4CEB-BBD7-920DD59F1BB7}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{D87C13E8-F5E8-493B-9137-4B5B6FBC46D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{D87C13E8-F5E8-493B-9137-4B5B6FBC46D7}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{D87C13E8-F5E8-493B-9137-4B5B6FBC46D7}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{D87C13E8-F5E8-493B-9137-4B5B6FBC46D7}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| @@ -311,9 +317,10 @@ Global | ||||
| 		{0D994CAE-42F3-4CDA-9132-94515D7EF40E} = {D43C0E54-5B4B-4F00-A782-CAB91782D284} | ||||
| 		{6AB13A30-C2DD-479D-BBE3-7ACFC104A416} = {D43C0E54-5B4B-4F00-A782-CAB91782D284} | ||||
| 		{96E46443-1F30-4CEB-BBD7-920DD59F1BB7} = {D43C0E54-5B4B-4F00-A782-CAB91782D284} | ||||
| 		{D87C13E8-F5E8-493B-9137-4B5B6FBC46D7} = {267B2DEB-0207-4F52-94E4-A1FF6B19214D} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {789BA852-9F20-4421-A555-E665A12587D1} | ||||
| 		RESX_NeutralResourcesLanguage = zh-Hans | ||||
| 		SolutionGuid = {789BA852-9F20-4421-A555-E665A12587D1} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
|   | ||||
| @@ -1,42 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.Foundation.Dlt645; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal class Dlt645MasterTest | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// 新建链路 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public IChannel GetChannel() | ||||
|         { | ||||
|             TouchSocketConfig touchSocketConfig = new TouchSocketConfig(); | ||||
|             return touchSocketConfig.GetSerialPortWithOption(new("COM1")); //直接获取串口对象 | ||||
|             //return touchSocketConfig.GetChannel(ChannelTypeEnum.SerialPortClient, null, null, new("COM1"));//通过链路枚举获取对象 | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 新建协议对象 | ||||
|         /// </summary> | ||||
|         /// <param name="channel"></param> | ||||
|         /// <returns></returns> | ||||
|         public IProtocol GetProtocol(IChannel channel) | ||||
|         { | ||||
|             var client = new Dlt645_2007Master(channel); | ||||
|             client.Station = "311111111114";//表号 | ||||
|             return client; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,79 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.Foundation.Modbus; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal class ModbusMasterTest | ||||
|     { | ||||
|         private static ModbusMaster GetMaster() | ||||
|         { | ||||
|             var clientConfig = new TouchSocketConfig(); | ||||
|             clientConfig.ConfigureContainer(a => a.AddConsoleLogger()); | ||||
|             //创建通道,也可以通过TouchSocketConfig.GetChannel扩展获取 | ||||
|             var clientChannel = clientConfig.GetTcpClientWithIPHost("tcp://127.0.0.1:502"); | ||||
|             //var clientChannel = clientConfig.GetSerialPortWithOption("COM1"); | ||||
|             //clientChannel.Logger.LogLevel = LogLevel.Trace; | ||||
|             ModbusMaster modbusMaster = new(clientChannel) | ||||
|             { | ||||
|                 //modbus协议格式 | ||||
|                 ModbusType = Modbus.ModbusTypeEnum.ModbusRtu, | ||||
|                 //ModbusType = Modbus.ModbusTypeEnum.ModbusTcp, | ||||
|             }; | ||||
|             return modbusMaster; | ||||
|         } | ||||
|  | ||||
|         public static void Test() | ||||
|         { | ||||
|             using ModbusMaster modbusMaster = GetMaster(); | ||||
|             //构造实体类对象,传入协议对象与连读打包的最大数量 | ||||
|             ModbusVariable modbusVariable = new(modbusMaster, 100); | ||||
|  | ||||
|             Test(modbusVariable, new ushort[] { 1, 2 }); | ||||
|             Console.WriteLine(modbusVariable.ToJsonString()); | ||||
|             Console.ReadLine(); | ||||
|  | ||||
|             static void Test(ModbusVariable modbusVariable, object value) | ||||
|             { | ||||
|                 //源生成WriteData1与WriteData2方法(Write{属性名称}) | ||||
|                 modbusVariable.WriteData3("123", default); | ||||
|                 modbusVariable.WriteData2(1, default); | ||||
|                 modbusVariable.WriteData1(value, default); | ||||
|  | ||||
|                 //执行连读 | ||||
|                 modbusVariable.MulRead(); | ||||
|                 Console.WriteLine(modbusVariable.ToJsonString()); | ||||
|                 //执行连读 | ||||
|                 modbusVariable.MulRead(); | ||||
|                 Console.WriteLine(modbusVariable.ToJsonString()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [GeneratorVariable] | ||||
|     public partial class ModbusVariable : VariableObject | ||||
|     { | ||||
|         [VariableRuntime(RegisterAddress = "400001;arraylen=2")] | ||||
|         public ushort[] Data1 { get; set; } | ||||
|  | ||||
|         [VariableRuntime(RegisterAddress = "400051")] | ||||
|         public ushort Data2 { get; set; } | ||||
|  | ||||
|         [VariableRuntime(RegisterAddress = "400061;len=10")] | ||||
|         public string Data3 { get; set; } | ||||
|  | ||||
|         public ModbusVariable(IProtocol protocol, int maxPack) : base(protocol, maxPack) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,61 +0,0 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using ThingsGateway.Foundation.Modbus; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal class ModbusMatserTest | ||||
|     { | ||||
|         public async Task Test() | ||||
|         { | ||||
|             var clientConfig = new TouchSocketConfig(); | ||||
|             //创建通道,也可以通过TouchSocketConfig.GetChannel扩展获取 | ||||
|             var clientChannel = clientConfig.GetTcpClientWithIPHost("tcp://127.0.0.1:502"); | ||||
|  | ||||
|             //创建modbus客户端,传入通道 | ||||
|             using ModbusMaster modbusMaster = new(clientChannel) | ||||
|             { | ||||
|                 //modbus协议格式 | ||||
|                 //ModbusType = Modbus.ModbusTypeEnum.ModbusRtu, | ||||
|                 ModbusType = Modbus.ModbusTypeEnum.ModbusTcp, | ||||
|             }; | ||||
|  | ||||
|             //测试5千次 | ||||
|             for (int i = 0; i < 5000; i++) | ||||
|             { | ||||
|                 //读写对应数据类型 | ||||
|                 var result = await modbusMaster.ReadInt32Async("40001", 1); | ||||
|                 if (!result.IsSuccess) | ||||
|                 { | ||||
|                     Console.WriteLine(result); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             var wResult = await modbusMaster.WriteAsync("40001", 1); | ||||
|  | ||||
|             //动态类型读写 | ||||
|             var objResult = await modbusMaster.ReadAsync("40001", 1, DataTypeEnum.Int32); | ||||
|             var objWResult = await modbusMaster.WriteAsync("40001", JToken.FromObject(1), DataTypeEnum.Int32); | ||||
|  | ||||
|             //地址说明 | ||||
|             //单独设置解析顺序 | ||||
|             var objABCDResult = await modbusMaster.ReadAsync("40001;dataformat=badc", 1, DataTypeEnum.Int32); | ||||
|             //单独设置站号 | ||||
|             var objSResult = await modbusMaster.ReadAsync("40001;dataformat=badc;s=2", 1, DataTypeEnum.Int32); | ||||
|             //单独设置写入功能码 | ||||
|             var objFWResult = await modbusMaster.ReadAsync("40001;s=2;w=16", 1, DataTypeEnum.Int16); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -8,21 +8,14 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal class Program | ||||
|     { | ||||
|         private static async Task Main(string[] args) | ||||
|         private static void Main(string[] args) | ||||
|         { | ||||
|             Dlt645MasterTest dlt645MasterTest = new Dlt645MasterTest(); | ||||
|             var channel = dlt645MasterTest.GetChannel(); | ||||
|             var protocol = dlt645MasterTest.GetProtocol(channel); | ||||
|             var data = await protocol.ReadDoubleAsync("02010100"); //读取A相电压 | ||||
|  | ||||
|             Console.WriteLine(data.ToJsonString()); | ||||
|             Console.ReadLine(); | ||||
|             ModbusMasterTest.Test(); | ||||
|             //S7MatserTest.Test(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,89 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using ThingsGateway.Foundation.SiemensS7; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal class S7MatserTest | ||||
|     { | ||||
|         private static void Test(S7Variable s7Variable, ushort value) | ||||
|         { | ||||
|             s7Variable.WriteData1(value, default); | ||||
|             s7Variable.WriteData2(value, default); | ||||
|  | ||||
|             //执行连读 | ||||
|             s7Variable.MulRead(); | ||||
|             Console.WriteLine(s7Variable.ToJsonString()); | ||||
|             //源生成WriteData1与WriteData2方法(Write{属性名称}) | ||||
|             var data1 = s7Variable.WriteData1(value + 10, default); | ||||
|             var data2 = s7Variable.WriteData2(value + 10, default); | ||||
|             //执行连读 | ||||
|             s7Variable.MulRead(); | ||||
|             Console.WriteLine(s7Variable.ToJsonString()); | ||||
|         } | ||||
|  | ||||
|         private static SiemensS7Master GetMaster() | ||||
|         { | ||||
|             var clientConfig = new TouchSocketConfig(); | ||||
|             clientConfig.ConfigureContainer(a => a.AddConsoleLogger()); | ||||
|             //创建通道,也可以通过TouchSocketConfig.GetChannel扩展获取 | ||||
|             var clientChannel = clientConfig.GetTcpClientWithIPHost("tcp://127.0.0.1:102"); | ||||
|             //clientChannel.Logger.LogLevel = LogLevel.Trace; | ||||
|             SiemensS7Master siemensS7Master = new(clientChannel) | ||||
|             { | ||||
|                 SiemensType = SiemensTypeEnum.S1500, | ||||
|             }; | ||||
|             return siemensS7Master; | ||||
|         } | ||||
|         public static void Test() | ||||
|         { | ||||
|             using SiemensS7Master siemensS7Master = GetMaster(); | ||||
|             //构造实体类对象,传入协议对象与连读打包的最大数量 | ||||
|             S7Variable s7Variable = new(siemensS7Master, 100); | ||||
|  | ||||
|             Test(s7Variable, 10); | ||||
|             Console.ReadLine(); | ||||
|  | ||||
|             static void Test(S7Variable s7Variable, ushort value) | ||||
|             { | ||||
|                 s7Variable.WriteData1(value, default); | ||||
|                 s7Variable.WriteData2(value, default); | ||||
|  | ||||
|                 //执行连读 | ||||
|                 s7Variable.MulRead(); | ||||
|                 Console.WriteLine(s7Variable.ToJsonString()); | ||||
|                 //源生成WriteData1与WriteData2方法(Write{属性名称}) | ||||
|                 var data1 = s7Variable.WriteData1(value + 10, default); | ||||
|                 var data2 = s7Variable.WriteData2(value + 10, default); | ||||
|                 //执行连读 | ||||
|                 s7Variable.MulRead(); | ||||
|                 Console.WriteLine(s7Variable.ToJsonString()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     [GeneratorVariable] | ||||
|     public partial class S7Variable : VariableObject | ||||
|     { | ||||
|         [VariableRuntime(RegisterAddress = "M100")] | ||||
|         public ushort Data1 { get; set; } | ||||
|  | ||||
|         [VariableRuntime(RegisterAddress = "M200")] | ||||
|         public ushort Data2 { get; set; } | ||||
|  | ||||
|         public S7Variable(IProtocol protocol, int maxPack) : base(protocol, maxPack) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -5,11 +5,16 @@ | ||||
| 		<OutputPath>$(SolutionDir)bin\$(Configuration)\$(MSBuildProjectName)\</OutputPath> | ||||
| 		<DocumentationFile></DocumentationFile> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\ThingsGateway.Foundation.Dlt645\src\ThingsGateway.Foundation.Dlt645.csproj" /> | ||||
| 		<ProjectReference Include="..\..\ThingsGateway.Foundation.Modbus\src\ThingsGateway.Foundation.Modbus.csproj"> | ||||
| 		</ProjectReference> | ||||
| 		<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="5.0.1.13" /> | ||||
| 		<PackageReference Include="ThingsGateway.Foundation.Dlt645" Version="5.0.1.13" /> | ||||
| 		<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="5.0.1.13" /> | ||||
| 	</ItemGroup> | ||||
| 	<!--<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\ThingsGateway.Foundation.Dlt645\src\ThingsGateway.Foundation.Dlt645.csproj" /> | ||||
| 		<ProjectReference Include="..\..\ThingsGateway.Foundation.Modbus\src\ThingsGateway.Foundation.Modbus.csproj" /> | ||||
| 		<ProjectReference Include="..\..\ThingsGateway.Foundation.SiemensS7\src\ThingsGateway.Foundation.SiemensS7.csproj" /> | ||||
| 		<ProjectReference Include="..\..\ThingsGateway.Foundation.SourceGenerator\src\ThingsGateway.Foundation.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> | ||||
| 	</ItemGroup>--> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -20,7 +20,7 @@ internal static class PackHelper | ||||
|     /// <param name="maxPack">最大打包长度</param> | ||||
|     /// <param name="defaultIntervalTime">默认间隔时间</param> | ||||
|     /// <returns></returns> | ||||
|     public static List<T> LoadSourceRead<T>(ProtocolBase device, List<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSourceT<IVariable>, new() | ||||
|     public static List<T> LoadSourceRead<T>(ProtocolBase device, List<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new() | ||||
|     { | ||||
|         var byteConverter = device.ThingsGatewayBitConverter; | ||||
|         var result = new List<T>(); | ||||
|   | ||||
| @@ -7,8 +7,6 @@ | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="$(SolutionDir)\foundation\ThingsGateway.Foundation\src\ThingsGateway.Foundation.csproj"> | ||||
| 			<Private>True</Private> | ||||
| 			<CopyLocalSatelliteAssemblies>True</CopyLocalSatelliteAssemblies> | ||||
| 		</ProjectReference> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public class PackHelper | ||||
|     /// <param name="maxPack">最大打包长度</param> | ||||
|     /// <param name="defaultIntervalTime">默认间隔时间</param> | ||||
|     /// <returns></returns> | ||||
|     public static List<T> LoadSourceRead<T>(IProtocol device, List<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSourceT<IVariable>, new() | ||||
|     public static List<T> LoadSourceRead<T>(IProtocol device, List<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new() | ||||
|     { | ||||
|         if (deviceVariables == null) | ||||
|             throw new ArgumentNullException(nameof(deviceVariables)); | ||||
| @@ -94,7 +94,7 @@ public class PackHelper | ||||
|         return deviceVariableSourceReads; | ||||
|     } | ||||
|  | ||||
|     private static List<T> LoadSourceRead<T>(Dictionary<ModbusAddress, IVariable> addressList, int functionCode, int intervalTime, int maxPack) where T : IVariableSourceT<IVariable>, new() | ||||
|     private static List<T> LoadSourceRead<T>(Dictionary<ModbusAddress, IVariable> addressList, int functionCode, int intervalTime, int maxPack) where T : IVariableSource, new() | ||||
|     { | ||||
|         List<T> sourceReads = new(); | ||||
|         //按地址和长度排序 | ||||
|   | ||||
| @@ -7,8 +7,6 @@ | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="$(SolutionDir)\foundation\ThingsGateway.Foundation\src\ThingsGateway.Foundation.csproj"> | ||||
| 		  <Private>True</Private> | ||||
| 		  <CopyLocalSatelliteAssemblies>True</CopyLocalSatelliteAssemblies> | ||||
| 		</ProjectReference> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 		<Import Project="$(SolutionDir)Foundation.props" /> | ||||
| 	<Import Project="$(SolutionDir)Foundation.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<Description>工业设备通讯协议-OpcDa协议</Description> | ||||
| 		<DocumentationFile></DocumentationFile> | ||||
| 		<IncludeSymbols>false</IncludeSymbols> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -55,19 +55,14 @@ internal class SiemensS7DataHandleAdapter : ReadWriteDevicesSingleStreamDataHand | ||||
|  | ||||
|                 default: | ||||
|                     //添加返回代码校验 | ||||
|                     if (response.Length == 22) | ||||
|  | ||||
|                     if (response[5] == 0xD0)//首次握手0XD0连接确认 | ||||
|                     { | ||||
|                         if (response[21] != 0x00) | ||||
|                         { | ||||
|                             result = new($"PLC返回错误,返回代码{response[21].ToString("X2")}") { Content = response, Content2 = FilterResult.Success }; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             result = new() { Content = response, Content2 = FilterResult.Success }; | ||||
|                         } | ||||
|                         result = new() { Content = response, Content2 = FilterResult.Success }; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         //其余情况判断错误代码 | ||||
|                         if (response[17] + response[18] > 0) | ||||
|                         { | ||||
|                             result = new($"PLC返回错误,错误类型{response[17].ToString("X2")}错误代码:{response[18].ToString("X2")}") { Content = response, Content2 = FilterResult.Success }; | ||||
| @@ -78,6 +73,7 @@ internal class SiemensS7DataHandleAdapter : ReadWriteDevicesSingleStreamDataHand | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ internal static class PackHelper | ||||
|     /// <param name="maxPack">最大打包长度</param> | ||||
|     /// <param name="defaultIntervalTime">默认间隔时间</param> | ||||
|     /// <returns></returns> | ||||
|     public static List<T> LoadSourceRead<T>(SiemensS7Master device, List<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSourceT<IVariable>, new() | ||||
|     public static List<T> LoadSourceRead<T>(SiemensS7Master device, List<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new() | ||||
|     { | ||||
|         var byteConverter = device.ThingsGatewayBitConverter; | ||||
|         var result = new List<T>(); | ||||
| @@ -138,7 +138,7 @@ internal static class PackHelper | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private static List<T> LoadSourceRead<T>(Dictionary<SiemensAddress, IVariable> addressList, int functionCode, int intervalTime, SiemensS7Master siemensS7Net) where T : IVariableSourceT<IVariable>, new() | ||||
|     private static List<T> LoadSourceRead<T>(Dictionary<SiemensAddress, IVariable> addressList, int functionCode, int intervalTime, SiemensS7Master siemensS7Net) where T : IVariableSource, new() | ||||
|     { | ||||
|         List<T> sourceReads = new(); | ||||
|         //实际地址与长度排序 | ||||
|   | ||||
| @@ -30,10 +30,10 @@ internal partial class SiemensHelper | ||||
| 		0x01, 0x0A, // PDU Max Length | ||||
| 		0xC1, // Src TSAP Identifier | ||||
| 		0x02, // Src TSAP Length (2 bytes) | ||||
| 		0x01, 0x00, // Src TSAP  (需重写) | ||||
| 		0x01, 0x02, // Src TSAP  (需重写) | ||||
| 		0xC2, // Dst TSAP Identifier | ||||
| 		0x02, // Dst TSAP Length (2 bytes) | ||||
| 		0x01, 0x02  // Dst TSAP  (需重写) | ||||
| 		0x01, 0x00  // Dst TSAP  (需重写) | ||||
| 	}; | ||||
|  | ||||
|     // ISO连接请求报文(也包含ISO头和COTP头) | ||||
|   | ||||
| @@ -7,8 +7,6 @@ | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="$(SolutionDir)\foundation\ThingsGateway.Foundation\src\ThingsGateway.Foundation.csproj"> | ||||
| 			<Private>True</Private> | ||||
| 			<CopyLocalSatelliteAssemblies>True</CopyLocalSatelliteAssemblies> | ||||
| 		</ProjectReference> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| @@ -0,0 +1,118 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||
| //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||
| //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||
| //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||
| //  Github源代码仓库:https://github.com/RRQM | ||||
| //  API首页:http://rrqm_home.gitee.io/touchsocket/ | ||||
| //  交流QQ群:234762506 | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| #if !NET45_OR_GREATER | ||||
|  | ||||
| using Microsoft.CodeAnalysis; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal static class Utils | ||||
|     { | ||||
|         public static bool IsInheritFrom(this ITypeSymbol typeSymbol, string baseType) | ||||
|         { | ||||
|             if (typeSymbol.ToDisplayString() == baseType) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if (typeSymbol.BaseType != null) | ||||
|             { | ||||
|                 var b = IsInheritFrom(typeSymbol.BaseType, baseType); | ||||
|                 if (b) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             foreach (var item in typeSymbol.AllInterfaces) | ||||
|             { | ||||
|                 var b = IsInheritFrom(item, baseType); | ||||
|                 if (b) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static string RenameCamelCase(this string str) | ||||
|         { | ||||
|             var firstChar = str[0]; | ||||
|  | ||||
|             if (firstChar == char.ToLowerInvariant(firstChar)) | ||||
|             { | ||||
|                 return str; | ||||
|             } | ||||
|  | ||||
|             var name = str.ToCharArray(); | ||||
|             name[0] = char.ToLowerInvariant(firstChar); | ||||
|  | ||||
|             return new string(name); | ||||
|         } | ||||
|  | ||||
|         public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute) | ||||
|         { | ||||
|             foreach (var attr in symbol.GetAttributes()) | ||||
|             { | ||||
|                 var attrClass = attr.AttributeClass; | ||||
|                 if (attrClass != null && attrClass.ToDisplayString() == attribute.ToDisplayString()) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static bool HasAttribute(this ISymbol symbol, string attribute, out AttributeData attributeData) | ||||
|         { | ||||
|             foreach (var attr in symbol.GetAttributes()) | ||||
|             { | ||||
|                 var attrClass = attr.AttributeClass; | ||||
|                 if (attrClass != null && attrClass.ToDisplayString() == attribute) | ||||
|                 { | ||||
|                     attributeData = attr; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             attributeData = default; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static bool HasFlags(int value, int flag) | ||||
|         { | ||||
|             return (value & flag) == flag; | ||||
|         } | ||||
|  | ||||
|         public static bool HasReturn(this IMethodSymbol method) | ||||
|         { | ||||
|             if (method.ReturnsVoid || method.ReturnType.ToDisplayString() == typeof(Task).FullName) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,135 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||
| //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||
| //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||
| //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||
| //  Github源代码仓库:https://github.com/RRQM | ||||
| //  API首页:http://rrqm_home.gitee.io/touchsocket/ | ||||
| //  交流QQ群:234762506 | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| #if !NET45_OR_GREATER | ||||
|  | ||||
| using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.Text; | ||||
|  | ||||
| using System.Reflection; | ||||
| using System.Text; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal sealed class VariableCodeBuilder | ||||
|     { | ||||
|         private readonly INamedTypeSymbol m_pluginClass; | ||||
|  | ||||
|         public VariableCodeBuilder(INamedTypeSymbol pluginClass) | ||||
|         { | ||||
|             this.m_pluginClass = pluginClass; | ||||
|         } | ||||
|  | ||||
|         public string Prefix { get; set; } | ||||
|  | ||||
|         public IEnumerable<string> Usings | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 yield return "using System;"; | ||||
|                 yield return "using System.Diagnostics;"; | ||||
|                 yield return "using ThingsGateway.Foundation;"; | ||||
|                 yield return "using System.Threading.Tasks;"; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public string GetFileName() | ||||
|         { | ||||
|             return this.m_pluginClass.ToDisplayString() + "Generator"; | ||||
|         } | ||||
|  | ||||
|         public bool TryToSourceText(out SourceText sourceText) | ||||
|         { | ||||
|             var code = this.ToString(); | ||||
|             if (string.IsNullOrEmpty(code)) | ||||
|             { | ||||
|                 sourceText = null; | ||||
|                 return false; | ||||
|             } | ||||
|             sourceText = SourceText.From(code, Encoding.UTF8); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         public override string ToString() | ||||
|         { | ||||
|             var propertys = this.FindPropertys().ToList(); | ||||
|             if (propertys.Count == 0) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|             var codeString = new StringBuilder(); | ||||
|             codeString.AppendLine("/*"); | ||||
|             codeString.AppendLine("此代码由Variable工具直接生成,非必要请不要修改此处代码"); | ||||
|             codeString.AppendLine("*/"); | ||||
|             codeString.AppendLine("#pragma warning disable"); | ||||
|  | ||||
|             foreach (var item in this.Usings) | ||||
|             { | ||||
|                 codeString.AppendLine(item); | ||||
|             } | ||||
|  | ||||
|             codeString.AppendLine($"namespace {this.m_pluginClass.ContainingNamespace}"); | ||||
|             codeString.AppendLine("{"); | ||||
|             codeString.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCode(\"ThingsGateway.Foundation\",\"{Assembly.GetExecutingAssembly().GetName().Version.ToString()}\")]"); | ||||
|             codeString.AppendLine($"partial class {this.m_pluginClass.Name}"); | ||||
|             codeString.AppendLine("{"); | ||||
|             foreach (var item in propertys) | ||||
|             { | ||||
|                 this.BuildMethod(codeString, item); | ||||
|             } | ||||
|             codeString.AppendLine("}"); | ||||
|             codeString.AppendLine("}"); | ||||
|  | ||||
|             return codeString.ToString(); | ||||
|         } | ||||
|  | ||||
|         private void BuildMethod(StringBuilder stringBuilder, IPropertySymbol propertySymbol) | ||||
|         { | ||||
|             var attributeData = propertySymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName); | ||||
|             stringBuilder.AppendLine(); | ||||
|             stringBuilder.AppendLine($"public OperResult Write{propertySymbol.Name}(object value,CancellationToken cancellationToken=default)"); | ||||
|             stringBuilder.AppendLine("{"); | ||||
|             stringBuilder.AppendLine($"return WriteValue(\"{propertySymbol.Name}\",value,cancellationToken);"); | ||||
|             stringBuilder.AppendLine("}"); | ||||
|             stringBuilder.AppendLine(); | ||||
|             stringBuilder.AppendLine(); | ||||
|             stringBuilder.AppendLine($"public Task<OperResult> Write{propertySymbol.Name}Async(object value,CancellationToken cancellationToken=default)"); | ||||
|             stringBuilder.AppendLine("{"); | ||||
|             stringBuilder.AppendLine($"return WriteValueAsync(\"{propertySymbol.Name}\",value,cancellationToken);"); | ||||
|             stringBuilder.AppendLine("}"); | ||||
|             stringBuilder.AppendLine(); | ||||
|         } | ||||
|  | ||||
|         private IEnumerable<IPropertySymbol> FindPropertys() | ||||
|         { | ||||
|             return this.m_pluginClass | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => | ||||
|                 { | ||||
|                     return m.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,89 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||
| //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||
| //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||
| //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||
| //  Github源代码仓库:https://github.com/RRQM | ||||
| //  API首页:http://rrqm_home.gitee.io/touchsocket/ | ||||
| //  交流QQ群:234762506 | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| #if !NET45_OR_GREATER | ||||
|  | ||||
| using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 源生成 | ||||
|     /// </summary> | ||||
|     [Generator] | ||||
|     public class VariableObjectSourceGenerator : ISourceGenerator | ||||
|     { | ||||
|         private string m_generatorVariableAttribute = @" | ||||
| using System; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 使用源生成变量写入方法的调用。 | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Class)] | ||||
|     internal class GeneratorVariableAttribute:Attribute | ||||
|     { | ||||
|     } | ||||
| } | ||||
|  | ||||
| "; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Initialize(GeneratorInitializationContext context) | ||||
|         { | ||||
|             //Debugger.Launch(); | ||||
|             context.RegisterForPostInitialization(a => | ||||
|             { | ||||
|                 a.AddSource(nameof(this.m_generatorVariableAttribute), this.m_generatorVariableAttribute); | ||||
|             }); | ||||
|             context.RegisterForSyntaxNotifications(() => new VariableSyntaxReceiver()); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Execute(GeneratorExecutionContext context) | ||||
|         { | ||||
|             var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly); | ||||
|  | ||||
|             if (context.SyntaxReceiver is VariableSyntaxReceiver receiver) | ||||
|             { | ||||
|                 var builders = receiver | ||||
|                     .GetVariableObjectTypes(context.Compilation) | ||||
|                     .Select(i => new VariableCodeBuilder(i)) | ||||
|                     .Distinct(); | ||||
|                 foreach (var builder in builders) | ||||
|                 { | ||||
|                     if (builder.TryToSourceText(out var sourceText)) | ||||
|                     { | ||||
|                         var tree = CSharpSyntaxTree.ParseText(sourceText); | ||||
|                         var root = tree.GetRoot().NormalizeWhitespace(); | ||||
|                         var ret = root.ToFullString(); | ||||
|                         context.AddSource($"{builder.GetFileName()}.g.cs", ret); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,117 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||
| //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||
| //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||
| //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||
| //  Github源代码仓库:https://github.com/RRQM | ||||
| //  API首页:http://rrqm_home.gitee.io/touchsocket/ | ||||
| //  交流QQ群:234762506 | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| #if !NET45_OR_GREATER | ||||
|  | ||||
| using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     internal sealed class VariableSyntaxReceiver : ISyntaxReceiver | ||||
|     { | ||||
|         public const string GeneratorVariableAttributeTypeName = "ThingsGateway.Foundation.GeneratorVariableAttribute"; | ||||
|         public const string VariableRuntimeAttributeTypeName = "ThingsGateway.Foundation.VariableRuntimeAttribute"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 接口列表 | ||||
|         /// </summary> | ||||
|         private readonly List<ClassDeclarationSyntax> m_classSyntaxList = new List<ClassDeclarationSyntax>(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 访问语法树 | ||||
|         /// </summary> | ||||
|         /// <param name="syntaxNode"></param> | ||||
|         void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) | ||||
|         { | ||||
|             if (syntaxNode is ClassDeclarationSyntax syntax) | ||||
|             { | ||||
|                 this.m_classSyntaxList.Add(syntax); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public static INamedTypeSymbol GeneratorVariableAttributeAttribute { get; private set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 获取所有插件符号 | ||||
|         /// </summary> | ||||
|         /// <param name="compilation"></param> | ||||
|         /// <returns></returns> | ||||
|         public IEnumerable<INamedTypeSymbol> GetVariableObjectTypes(Compilation compilation) | ||||
|         { | ||||
|             GeneratorVariableAttributeAttribute = compilation.GetTypeByMetadataName(GeneratorVariableAttributeTypeName); | ||||
|             if (GeneratorVariableAttributeAttribute == null) | ||||
|             { | ||||
|                 yield break; | ||||
|             } | ||||
|             foreach (var classSyntax in this.m_classSyntaxList) | ||||
|             { | ||||
|                 var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); | ||||
|                 if (@class != null && IsVariableObject(@class)) | ||||
|                 { | ||||
|                     yield return @class; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 是否为变量类 | ||||
|         /// </summary> | ||||
|         /// <param name="class"></param> | ||||
|         /// <returns></returns> | ||||
|         public static bool IsVariableObject(INamedTypeSymbol @class) | ||||
|         { | ||||
|             if (GeneratorVariableAttributeAttribute is null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (@class.IsAbstract) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|             return @class.BaseType.ToDisplayString() == "ThingsGateway.Foundation.VariableObject"; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 返回是否声明指定的特性 | ||||
|         /// </summary> | ||||
|         /// <param name="symbol"></param> | ||||
|         /// <param name="attribute"></param> | ||||
|         /// <returns></returns> | ||||
|         public static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attribute) | ||||
|         { | ||||
|             foreach (var attr in symbol.GetAttributes()) | ||||
|             { | ||||
|                 var attrClass = attr.AttributeClass; | ||||
|                 if (attrClass != null && attrClass.AllInterfaces.Contains(attribute)) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
| @@ -0,0 +1,17 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>netstandard2.0;</TargetFrameworks> | ||||
| 		<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> | ||||
| 		<NoPackageAnalysis>true</NoPackageAnalysis> | ||||
| 		<SignAssembly>false</SignAssembly> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <None Include="..\..\..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| @@ -0,0 +1,49 @@ | ||||
| param($installPath, $toolsPath, $package, $project) | ||||
|  | ||||
| $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve | ||||
|  | ||||
| foreach($analyzersPath in $analyzersPaths) | ||||
| { | ||||
|     # Install the language agnostic analyzers. | ||||
|     if (Test-Path $analyzersPath) | ||||
|     { | ||||
|         foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) | ||||
|         { | ||||
|             if($project.Object.AnalyzerReferences) | ||||
|             { | ||||
|                 $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| # $project.Type gives the language name like (C# or VB.NET) | ||||
| $languageFolder = "" | ||||
| if($project.Type -eq "C#") | ||||
| { | ||||
|     $languageFolder = "cs" | ||||
| } | ||||
| if($project.Type -eq "VB.NET") | ||||
| { | ||||
|     $languageFolder = "vb" | ||||
| } | ||||
| if($languageFolder -eq "") | ||||
| { | ||||
|     return | ||||
| } | ||||
|  | ||||
| foreach($analyzersPath in $analyzersPaths) | ||||
| { | ||||
|     # Install language specific analyzers. | ||||
|     $languageAnalyzersPath = join-path $analyzersPath $languageFolder | ||||
|     if (Test-Path $languageAnalyzersPath) | ||||
|     { | ||||
|         foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) | ||||
|         { | ||||
|             if($project.Object.AnalyzerReferences) | ||||
|             { | ||||
|                 $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,56 @@ | ||||
| param($installPath, $toolsPath, $package, $project) | ||||
|  | ||||
| $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve | ||||
|  | ||||
| foreach($analyzersPath in $analyzersPaths) | ||||
| { | ||||
|     # Uninstall the language agnostic analyzers. | ||||
|     if (Test-Path $analyzersPath) | ||||
|     { | ||||
|         foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) | ||||
|         { | ||||
|             if($project.Object.AnalyzerReferences) | ||||
|             { | ||||
|                 $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| # $project.Type gives the language name like (C# or VB.NET) | ||||
| $languageFolder = "" | ||||
| if($project.Type -eq "C#") | ||||
| { | ||||
|     $languageFolder = "cs" | ||||
| } | ||||
| if($project.Type -eq "VB.NET") | ||||
| { | ||||
|     $languageFolder = "vb" | ||||
| } | ||||
| if($languageFolder -eq "") | ||||
| { | ||||
|     return | ||||
| } | ||||
|  | ||||
| foreach($analyzersPath in $analyzersPaths) | ||||
| { | ||||
|     # Uninstall language specific analyzers. | ||||
|     $languageAnalyzersPath = join-path $analyzersPath $languageFolder | ||||
|     if (Test-Path $languageAnalyzersPath) | ||||
|     { | ||||
|         foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) | ||||
|         { | ||||
|             if($project.Object.AnalyzerReferences) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -496,10 +496,7 @@ namespace ThingsGateway.Foundation | ||||
|         /// <exception cref="Exception"></exception> | ||||
|         public void Send(IRequestInfo requestInfo) | ||||
|         { | ||||
|             if (this.DisposedValue) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             this.ThrowIfDisposed(); | ||||
|             if (this.DataHandlingAdapter == null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(this.DataHandlingAdapter), TouchSocketResource.NullDataAdapter.GetDescription()); | ||||
|   | ||||
| @@ -42,7 +42,7 @@ namespace ThingsGateway.Foundation | ||||
|         private DelaySender m_delaySender; | ||||
|         private volatile bool m_online; | ||||
|         private readonly EasyLock m_semaphoreForConnect = new(); | ||||
|         private readonly TcpCore m_tcpCore = new TcpCore(); | ||||
|         private readonly TcpCore m_tcpCore = new(); | ||||
|  | ||||
|         #endregion 变量 | ||||
|  | ||||
| @@ -215,6 +215,10 @@ namespace ThingsGateway.Foundation | ||||
|         /// <inheritdoc/> | ||||
|         public int Port { get; private set; } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         [Obsolete("该配置已被弃用,正式版发布时会直接删除", true)] | ||||
|         public ReceiveType ReceiveType { get; private set; } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public bool UseSsl => this.GetTcpCore().UseSsl; | ||||
|  | ||||
| @@ -227,10 +231,6 @@ namespace ThingsGateway.Foundation | ||||
|         /// <inheritdoc/> | ||||
|         public bool IsClient => true; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         [Obsolete("该配置已被弃用,正式版发布时会直接删除", true)] | ||||
|         public ReceiveType ReceiveType => default; | ||||
|  | ||||
|         #endregion 属性 | ||||
|  | ||||
|         #region 断开操作 | ||||
| @@ -450,7 +450,7 @@ namespace ThingsGateway.Foundation | ||||
|  | ||||
|         #endregion Connect | ||||
|  | ||||
|         #region TgReceiver | ||||
|         #region Receiver | ||||
|  | ||||
|         private TgReceiver m_receiver; | ||||
|  | ||||
| @@ -466,7 +466,7 @@ namespace ThingsGateway.Foundation | ||||
|             this.m_receiver = null; | ||||
|         } | ||||
|  | ||||
|         #endregion TgReceiver | ||||
|         #endregion Receiver | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string ToString() | ||||
| @@ -532,7 +532,6 @@ namespace ThingsGateway.Foundation | ||||
|         /// 当收到适配器处理的数据时。 | ||||
|         /// </summary> | ||||
|         /// <param name="e"></param> | ||||
|         /// <returns>如果返回<see langword="true"/>则表示数据已被处理,且不会再向下传递。</returns> | ||||
|         protected virtual Task ReceivedData(ReceivedDataEventArgs e) | ||||
|         { | ||||
|             return this.PluginManager.RaiseAsync(nameof(ITcpReceivedPlugin.OnTcpReceived), this, e); | ||||
| @@ -651,10 +650,7 @@ namespace ThingsGateway.Foundation | ||||
|         /// <exception cref="Exception"></exception> | ||||
|         public void Send(IRequestInfo requestInfo) | ||||
|         { | ||||
|             if (this.DisposedValue) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             this.ThrowIfDisposed(); | ||||
|             if (this.DataHandlingAdapter == null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(this.DataHandlingAdapter), TouchSocketResource.NullDataAdapter.GetDescription()); | ||||
|   | ||||
| @@ -69,7 +69,7 @@ namespace ThingsGateway.Foundation | ||||
|         /// <inheritdoc/> | ||||
|         protected override async Task OnConnected(ConnectedEventArgs e) | ||||
|         { | ||||
|             Logger?.Debug($"{ToString()} {FoundationConst.Connected}"); | ||||
|             Logger?.Debug($"{ToString()}  {FoundationConst.Connected}"); | ||||
|             if (Started != null) | ||||
|                 await Started.Invoke(this); | ||||
|             await base.OnConnected(e); | ||||
| @@ -78,7 +78,7 @@ namespace ThingsGateway.Foundation | ||||
|         /// <inheritdoc/> | ||||
|         protected override async Task OnConnecting(SerialConnectingEventArgs e) | ||||
|         { | ||||
|             Logger?.Debug($"{ToString()} {FoundationConst.Connecting}{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}"); | ||||
|             Logger?.Debug($"{ToString()}  {FoundationConst.Connecting}{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}"); | ||||
|             if (Starting != null) | ||||
|                 await Starting.Invoke(this); | ||||
|             await base.OnConnecting(e); | ||||
|   | ||||
| @@ -67,7 +67,7 @@ namespace ThingsGateway.Foundation | ||||
|         /// <inheritdoc/> | ||||
|         protected override async Task OnConnected(ConnectedEventArgs e) | ||||
|         { | ||||
|             Logger?.Debug($"{ToString()} {FoundationConst.Connected}"); | ||||
|             Logger?.Debug($"{ToString()}  {FoundationConst.Connected}"); | ||||
|             if (Started != null) | ||||
|                 await Started.Invoke(this); | ||||
|             await base.OnConnected(e); | ||||
|   | ||||
| @@ -29,14 +29,14 @@ namespace ThingsGateway.Foundation | ||||
|         /// <inheritdoc/> | ||||
|         protected override Task OnConnected(TClient socketClient, ConnectedEventArgs e) | ||||
|         { | ||||
|             Logger?.Debug($"{socketClient.ToString()} {FoundationConst.Connected}"); | ||||
|             Logger?.Debug($"{socketClient.ToString()}  {FoundationConst.Connected}"); | ||||
|             return base.OnConnected(socketClient, e); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         protected override Task OnConnecting(TClient socketClient, ConnectingEventArgs e) | ||||
|         { | ||||
|             Logger?.Debug($"{socketClient.ToString()} {FoundationConst.Connecting}"); | ||||
|             Logger?.Debug($"{socketClient.ToString()}  {FoundationConst.Connecting}"); | ||||
|             return base.OnConnecting(socketClient, e); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -408,16 +408,11 @@ namespace ThingsGateway.Foundation | ||||
|  | ||||
|             #region Windows下UDP连接被重置错误10054 | ||||
|  | ||||
| #if NET45_OR_GREATER | ||||
|             const int SIP_UDP_CONNRESET = -1744830452; | ||||
|             socket.IOControl(SIP_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null); | ||||
| #else | ||||
|             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||||
|             if (NewLife.Runtime.Windows) | ||||
|             { | ||||
|                 const int SIP_UDP_CONNRESET = -1744830452; | ||||
|                 socket.IOControl(SIP_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null); | ||||
|             } | ||||
| #endif | ||||
|  | ||||
|             #endregion Windows下UDP连接被重置错误10054 | ||||
|  | ||||
| @@ -689,7 +684,7 @@ namespace ThingsGateway.Foundation | ||||
|  | ||||
|                 var newByteBlock = new ByteBlock(1024 * 64); | ||||
|                 e.UserToken = newByteBlock; | ||||
|                 e.SetBuffer(newByteBlock.Buffer, 0, newByteBlock.Buffer.Length); | ||||
|                 e.SetBuffer(newByteBlock.Buffer, 0, newByteBlock.Capacity); | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|   | ||||
| @@ -39,6 +39,30 @@ public static class DataTypeExtension | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取DataTypeEnum | ||||
|     /// </summary> | ||||
|     /// <param name="coreType"></param> | ||||
|     /// <returns></returns> | ||||
|     public static DataTypeEnum GetDataType(this TypeCode coreType) | ||||
|     { | ||||
|         return coreType switch | ||||
|         { | ||||
|             TypeCode.String => DataTypeEnum.String, | ||||
|             TypeCode.Boolean => DataTypeEnum.Boolean, | ||||
|             TypeCode.Byte => DataTypeEnum.Byte, | ||||
|             TypeCode.Int16 => DataTypeEnum.Int16, | ||||
|             TypeCode.UInt16 => DataTypeEnum.UInt16, | ||||
|             TypeCode.Int32 => DataTypeEnum.Int32, | ||||
|             TypeCode.UInt32 => DataTypeEnum.UInt32, | ||||
|             TypeCode.Int64 => DataTypeEnum.Int64, | ||||
|             TypeCode.UInt64 => DataTypeEnum.UInt64, | ||||
|             TypeCode.Single => DataTypeEnum.Single, | ||||
|             TypeCode.Double => DataTypeEnum.Double, | ||||
|             _ => DataTypeEnum.Object, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取实际字节长度,不能确定返回0,bool返回1 | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -108,7 +108,7 @@ public interface IProtocol : IDisposable | ||||
|     /// <summary> | ||||
|     /// 连读寄存器打包 | ||||
|     /// </summary> | ||||
|     List<T> LoadSourceRead<T>(IEnumerable<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSourceT<IVariable>, new(); | ||||
|     List<T> LoadSourceRead<T>(IEnumerable<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new(); | ||||
|  | ||||
|     #endregion 变量地址解析 | ||||
|  | ||||
|   | ||||
| @@ -11,12 +11,18 @@ | ||||
| namespace ThingsGateway.Foundation; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// IVariableSource | ||||
| /// 变量特性 | ||||
| /// </summary> | ||||
| public interface IVariableSourceT<T> : IVariableSource where T : IVariable | ||||
| [AttributeUsage(AttributeTargets.Property)] | ||||
| public class VariableRuntimeAttribute : Attribute | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 已打包变量 | ||||
|     /// 寄存器地址 | ||||
|     /// </summary> | ||||
|     ICollection<T> VariableRunTimes { get; } | ||||
|     public string? RegisterAddress { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 数据类型,默认不填时会使用属性的Type | ||||
|     /// </summary> | ||||
|     public DataTypeEnum DataType { get; set; } | ||||
| } | ||||
| @@ -45,16 +45,6 @@ public interface IVariable | ||||
|     /// </summary> | ||||
|     IVariableSource VariableSource { get; set; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 是否在线 | ||||
|     /// </summary> | ||||
|     bool IsOnline { get; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 离线原因 | ||||
|     /// </summary> | ||||
|     string? LastErrorMessage { get; } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 实时值 | ||||
|     /// </summary> | ||||
| @@ -68,13 +58,4 @@ public interface IVariable | ||||
|     /// <param name="isOnline">是否在线</param> | ||||
|     /// <returns></returns> | ||||
|     OperResult SetValue(object value, DateTime dateTime = default, bool isOnline = true); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 写入值/方法到设备中 | ||||
|     /// </summary> | ||||
|     /// <param name="value">写入值/方法参数</param> | ||||
|     /// <param name="executive">执行方</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     /// <returns></returns> | ||||
|     Task<OperResult> SetValueToDeviceAsync(string value, string? executive = null, CancellationToken cancellationToken = default); | ||||
| } | ||||
| @@ -21,7 +21,7 @@ namespace ThingsGateway.Foundation | ||||
|         string? LastErrorMessage { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 最后一次失败原因 | ||||
|         /// 添加变量 | ||||
|         /// </summary> | ||||
|         void AddVariable(IVariable variable); | ||||
| 
 | ||||
| @@ -0,0 +1,67 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| /// VariableClass | ||||
| /// </summary> | ||||
| public class VariableClass : IVariable | ||||
| { | ||||
|     private object? _value; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 数据类型 | ||||
|     /// </summary> | ||||
|     public virtual DataTypeEnum DataType { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 偏移量,注意如果是布尔类型,Index应该为bit的偏移 | ||||
|     /// </summary> | ||||
|     public virtual int Index { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 执行间隔 | ||||
|     /// </summary> | ||||
|     public virtual int? IntervalTime { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 寄存器地址 | ||||
|     /// </summary> | ||||
|     public virtual string? RegisterAddress { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 数据转换规则 | ||||
|     /// </summary> | ||||
|     public virtual IThingsGatewayBitConverter ThingsGatewayBitConverter { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 实时值 | ||||
|     /// </summary> | ||||
|     public virtual object? Value => _value; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// IVariableSource | ||||
|     /// </summary> | ||||
|     public IVariableSource VariableSource { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 赋值变量 | ||||
|     /// </summary> | ||||
|     /// <param name="value">值</param> | ||||
|     /// <param name="dateTime">采集时间</param> | ||||
|     /// <param name="isOnline">是否在线</param> | ||||
|     /// <returns></returns> | ||||
|     public virtual OperResult SetValue(object value, DateTime dateTime = default, bool isOnline = true) | ||||
|     { | ||||
|         _value = value; | ||||
|         return new(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,197 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.Generic; | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| /// VariableObject | ||||
| /// </summary> | ||||
| public abstract class VariableObject | ||||
| { | ||||
|     private int maxPack; | ||||
|  | ||||
|     private List<VariableSourceClass>? deviceVariableSourceReads; | ||||
|  | ||||
|     private Dictionary<string, VariableRuntimeProperty>? dict; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// VariableObject | ||||
|     /// </summary> | ||||
|     public VariableObject(IProtocol protocol, int maxPack) | ||||
|     { | ||||
|         this.Protocol = protocol; | ||||
|         this.maxPack = maxPack; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 协议对象 | ||||
|     /// </summary> | ||||
|     public IProtocol Protocol { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性 | ||||
|     /// </summary> | ||||
|     public async Task<OperResult> MulReadAsync(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GetVariableSources(); | ||||
|             //连读 | ||||
|             foreach (var item in deviceVariableSourceReads) | ||||
|             { | ||||
|                 var result = await Protocol.ReadAsync(item.RegisterAddress, item.Length); | ||||
|                 if (result.IsSuccess) | ||||
|                 { | ||||
|                     item.VariableRunTimes.PraseStructContent(Protocol, result.Content, item, exWhenAny: true); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     item.LastErrorMessage = result.ErrorMessage; | ||||
|                     item.VariableRunTimes.ForEach(a => a.SetValue(null, isOnline: false)); | ||||
|                     return new(result); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             //结果反射赋值 | ||||
|             var dict1 = VariableObjectHelper.GetPairs(GetType()); | ||||
|             foreach (var pair in dict1) | ||||
|             { | ||||
|                 pair.Value.Property.SetValue(this, pair.Value.VariableClass.Value); | ||||
|             } | ||||
|             return new(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性 | ||||
|     /// </summary> | ||||
|     public OperResult MulRead(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GetVariableSources(); | ||||
|             //连读 | ||||
|             foreach (var item in deviceVariableSourceReads) | ||||
|             { | ||||
|                 var result = Protocol.Read(item.RegisterAddress, item.Length); | ||||
|                 if (result.IsSuccess) | ||||
|                 { | ||||
|                     item.VariableRunTimes.PraseStructContent(Protocol, result.Content, item, exWhenAny: true); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     item.LastErrorMessage = result.ErrorMessage; | ||||
|                     item.VariableRunTimes.ForEach(a => a.SetValue(null, isOnline: false)); | ||||
|                     return new(result); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             //结果反射赋值 | ||||
|             var dict1 = VariableObjectHelper.GetPairs(GetType()); | ||||
|             foreach (var pair in dict1) | ||||
|             { | ||||
|                 pair.Value.Property.SetValue(this, pair.Value.VariableClass.Value); | ||||
|             } | ||||
|             return new(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 写入值到设备中 | ||||
|     /// </summary> | ||||
|     /// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param> | ||||
|     /// <param name="value">写入值</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     public OperResult WriteValue(string propertyName, object value, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GetVariableSources(); | ||||
|             if (string.IsNullOrEmpty(propertyName)) | ||||
|             { | ||||
|                 return new($"propertyName不能为 null 或空。"); | ||||
|             } | ||||
|  | ||||
|             if (!dict.TryGetValue(propertyName, out var variableRuntimeProperty)) | ||||
|             { | ||||
|                 return new($"该属性未被识别,可能没有使用{typeof(VariableRuntimeAttribute)}特性标识"); | ||||
|             } | ||||
|             var result = Protocol.Write(variableRuntimeProperty.VariableClass.RegisterAddress, JToken.FromObject(value), variableRuntimeProperty.VariableClass.DataType, cancellationToken); | ||||
|             return result; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 写入值到设备中 | ||||
|     /// </summary> | ||||
|     /// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param> | ||||
|     /// <param name="value">写入值</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     public async Task<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             GetVariableSources(); | ||||
|             if (string.IsNullOrEmpty(propertyName)) | ||||
|             { | ||||
|                 return new($"propertyName不能为 null 或空。"); | ||||
|             } | ||||
|  | ||||
|             if (!dict.TryGetValue(propertyName, out var variableRuntimeProperty)) | ||||
|             { | ||||
|                 return new($"该属性未被识别,可能没有使用{typeof(VariableRuntimeAttribute)}特性标识"); | ||||
|             } | ||||
|             var result = await Protocol.WriteAsync(variableRuntimeProperty.VariableClass.RegisterAddress, JToken.FromObject(value), variableRuntimeProperty.VariableClass.DataType, cancellationToken); | ||||
|             return result; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void GetVariableSources() | ||||
|     { | ||||
|         if (deviceVariableSourceReads == null) | ||||
|         { | ||||
|             dict = VariableObjectHelper.GetPairs(GetType()); | ||||
|             List<VariableClass> variableClasss = new(); | ||||
|             foreach (var pair in dict) | ||||
|             { | ||||
|                 var dataType = pair.Value.Attribute.DataType == DataTypeEnum.Object ? Type.GetTypeCode(pair.Value.Property.PropertyType.IsArray ? pair.Value.Property.PropertyType.GetElementType() : pair.Value.Property.PropertyType).GetDataType() : pair.Value.Attribute.DataType; | ||||
|                 VariableClass variableClass = new VariableClass() | ||||
|                 { | ||||
|                     DataType = dataType, | ||||
|                     RegisterAddress = pair.Value.Attribute.RegisterAddress, | ||||
|                     IntervalTime = 1000, | ||||
|                 }; | ||||
|                 pair.Value.VariableClass = variableClass; | ||||
|                 variableClasss.Add(variableClass); | ||||
|             } | ||||
|             deviceVariableSourceReads = Protocol.LoadSourceRead<VariableSourceClass>(variableClasss, maxPack, 1000); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,41 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| internal static class VariableObjectHelper | ||||
| { | ||||
|     private static MemoryCache<Type, Dictionary<string, VariableRuntimeProperty>> variablePropertyDicts = new MemoryCache<Type, Dictionary<string, VariableRuntimeProperty>>(); | ||||
|  | ||||
|     public static Dictionary<string, VariableRuntimeProperty> GetPairs(Type type) | ||||
|     { | ||||
|         if (variablePropertyDicts.TryGetValue(type, out var value)) | ||||
|         { | ||||
|             return value; | ||||
|         } | ||||
|  | ||||
|         var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); | ||||
|         var dictionary = new Dictionary<string, VariableRuntimeProperty>(); | ||||
|         foreach (var propertyInfo in properties) | ||||
|         { | ||||
|             VariableRuntimeAttribute variableRuntimeAttribute = propertyInfo.GetCustomAttribute<VariableRuntimeAttribute>(); | ||||
|             if (variableRuntimeAttribute == null) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|             dictionary.Add(propertyInfo.Name, new VariableRuntimeProperty(variableRuntimeAttribute, propertyInfo)); | ||||
|         } | ||||
|  | ||||
|         variablePropertyDicts.AddCache(type, dictionary, 60 * 60 * 1000); | ||||
|         return dictionary; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| /// VariableRuntimeProperty | ||||
| /// </summary> | ||||
| internal class VariableRuntimeProperty | ||||
| { | ||||
|     public VariableRuntimeAttribute Attribute { get; } | ||||
|  | ||||
|     public PropertyInfo Property { get; } | ||||
|     public VariableClass VariableClass { get; set; } | ||||
|  | ||||
|     public VariableRuntimeProperty(VariableRuntimeAttribute attribute, PropertyInfo property) | ||||
|     { | ||||
|         Attribute = attribute; | ||||
|         Property = property; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation | ||||
| { | ||||
|     /// <summary> | ||||
|     /// VariableSourceClass | ||||
|     /// </summary> | ||||
|     public class VariableSourceClass : IVariableSource | ||||
|     { | ||||
|         private List<IVariable> _variableRunTimes = new(); | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public string? LastErrorMessage { get; set; } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length { get; set; } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public string RegisterAddress { get; set; } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public TimeTick TimeTick { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 已打包变量 | ||||
|         /// </summary> | ||||
|         public IEnumerable<IVariable> VariableRunTimes => _variableRunTimes; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public virtual void AddVariable(IVariable variable) | ||||
|         { | ||||
|             variable.VariableSource = this; | ||||
|             _variableRunTimes.Add(variable); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -155,7 +155,7 @@ public abstract class ProtocolBase : DisposableObject, IProtocol | ||||
|     #region 变量地址解析 | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public abstract List<T> LoadSourceRead<T>(IEnumerable<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSourceT<IVariable>, new(); | ||||
|     public abstract List<T> LoadSourceRead<T>(IEnumerable<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new(); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual string GetAddressDescription() | ||||
| @@ -462,6 +462,12 @@ public abstract class ProtocolBase : DisposableObject, IProtocol | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 string address1 = address; | ||||
|                 IThingsGatewayBitConverter transformParameter = ByteTransUtil.GetTransByAddress(ref address1, ThingsGatewayBitConverter); | ||||
|                 if (transformParameter.ArrayLength > 1) | ||||
|                 { | ||||
|                     return new("需要写入数组"); | ||||
|                 } | ||||
|                 return dataType switch | ||||
|                 { | ||||
|                     DataTypeEnum.String => await WriteAsync(address, value.ToObject<String>(), cancellationToken), | ||||
| @@ -510,6 +516,12 @@ public abstract class ProtocolBase : DisposableObject, IProtocol | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 string address1 = address; | ||||
|                 IThingsGatewayBitConverter transformParameter = ByteTransUtil.GetTransByAddress(ref address1, ThingsGatewayBitConverter); | ||||
|                 if (transformParameter.ArrayLength > 1) | ||||
|                 { | ||||
|                     return new("需要写入数组"); | ||||
|                 } | ||||
|                 return dataType switch | ||||
|                 { | ||||
|                     DataTypeEnum.String => Write(address, value.ToObject<String>(), cancellationToken), | ||||
|   | ||||
| @@ -6,23 +6,26 @@ | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <None Include="..\..\..\.editorconfig" Link=".editorconfig" /> | ||||
| 		<None Include="..\..\..\.editorconfig" Link=".editorconfig" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 	  <PackageReference Include="NewLife.Core" Version="10.7.2024.202" /> | ||||
| 	  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
| 	  <PackageReference Include="TouchSocket" Version="2.0.0-beta.275" /> | ||||
| 		<PackageReference Include="NewLife.Core" Version="10.7.2024.202" /> | ||||
| 		<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
| 		<PackageReference Include="TouchSocket" Version="2.0.0-beta.278" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
|  | ||||
| 	<ItemGroup Condition="'$(TargetFramework)'!='net45'"> | ||||
| 		<PackageReference Include="System.IO.Ports" Version="8.0.0" /> | ||||
| 	</ItemGroup> | ||||
| 	 | ||||
|  | ||||
| 	<ItemGroup Condition="'$(TargetFramework)'!='net6'"> | ||||
| 		<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<None Include="..\..\ThingsGateway.Foundation.SourceGenerator\src\tools\*.ps1" PackagePath="tools" Pack="true" Visible="false" /> | ||||
| 		<None Include="..\..\ThingsGateway.Foundation.SourceGenerator\src\bin\$(Configuration)\netstandard2.0\ThingsGateway.Foundation.SourceGenerator.dll" PackagePath="analyzers\dotnet\cs" Pack="true" Visible="false" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
|   | ||||
| @@ -36,77 +36,91 @@ public static class ByteTransExtension | ||||
|             switch (dataType) | ||||
|             { | ||||
|                 case DataTypeEnum.String: | ||||
|                     Set(organizedVariable, byteConverter.ToString(buffer, index, byteConverter.StringLength ?? 1)); | ||||
|                     if (byteConverter.ArrayLength > 1) | ||||
|                     { | ||||
|                         List<String> strings = new(); | ||||
|                         for (int i = 0; i < byteConverter.ArrayLength; i++) | ||||
|                         { | ||||
|                             var data = byteConverter.ToString(buffer, index + i * byteConverter.StringLength ?? 1, byteConverter.StringLength ?? 1); | ||||
|                             strings.Add(data); | ||||
|                         } | ||||
|                         Set(organizedVariable, strings.ToArray()); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         Set(organizedVariable, | ||||
|  byteConverter.ToString(buffer, index, byteConverter.StringLength ?? 1)); | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Boolean: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToBoolean(buffer, index, byteConverter.StringLength.Value, plc.BitReverse(organizedVariable.RegisterAddress)) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToBoolean(buffer, index, byteConverter.ArrayLength.Value, plc.BitReverse(organizedVariable.RegisterAddress)) : | ||||
|                     byteConverter.ToBoolean(buffer, index, plc.BitReverse(organizedVariable.RegisterAddress)) | ||||
|                     ); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Byte: | ||||
|                     Set(organizedVariable, | ||||
|                     byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToByte(buffer, index, byteConverter.StringLength.Value) : | ||||
|                     byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToByte(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToByte(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Int16: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToInt16(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToInt16(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToInt16(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.UInt16: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToUInt16(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToUInt16(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToUInt16(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Int32: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToInt32(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToInt32(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToInt32(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.UInt32: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToUInt32(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToUInt32(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToUInt32(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Int64: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToInt64(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToInt64(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToInt64(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.UInt64: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToUInt64(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToUInt64(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToUInt64(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Single: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToSingle(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToSingle(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToSingle(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|                 case DataTypeEnum.Double: | ||||
|                     Set(organizedVariable, | ||||
|                      byteConverter.StringLength > 1 ? | ||||
|                     byteConverter.ToDouble(buffer, index, byteConverter.StringLength.Value) : | ||||
|                      byteConverter.ArrayLength > 1 ? | ||||
|                     byteConverter.ToDouble(buffer, index, byteConverter.ArrayLength.Value) : | ||||
|                     byteConverter.ToDouble(buffer, index)); | ||||
|                     break; | ||||
|  | ||||
|   | ||||
| @@ -22,10 +22,6 @@ namespace ThingsGateway.Foundation; | ||||
| /// </summary> | ||||
| public class ThingsGatewayBitConverter : IThingsGatewayBitConverter | ||||
| { | ||||
|     private readonly EndianType endianType; | ||||
|  | ||||
|     private DataFormatEnum? dataFormat; | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
| #if NET6_0_OR_GREATER | ||||
|  | ||||
| @@ -61,7 +57,7 @@ public class ThingsGatewayBitConverter : IThingsGatewayBitConverter | ||||
|     /// <param name="endianType"></param> | ||||
|     public ThingsGatewayBitConverter(EndianType endianType) | ||||
|     { | ||||
|         this.endianType = endianType; | ||||
|         this.EndianType = endianType; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -69,24 +65,14 @@ public class ThingsGatewayBitConverter : IThingsGatewayBitConverter | ||||
|     /// </summary> | ||||
|     public ThingsGatewayBitConverter(EndianType endianType, DataFormatEnum dataFormat) | ||||
|     { | ||||
|         this.endianType = endianType; this.dataFormat = dataFormat; | ||||
|         this.EndianType = endianType; this.DataFormat = dataFormat; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual DataFormatEnum? DataFormat | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             return dataFormat; | ||||
|         } | ||||
|         set | ||||
|         { | ||||
|             dataFormat = value; | ||||
|         } | ||||
|     } | ||||
|     public virtual DataFormatEnum? DataFormat { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual EndianType EndianType => endianType; | ||||
|     public virtual EndianType EndianType { get; init; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual bool IsStringReverseByteWord { get; set; } | ||||
| @@ -298,7 +284,7 @@ public class ThingsGatewayBitConverter : IThingsGatewayBitConverter | ||||
|     /// <inheritdoc/> | ||||
|     public virtual bool IsSameOfSet() | ||||
|     { | ||||
|         return !(BitConverter.IsLittleEndian ^ (endianType == EndianType.Little)); | ||||
|         return !(BitConverter.IsLittleEndian ^ (EndianType == EndianType.Little)); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/nuget.exe
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/nuget.exe
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -19,6 +19,8 @@ using ThingsGateway.Foundation.Extension.String; | ||||
| using ThingsGateway.Foundation.Modbus; | ||||
| using ThingsGateway.Gateway.Application; | ||||
|  | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.Modbus; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -96,6 +98,8 @@ public class ModbusSlave : BusinessBase | ||||
|         _plc.WriteData += OnWriteData; | ||||
|     } | ||||
|  | ||||
|     private volatile bool success = true; | ||||
|  | ||||
|     protected override async Task ProtectedExecuteAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         //获取设备连接状态 | ||||
| @@ -107,6 +111,18 @@ public class ModbusSlave : BusinessBase | ||||
|         else | ||||
|         { | ||||
|             CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 999); | ||||
|             try | ||||
|             { | ||||
|                 Protocol.Channel.Close(); | ||||
|                 await Protocol.Channel.ConnectAsync(3000, cancellationToken); | ||||
|                 success = true; | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 if (success) | ||||
|                     LogMessage.LogWarning(ex, "无法启动服务"); | ||||
|                 success = false; | ||||
|             } | ||||
|         } | ||||
|         var list = _modbusVariableDict.ToListWithDequeue(); | ||||
|         foreach (var item in list) | ||||
|   | ||||
| @@ -112,6 +112,28 @@ public partial class OpcUaServer : BusinessBase | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (IsConnected()) | ||||
|             { | ||||
|                 //更新设备活动时间 | ||||
|                 CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 0); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 CurrentDevice.SetDeviceStatus(DateTimeUtil.Now, 999); | ||||
|                 try | ||||
|                 { | ||||
|                     await m_application.CheckApplicationInstanceCertificate(false, 0, 1200); | ||||
|                     await m_application.Start(m_server); | ||||
|                     success = true; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     if (success) | ||||
|                         LogMessage.LogWarning(ex, "无法启动服务"); | ||||
|                     success = false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             ////变化推送 | ||||
|             var varList = CollectVariableRunTimes.ToListWithDequeue(); | ||||
|             if (varList?.Count != 0) | ||||
|   | ||||
| @@ -14,8 +14,8 @@ using ThingsGateway.Core; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlDB; | ||||
|  | ||||
| [SplitTable(SplitType.Month)]//按月分表 (自带分表支持 年、季、月、周、日) | ||||
| [SugarTable("historyValue_{year}{month}{day}", TableDescription = "设备采集历史表")]//3个变量必须要有 | ||||
| [SplitTable(SplitType.Week)]//按周分表 (自带分表支持 年、季、月、周、日) | ||||
| [SugarTable("{name}_{year}{month}{day}", TableDescription = "设备采集历史表")]//3个变量必须要有 | ||||
| [SugarIndex("index_Name", nameof(SQLHistoryValue.Name), OrderByType.Desc)] | ||||
| [SugarIndex("index_DeviceName", nameof(SQLHistoryValue.DeviceName), OrderByType.Desc)] | ||||
| [SugarIndex("index_CollectTime", nameof(SQLHistoryValue.CollectTime), OrderByType.Desc)] | ||||
|   | ||||
| @@ -0,0 +1,55 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using Mapster; | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Reflection; | ||||
|  | ||||
| using ThingsGateway.Admin.Core; | ||||
| using ThingsGateway.Plugin.SqlDB; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| /// 上传数据库插件静态方法 | ||||
| /// </summary> | ||||
| public static class SqlDBBusinessDatabaseUtil | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 获取数据库链接 | ||||
|     /// </summary> | ||||
|     /// <returns></returns> | ||||
|     public static SqlSugarClient GetDb(SqlDBProducerProperty sqlDBProducerProperty) | ||||
|     { | ||||
|         var configureExternalServices = new ConfigureExternalServices | ||||
|         { | ||||
|             SplitTableService = new SqlDBDateSplitTableService(sqlDBProducerProperty), | ||||
|             EntityService = (type, column) => // 修改列可空-1、带?问号 2、String类型若没有Required | ||||
|             { | ||||
|                 if ((type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) | ||||
|                     || (type.PropertyType == typeof(string) && type.GetCustomAttribute<RequiredAttribute>() == null)) | ||||
|                     column.IsNullable = true; | ||||
|             }, | ||||
|         }; | ||||
|         var sqlSugarClient = new SqlSugarClient(new ConnectionConfig() | ||||
|         { | ||||
|             ConnectionString = sqlDBProducerProperty.BigTextConnectStr,//连接字符串 | ||||
|             DbType = sqlDBProducerProperty.DbType,//数据库类型 | ||||
|             IsAutoCloseConnection = true, //不设成true要手动close | ||||
|             ConfigureExternalServices = configureExternalServices, | ||||
|         } | ||||
|         ); | ||||
|         DbContext.AopSetting(sqlSugarClient);//aop配置 | ||||
|         return sqlSugarClient; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,136 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| //  使用文档:https://diego2098.gitee.io/thingsgateway-docs/ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using SqlSugar; | ||||
|  | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.SqlDB; | ||||
|  | ||||
| public class SqlDBDateSplitTableService : DateSplitTableService | ||||
| { | ||||
|     private SqlDBProducerProperty sqlDBProducerProperty; | ||||
|  | ||||
|     public SqlDBDateSplitTableService(SqlDBProducerProperty sqlDBProducerProperty) | ||||
|     { | ||||
|         this.sqlDBProducerProperty = sqlDBProducerProperty; | ||||
|     } | ||||
|  | ||||
|     #region Core | ||||
|  | ||||
|     public override string GetTableName(ISqlSugarClient db, EntityInfo EntityInfo) | ||||
|     { | ||||
|         var splitTableAttribute = EntityInfo.Type.GetCustomAttribute<SplitTableAttribute>(); | ||||
|         if (splitTableAttribute != null) | ||||
|         { | ||||
|             var type = (splitTableAttribute as SplitTableAttribute).SplitType; | ||||
|             return GetTableName(db, EntityInfo, type); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return GetTableName(db, EntityInfo, SplitType.Day); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override string GetTableName(ISqlSugarClient db, EntityInfo EntityInfo, SplitType splitType) | ||||
|     { | ||||
|         var date = db.GetDate(); | ||||
|         return GetTableNameByDate(EntityInfo, splitType, date); | ||||
|     } | ||||
|  | ||||
|     public override string GetTableName(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object fieldValue) | ||||
|     { | ||||
|         var value = Convert.ToDateTime(fieldValue); | ||||
|         return GetTableNameByDate(entityInfo, splitType, value); | ||||
|     } | ||||
|  | ||||
|     #endregion Core | ||||
|  | ||||
|     #region Common Helper | ||||
|  | ||||
|     private string GetTableNameByDate(EntityInfo EntityInfo, SplitType splitType, DateTime date) | ||||
|     { | ||||
|         date = ConvertDateBySplitType(date, splitType); | ||||
|         return EntityInfo.DbTableName.Replace("{year}", date.Year + "").Replace("{day}", PadLeft2(date.Day + "")).Replace("{month}", PadLeft2(date.Month + "")).Replace("{name}", sqlDBProducerProperty.HisDBTableName); | ||||
|     } | ||||
|  | ||||
|     private string PadLeft2(string str) | ||||
|     { | ||||
|         if (str.Length < 2) | ||||
|         { | ||||
|             return str.PadLeft(2, '0'); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return str; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion Common Helper | ||||
|  | ||||
|     #region Date Helper | ||||
|  | ||||
|     private DateTime ConvertDateBySplitType(DateTime time, SplitType type) | ||||
|     { | ||||
|         switch (type) | ||||
|         { | ||||
|             case SplitType.Day: | ||||
|                 return Convert.ToDateTime(time.ToString("yyyy-MM-dd")); | ||||
|  | ||||
|             case SplitType.Week: | ||||
|                 return GetMondayDate(time); | ||||
|  | ||||
|             case SplitType.Month: | ||||
|                 return Convert.ToDateTime(time.ToString("yyyy-MM-01")); | ||||
|  | ||||
|             case SplitType.Season: | ||||
|                 if (time.Month <= 3) | ||||
|                 { | ||||
|                     return Convert.ToDateTime(time.ToString("yyyy-01-01")); | ||||
|                 } | ||||
|                 else if (time.Month <= 6) | ||||
|                 { | ||||
|                     return Convert.ToDateTime(time.ToString("yyyy-04-01")); | ||||
|                 } | ||||
|                 else if (time.Month <= 9) | ||||
|                 { | ||||
|                     return Convert.ToDateTime(time.ToString("yyyy-07-01")); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return Convert.ToDateTime(time.ToString("yyyy-10-01")); | ||||
|                 } | ||||
|             case SplitType.Year: | ||||
|                 return Convert.ToDateTime(time.ToString("yyyy-01-01")); | ||||
|  | ||||
|             case SplitType.Month_6: | ||||
|                 if (time.Month <= 6) | ||||
|                 { | ||||
|                     return Convert.ToDateTime(time.ToString("yyyy-01-01")); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return Convert.ToDateTime(time.ToString("yyyy-07-01")); | ||||
|                 } | ||||
|             default: | ||||
|                 throw new Exception($"SplitType parameter error "); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private DateTime GetMondayDate(DateTime someDate) | ||||
|     { | ||||
|         int i = someDate.DayOfWeek - DayOfWeek.Monday; | ||||
|         if (i == -1) i = 6; | ||||
|         TimeSpan ts = new TimeSpan(i, 0, 0, 0); | ||||
|         return someDate.Subtract(ts); | ||||
|     } | ||||
|  | ||||
|     #endregion Date Helper | ||||
| } | ||||
| @@ -30,7 +30,7 @@ public partial class SqlDBPage : IDriverUIBase | ||||
|  | ||||
|     private async Task<SqlSugarPagedList<SQLHistoryValue>> QueryCallAsync(SqlDBPageInput input) | ||||
|     { | ||||
|         using var db = BusinessDatabaseUtil.GetDb(SqlDBProducer._driverPropertys.DbType, SqlDBProducer._driverPropertys.BigTextConnectStr); | ||||
|         using var db = SqlDBBusinessDatabaseUtil.GetDb(SqlDBProducer._driverPropertys); | ||||
|         var query = db.Queryable<SQLHistoryValue>().SplitTable() | ||||
|                              .WhereIF(input.StartTime != null, a => a.CreateTime >= input.StartTime) | ||||
|                            .WhereIF(input.EndTime != null, a => a.CreateTime <= input.EndTime) | ||||
| @@ -49,7 +49,7 @@ public partial class SqlDBPage : IDriverUIBase | ||||
|  | ||||
|     private async Task<SqlSugarPagedList<SQLRealValue>> RealQueryCallAsync(SqlDBPageInput input) | ||||
|     { | ||||
|         using var db = BusinessDatabaseUtil.GetDb(SqlDBProducer._driverPropertys.DbType, SqlDBProducer._driverPropertys.BigTextConnectStr); | ||||
|         using var db = SqlDBBusinessDatabaseUtil.GetDb(SqlDBProducer._driverPropertys); | ||||
|         var query = db.Queryable<SQLRealValue>().AS(SqlDBProducer._driverPropertys.ReadDBTableName) | ||||
|                            .WhereIF(!string.IsNullOrEmpty(input.VariableName), it => it.Name.Contains(input.VariableName)) | ||||
|                            ; | ||||
|   | ||||
| @@ -59,7 +59,7 @@ YitIdHelper.NextId()) | ||||
|  | ||||
|     protected override async Task ProtectedBeforStartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         var db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr); | ||||
|         var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys); | ||||
|         db.CodeFirst.InitTables(typeof(SQLHistoryValue)); | ||||
|         db.MappingTables.Add(nameof(SQLRealValue), _driverPropertys.ReadDBTableName); | ||||
|         db.CodeFirst.InitTables(typeof(SQLRealValue)); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ public class SqlDBProducerProperty : BusinessPropertyWithCacheInterval | ||||
|     [DynamicProperty("是否实时表", "true=>实时表更新")] public bool IsReadDB { get; set; } = false; | ||||
|     [DynamicProperty("是否历史表", "true=>历史存储(按月分表)")] public bool IsHisDB { get; set; } = true; | ||||
|     [DynamicProperty("实时表名称", "")] public string ReadDBTableName { get; set; } = "ReadDBTableName"; | ||||
|     [DynamicProperty("历史表名称", "")] public string HisDBTableName { get; set; } = "HisDBTableName"; | ||||
|  | ||||
|     [DynamicProperty("数据库类型", "MySql/SqlServer")] public DbType DbType { get; set; } = DbType.SqlServer; | ||||
|     [DynamicProperty("链接字符串", "")] public string BigTextConnectStr { get; set; } = "server=.;uid=sa;pwd=111111;database=test;"; | ||||
|   | ||||
| @@ -59,7 +59,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheInterval<SQLHistoryVal | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr); | ||||
|             var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys); | ||||
|             db.Ado.CancellationToken = cancellationToken; | ||||
|             var result = await db.Fastest<SQLHistoryValue>().PageSize(50000).SplitTable().BulkCopyAsync(dbInserts); | ||||
|             //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync(); | ||||
| @@ -79,7 +79,7 @@ public partial class SqlDBProducer : BusinessBaseWithCacheInterval<SQLHistoryVal | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var db = BusinessDatabaseUtil.GetDb(_driverPropertys.DbType, _driverPropertys.BigTextConnectStr); | ||||
|             var db = SqlDBBusinessDatabaseUtil.GetDb(_driverPropertys); | ||||
|             db.Ado.CancellationToken = cancellationToken; | ||||
|             if (!_initRealData) | ||||
|             { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user