Compare commits

...

11 Commits

Author SHA1 Message Date
Kimdiego2098
27e8653a1a 3.0.0.13 2023-10-16 17:44:09 +08:00
Kimdiego2098
863beda82c 增加关系库存储插件; 2023-10-16 17:40:17 +08:00
Kimdiego2098
bac84c3ecd 增加时序库存储插件; 2023-10-16 17:40:13 +08:00
Kimdiego2098
2fca2ad9f8 更新pro用户列表 2023-10-16 17:39:14 +08:00
Kimdiego2098
dd75286fe0 3.0.0.12 2023-10-16 08:47:39 +08:00
Kimdiego2098
7f91792cf1 opcuaclient添加是否加载服务端数据类型的选项 2023-10-16 08:46:46 +08:00
Kimdiego2098
0e0ccad311 fix:tcpservice dispose err 2023-10-16 08:46:32 +08:00
Diego2098
0691f72e67 !9 增加可选择安全订阅
Merge pull request !9 from youthalan/N/A
2023-10-16 00:33:19 +00:00
youthalan
7e38a51720 增加可选择安全订阅,以加快订阅速度
Signed-off-by: youthalan <youthalan@126.com>
2023-10-16 00:26:59 +00:00
Kimdiego2098
34ca8243a3 更新3.0.0.11 2023-10-15 20:26:18 +08:00
Diego2098
112fea7632 读取数据类型方法改为批量 2023-10-15 20:21:23 +08:00
93 changed files with 1941 additions and 850 deletions

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.10</Version>
<Version>3.0.0.13</Version>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Authors>Diego</Authors>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.10</Version>
<Version>3.0.0.13</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net45;netstandard2.0;net6.0;net7.0</TargetFrameworks>

View File

@@ -1,4 +1,4 @@
#region copyright
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -70,6 +70,11 @@ public class OPCNode
/// </summary>
[Description("安全策略")]
public bool IsUseSecurity { get; set; } = false;
/// <summary>
/// 加载服务端数据类型
/// </summary>
[Description("加载服务端数据类型")]
public bool LoadType { get; set; } = true;
/// <inheritdoc/>
public override string ToString()
{

View File

@@ -1,4 +1,4 @@
#region copyright
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
@@ -200,7 +200,7 @@ public class OPCUAClient : IDisposable
/// <summary>
/// 新增订阅需要指定订阅组名称订阅的tag名数组
/// </summary>
public async Task AddSubscriptionAsync(string subscriptionName, string[] items)
public async Task AddSubscriptionAsync(string subscriptionName, string[] items, bool loadType = true)
{
Subscription m_subscription = new(m_session.DefaultSubscription)
{
@@ -213,14 +213,14 @@ public class OPCUAClient : IDisposable
DisplayName = subscriptionName
};
List<MonitoredItem> monitoredItems = new();
var variableNodes = loadType ? await ReadNodesAsync(items) : null;
for (int i = 0; i < items.Length; i++)
{
try
{
var variableNode = await ReadNodeAsync(items[i], false);
var item = new MonitoredItem
{
StartNodeId = variableNode.NodeId,
StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
AttributeId = Attributes.Value,
DisplayName = items[i],
Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
@@ -307,11 +307,11 @@ public class OPCUAClient : IDisposable
}
private async void Callback(MonitoredItem monitoreditem, MonitoredItemNotificationEventArgs monitoredItemNotificationEventArgs)
private void Callback(MonitoredItem monitoreditem, MonitoredItemNotificationEventArgs monitoredItemNotificationEventArgs)
{
try
{
var variableNode = await ReadNodeAsync(monitoreditem.StartNodeId.ToString(), false);
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
foreach (var value in monitoreditem.DequeueValues())
{
if (value.Value != null)
@@ -532,7 +532,7 @@ public class OPCUAClient : IDisposable
//如果是订阅模式,连接时添加订阅组
if (OPCNode.ActiveSubscribe)
await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray());
await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray(), OPCNode.LoadType);
return m_session;
}
@@ -680,634 +680,67 @@ public class OPCUAClient : IDisposable
return value;
}
}
NodeId nodeToRead = new(nodeIdStr);
var node = (VariableNode)await ReadNodeAsync(nodeToRead, NodeClass.Unspecified, false, cancellationToken);
await typeSystem.LoadType(node.DataType, true, false);
var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
if (OPCNode.LoadType)
await typeSystem.LoadType(node.DataType);
_variableDicts.AddOrUpdate(nodeIdStr, node);
return node;
}
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
{
if (!isOnlyServer)
{
if (_variableDicts.TryGetValue(nodeIdStr, out var value))
{
return value;
}
}
NodeId nodeToRead = new(nodeIdStr);
var node = (VariableNode)m_session.ReadNode(nodeToRead, NodeClass.Variable, false);
_variableDicts.AddOrUpdate(nodeIdStr, node);
return node;
}
#endregion
#region session
/// <inheritdoc/>
public async Task<Node> ReadNodeAsync(
NodeId nodeId,
NodeClass nodeClass,
bool optionalAttributes = true,
CancellationToken ct = default)
{
// build list of attributes.
var attributes = CreateAttributes(nodeClass, optionalAttributes);
// build list of values to read.
ReadValueIdCollection itemsToRead = new ReadValueIdCollection();
foreach (uint attributeId in attributes.Keys)
{
ReadValueId itemToRead = new ReadValueId
{
NodeId = nodeId,
AttributeId = attributeId
};
itemsToRead.Add(itemToRead);
}
// read from server.
ReadResponse readResponse = await m_session.ReadAsync(
null,
0,
TimestampsToReturn.Neither,
itemsToRead, ct).ConfigureAwait(false);
DataValueCollection values = readResponse.Results;
DiagnosticInfoCollection diagnosticInfos = readResponse.DiagnosticInfos;
ClientBase.ValidateResponse(values, itemsToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead);
return ProcessReadResponse(readResponse.ResponseHeader, attributes, itemsToRead, values, diagnosticInfos);
}
/// <summary>
/// Creates a Node based on the read response.
/// 从服务器读取节点
/// </summary>
private Node ProcessReadResponse(
ResponseHeader responseHeader,
IDictionary<uint, DataValue> attributes,
ReadValueIdCollection itemsToRead,
DataValueCollection values,
DiagnosticInfoCollection diagnosticInfos)
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
{
// process results.
int? nodeClass = null;
for (int ii = 0; ii < itemsToRead.Count; ii++)
List<NodeId> nodeIds = new List<NodeId>();
foreach (var item in nodeIdStrs)
{
uint attributeId = itemsToRead[ii].AttributeId;
// the node probably does not exist if the node class is not found.
if (attributeId == Attributes.NodeClass)
NodeId nodeToRead = new(item);
nodeIds.Add(nodeToRead);
}
(IList<Node>, IList<ServiceResult>) nodes = await m_session.ReadNodesAsync(nodeIds, NodeClass.Variable, false, cancellationToken);
for (int i = 0; i < nodes.Item1.Count; i++)
{
if (StatusCode.IsGood(nodes.Item2[i].StatusCode))
{
if (!DataValue.IsGood(values[ii]))
{
throw ServiceResultException.Create(values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
}
// check for valid node class.
nodeClass = values[ii].Value as int?;
if (nodeClass == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", values[ii].Value);
}
var node = ((VariableNode)nodes.Item1[i]);
await typeSystem.LoadType(node.DataType);
_variableDicts.AddOrUpdate(nodeIdStrs[i], node);
}
else
{
if (!DataValue.IsGood(values[ii]))
{
// check for unsupported attributes.
if (values[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
{
continue;
}
// ignore errors on optional attributes
if (StatusCode.IsBad(values[ii].StatusCode))
{
if (attributeId == Attributes.AccessRestrictions ||
attributeId == Attributes.Description ||
attributeId == Attributes.RolePermissions ||
attributeId == Attributes.UserRolePermissions ||
attributeId == Attributes.DataTypeDefinition ||
attributeId == Attributes.AccessLevelEx ||
attributeId == Attributes.UserWriteMask ||
attributeId == Attributes.WriteMask)
{
continue;
}
}
// all supported attributes must be readable.
if (attributeId != Attributes.Value)
{
throw ServiceResultException.Create(values[ii].StatusCode, ii, diagnosticInfos, responseHeader.StringTable);
}
}
}
attributes[attributeId] = values[ii];
}
Node node;
DataValue value;
switch ((NodeClass)nodeClass.Value)
{
default:
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", nodeClass.Value);
}
case NodeClass.Object:
{
ObjectNode objectNode = new ObjectNode();
value = attributes[Attributes.EventNotifier];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Object does not support the EventNotifier attribute.");
}
objectNode.EventNotifier = (byte)value.GetValue(typeof(byte));
node = objectNode;
break;
}
case NodeClass.ObjectType:
{
ObjectTypeNode objectTypeNode = new ObjectTypeNode();
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ObjectType does not support the IsAbstract attribute.");
}
objectTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
node = objectTypeNode;
break;
}
case NodeClass.Variable:
{
VariableNode variableNode = new VariableNode();
// DataType Attribute
value = attributes[Attributes.DataType];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the DataType attribute.");
}
variableNode.DataType = (NodeId)value.GetValue(typeof(NodeId));
// ValueRank Attribute
value = attributes[Attributes.ValueRank];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the ValueRank attribute.");
}
variableNode.ValueRank = (int)value.GetValue(typeof(int));
// ArrayDimensions Attribute
value = attributes[Attributes.ArrayDimensions];
if (value != null)
{
if (value.Value == null)
{
variableNode.ArrayDimensions = Array.Empty<uint>();
}
else
{
variableNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[]));
}
}
// AccessLevel Attribute
value = attributes[Attributes.AccessLevel];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the AccessLevel attribute.");
}
variableNode.AccessLevel = (byte)value.GetValue(typeof(byte));
// UserAccessLevel Attribute
value = attributes[Attributes.UserAccessLevel];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the UserAccessLevel attribute.");
}
variableNode.UserAccessLevel = (byte)value.GetValue(typeof(byte));
// Historizing Attribute
value = attributes[Attributes.Historizing];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the Historizing attribute.");
}
variableNode.Historizing = (bool)value.GetValue(typeof(bool));
// MinimumSamplingInterval Attribute
value = attributes[Attributes.MinimumSamplingInterval];
if (value != null)
{
variableNode.MinimumSamplingInterval = Convert.ToDouble(attributes[Attributes.MinimumSamplingInterval].Value);
}
// AccessLevelEx Attribute
value = attributes[Attributes.AccessLevelEx];
if (value != null)
{
variableNode.AccessLevelEx = (uint)value.GetValue(typeof(uint));
}
node = variableNode;
break;
}
case NodeClass.VariableType:
{
VariableTypeNode variableTypeNode = new VariableTypeNode();
// IsAbstract Attribute
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the IsAbstract attribute.");
}
variableTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
// DataType Attribute
value = attributes[Attributes.DataType];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the DataType attribute.");
}
variableTypeNode.DataType = (NodeId)value.GetValue(typeof(NodeId));
// ValueRank Attribute
value = attributes[Attributes.ValueRank];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the ValueRank attribute.");
}
variableTypeNode.ValueRank = (int)value.GetValue(typeof(int));
// ArrayDimensions Attribute
value = attributes[Attributes.ArrayDimensions];
if (value != null && value.Value != null)
{
variableTypeNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[]));
}
node = variableTypeNode;
break;
}
case NodeClass.Method:
{
MethodNode methodNode = new MethodNode();
// Executable Attribute
value = attributes[Attributes.Executable];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the Executable attribute.");
}
methodNode.Executable = (bool)value.GetValue(typeof(bool));
// UserExecutable Attribute
value = attributes[Attributes.UserExecutable];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the UserExecutable attribute.");
}
methodNode.UserExecutable = (bool)value.GetValue(typeof(bool));
node = methodNode;
break;
}
case NodeClass.DataType:
{
DataTypeNode dataTypeNode = new DataTypeNode();
// IsAbstract Attribute
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "DataType does not support the IsAbstract attribute.");
}
dataTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
// DataTypeDefinition Attribute
value = attributes[Attributes.DataTypeDefinition];
if (value != null)
{
dataTypeNode.DataTypeDefinition = value.Value as ExtensionObject;
}
node = dataTypeNode;
break;
}
case NodeClass.ReferenceType:
{
ReferenceTypeNode referenceTypeNode = new ReferenceTypeNode();
// IsAbstract Attribute
value = attributes[Attributes.IsAbstract];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the IsAbstract attribute.");
}
referenceTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool));
// Symmetric Attribute
value = attributes[Attributes.Symmetric];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the Symmetric attribute.");
}
referenceTypeNode.Symmetric = (bool)value.GetValue(typeof(bool));
// InverseName Attribute
value = attributes[Attributes.InverseName];
if (value != null && value.Value != null)
{
referenceTypeNode.InverseName = (LocalizedText)value.GetValue(typeof(LocalizedText));
}
node = referenceTypeNode;
break;
}
case NodeClass.View:
{
ViewNode viewNode = new ViewNode();
// EventNotifier Attribute
value = attributes[Attributes.EventNotifier];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the EventNotifier attribute.");
}
viewNode.EventNotifier = (byte)value.GetValue(typeof(byte));
// ContainsNoLoops Attribute
value = attributes[Attributes.ContainsNoLoops];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the ContainsNoLoops attribute.");
}
viewNode.ContainsNoLoops = (bool)value.GetValue(typeof(bool));
node = viewNode;
break;
}
}
// NodeId Attribute
value = attributes[Attributes.NodeId];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the NodeId attribute.");
}
node.NodeId = (NodeId)value.GetValue(typeof(NodeId));
node.NodeClass = (NodeClass)nodeClass.Value;
// BrowseName Attribute
value = attributes[Attributes.BrowseName];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the BrowseName attribute.");
}
node.BrowseName = (QualifiedName)value.GetValue(typeof(QualifiedName));
// DisplayName Attribute
value = attributes[Attributes.DisplayName];
if (value == null)
{
throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not support the DisplayName attribute.");
}
node.DisplayName = (LocalizedText)value.GetValue(typeof(LocalizedText));
// all optional attributes follow
// Description Attribute
if (attributes.TryGetValue(Attributes.Description, out value) &&
value != null && value.Value != null)
{
node.Description = (LocalizedText)value.GetValue(typeof(LocalizedText));
}
// WriteMask Attribute
if (attributes.TryGetValue(Attributes.WriteMask, out value) &&
value != null)
{
node.WriteMask = (uint)value.GetValue(typeof(uint));
}
// UserWriteMask Attribute
if (attributes.TryGetValue(Attributes.UserWriteMask, out value) &&
value != null)
{
node.UserWriteMask = (uint)value.GetValue(typeof(uint));
}
// RolePermissions Attribute
if (attributes.TryGetValue(Attributes.RolePermissions, out value) &&
value != null)
{
ExtensionObject[] rolePermissions = value.Value as ExtensionObject[];
if (rolePermissions != null)
{
node.RolePermissions = new RolePermissionTypeCollection();
foreach (ExtensionObject rolePermission in rolePermissions)
{
node.RolePermissions.Add(rolePermission.Body as RolePermissionType);
}
_logAction?.Invoke(3, this, $"获取服务器节点信息失败{nodes.Item2[i]}", null);
}
}
// UserRolePermissions Attribute
if (attributes.TryGetValue(Attributes.UserRolePermissions, out value) &&
value != null)
{
ExtensionObject[] userRolePermissions = value.Value as ExtensionObject[];
if (userRolePermissions != null)
{
node.UserRolePermissions = new RolePermissionTypeCollection();
foreach (ExtensionObject rolePermission in userRolePermissions)
{
node.UserRolePermissions.Add(rolePermission.Body as RolePermissionType);
}
}
}
// AccessRestrictions Attribute
if (attributes.TryGetValue(Attributes.AccessRestrictions, out value) &&
value != null)
{
node.AccessRestrictions = (ushort)value.GetValue(typeof(ushort));
}
return node;
return nodes.Item1.ToList();
}
/// <summary>
/// Create a dictionary of attributes to read for a nodeclass.
/// </summary>
private IDictionary<uint, DataValue> CreateAttributes(NodeClass nodeclass = NodeClass.Unspecified, bool optionalAttributes = true)
{
// Attributes to read for all types of nodes
var attributes = new SortedDictionary<uint, DataValue>() {
{ Attributes.NodeId, null },
{ Attributes.NodeClass, null },
{ Attributes.BrowseName, null },
{ Attributes.DisplayName, null },
};
switch (nodeclass)
{
case NodeClass.Object:
attributes.Add(Attributes.EventNotifier, null);
break;
case NodeClass.Variable:
attributes.Add(Attributes.DataType, null);
attributes.Add(Attributes.ValueRank, null);
attributes.Add(Attributes.ArrayDimensions, null);
attributes.Add(Attributes.AccessLevel, null);
attributes.Add(Attributes.UserAccessLevel, null);
attributes.Add(Attributes.Historizing, null);
attributes.Add(Attributes.MinimumSamplingInterval, null);
attributes.Add(Attributes.AccessLevelEx, null);
break;
case NodeClass.Method:
attributes.Add(Attributes.Executable, null);
attributes.Add(Attributes.UserExecutable, null);
break;
case NodeClass.ObjectType:
attributes.Add(Attributes.IsAbstract, null);
break;
case NodeClass.VariableType:
attributes.Add(Attributes.IsAbstract, null);
attributes.Add(Attributes.DataType, null);
attributes.Add(Attributes.ValueRank, null);
attributes.Add(Attributes.ArrayDimensions, null);
break;
case NodeClass.ReferenceType:
attributes.Add(Attributes.IsAbstract, null);
attributes.Add(Attributes.Symmetric, null);
attributes.Add(Attributes.InverseName, null);
break;
case NodeClass.DataType:
attributes.Add(Attributes.IsAbstract, null);
attributes.Add(Attributes.DataTypeDefinition, null);
break;
case NodeClass.View:
attributes.Add(Attributes.EventNotifier, null);
attributes.Add(Attributes.ContainsNoLoops, null);
break;
default:
// build complete list of attributes.
attributes = new SortedDictionary<uint, DataValue> {
{ Attributes.NodeId, null },
{ Attributes.NodeClass, null },
{ Attributes.BrowseName, null },
{ Attributes.DisplayName, null },
//{ Attributes.Description, null },
//{ Attributes.WriteMask, null },
//{ Attributes.UserWriteMask, null },
{ Attributes.DataType, null },
{ Attributes.ValueRank, null },
{ Attributes.ArrayDimensions, null },
{ Attributes.AccessLevel, null },
{ Attributes.UserAccessLevel, null },
{ Attributes.MinimumSamplingInterval, null },
{ Attributes.Historizing, null },
{ Attributes.EventNotifier, null },
{ Attributes.Executable, null },
{ Attributes.UserExecutable, null },
{ Attributes.IsAbstract, null },
{ Attributes.InverseName, null },
{ Attributes.Symmetric, null },
{ Attributes.ContainsNoLoops, null },
{ Attributes.DataTypeDefinition, null },
//{ Attributes.RolePermissions, null },
//{ Attributes.UserRolePermissions, null },
//{ Attributes.AccessRestrictions, null },
{ Attributes.AccessLevelEx, null }
};
break;
}
if (optionalAttributes)
{
attributes.Add(Attributes.Description, null);
attributes.Add(Attributes.WriteMask, null);
attributes.Add(Attributes.UserWriteMask, null);
attributes.Add(Attributes.RolePermissions, null);
attributes.Add(Attributes.UserRolePermissions, null);
attributes.Add(Attributes.AccessRestrictions, null);
}
return attributes;
}
#endregion
#region
/// <summary>

View File

@@ -498,9 +498,9 @@ namespace ThingsGateway.Foundation.Sockets
this.Clear();
this.m_serverState = ServerState.Stopped;
if (this.PluginsManager.Enable)
if (this.PluginsManager?.Enable == true)
{
this.m_pluginsManager.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
this.m_pluginsManager?.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
}
return this;
}
@@ -540,11 +540,11 @@ namespace ThingsGateway.Foundation.Sockets
this.Clear();
this.m_serverState = ServerState.Disposed;
if (this.PluginsManager.Enable)
if (this.PluginsManager?.Enable == true)
{
this.m_pluginsManager.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
this.m_pluginsManager?.Raise(nameof(IServerStopedPlugin.OnServerStoped), this, new ServiceStateEventArgs(this.m_serverState, default));
}
this.PluginsManager.SafeDispose();
this.PluginsManager?.SafeDispose();
}
base.Dispose(disposing);
}

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.10</Version>
<Version>3.0.0.13</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>

View File

@@ -200,6 +200,7 @@ public class OPCUAClient : CollectBase
UserName = driverPropertys.UserName,
Password = driverPropertys.Password,
CheckDomain = driverPropertys.CheckDomain,
LoadType = driverPropertys.LoadType,
};
if (_plc == null)
{

View File

@@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Plugin.OPCUA;
/// <inheritdoc/>
@@ -51,6 +52,12 @@ public class OPCUAClientProperty : CollectDriverPropertyBase
[DeviceProperty("使用SourceTime", "")]
public bool SourceTimestampEnable { get; set; } = true;
/// <summary>
/// 加载服务端数据类型
/// </summary>
[DeviceProperty("加载服务端数据类型")]
public bool LoadType { get; set; } = true;
/// <summary>
/// 激活订阅
/// </summary>

View File

@@ -40,6 +40,7 @@
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.ActiveSubscribe) Dense Outlined HideDetails="@("auto")" @bind-Value=@node.ActiveSubscribe />
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.IsUseSecurity) Dense HideDetails="@("auto")" @bind-Value=@node.IsUseSecurity />
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.CheckDomain) Dense HideDetails="@("auto")" @bind-Value=@node.CheckDomain />
<MCheckbox Class="ma-1" Label=@node.DescriptionWithOutSugar(a=>a.LoadType) Dense HideDetails="@("auto")" @bind-Value=@node.LoadType />
<MButton Class="ma-1" OnClick=@ConnectAsync Color="primary">
连接

View File

@@ -147,6 +147,7 @@ public partial class OPCUAImportVariable
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.UserName), Value = PLC.OPCNode.UserName, Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.UserName)).GetCustomAttribute<DevicePropertyAttribute>().Description });
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.Password), Value = PLC.OPCNode.Password, Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.Password)).GetCustomAttribute<DevicePropertyAttribute>().Description });
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.CheckDomain), Value = PLC.OPCNode.CheckDomain.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.CheckDomain)).GetCustomAttribute<DevicePropertyAttribute>().Description });
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.LoadType), Value = PLC.OPCNode.LoadType.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.LoadType)).GetCustomAttribute<DevicePropertyAttribute>().Description });
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.IsUseSecurity), Value = PLC.OPCNode.IsUseSecurity.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.IsUseSecurity)).GetCustomAttribute<DevicePropertyAttribute>().Description });
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.ActiveSubscribe), Value = true.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.ActiveSubscribe)).GetCustomAttribute<DevicePropertyAttribute>().Description });
data.DevicePropertys.Add(new() { PropertyName = nameof(OPCUAClientProperty.DeadBand), Value = PLC.OPCNode.DeadBand.ToString(), Description = typeof(OPCUAClientProperty).GetProperty(nameof(OPCUAClientProperty.DeadBand)).GetCustomAttribute<DevicePropertyAttribute>().Description });

View File

@@ -0,0 +1,15 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using ThingsGateway.Foundation.Core;
global using ThingsGateway.Gateway.Application;
global using ThingsGateway.Gateway.Core;

View File

@@ -0,0 +1,325 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Mapster;
using Microsoft.Extensions.Hosting;
using SqlSugar;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using ThingsGateway.Foundation.Extension;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Foundation.Extension.String;
namespace ThingsGateway.Plugin.QuestDB;
public class QuestDB : UpLoadBase
{
private readonly ConcurrentQueue<QuestDBHistoryValue> DeviceVariableRunTimes = new();
private readonly QuestDBProperty driverPropertys = new();
private readonly QuestDBVariableProperty variablePropertys = new();
private TypeAdapterConfig _config;
private GlobalDeviceData _globalDeviceData;
private List<DeviceVariableRunTime> _uploadVariables = new();
private TimerTick exTimerTick;
public QuestDB()
{
_config = new TypeAdapterConfig();
_config.ForType<DeviceVariableRunTime, HistoryValue>()
.Map(dest => dest.Value, (src) => ValueReturn(src))
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime())//注意sqlsugar插入时无时区直接utc时间
.Map(dest => dest.CreateTime, (src) => DateTime.UtcNow);//注意sqlsugar插入时无时区直接utc时间
}
private static object ValueReturn(DeviceVariableRunTime src)
{
if (src.Value?.ToString()?.IsBoolValue() == true)
{
if (src.Value.ToBoolean())
{
return 1;
}
else
{
return 0;
}
}
else
{
return src.Value;
}
}
public override Type DriverDebugUIType => null;
public override UpDriverPropertyBase DriverPropertys => driverPropertys;
public override List<DeviceVariableRunTime> UploadVariables => _uploadVariables;
public override VariablePropertyBase VariablePropertys => variablePropertys;
public override Task AfterStopAsync()
{
return Task.CompletedTask;
}
public override async Task BeforStartAsync(CancellationToken cancellationToken)
{
SqlSugarClient db = GetHisDbAsync();
db.DbMaintenance.CreateDatabase();
db.CodeFirst.InitTables(typeof(QuestDBHistoryValue));
await Task.CompletedTask;
}
public override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var db = GetHisDbAsync();
{
if (!driverPropertys.IsInterval)
{
try
{
////变化推送
var varList = DeviceVariableRunTimes.ToListWithDequeue();
if (varList?.Count != 0)
{
await InserableAsync(db, varList, cancellationToken);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
else
{
if (exTimerTick.IsTickHappen())
{
try
{
var varList = _uploadVariables.ToList().Adapt<List<QuestDBHistoryValue>>(_config);
if (varList?.Count != 0)
{
await InserableAsync(db, varList, cancellationToken);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
}
}
if (driverPropertys.CycleInterval > UploadDeviceThread.CycleInterval + 50)
{
try
{
await Task.Delay(driverPropertys.CycleInterval - UploadDeviceThread.CycleInterval, cancellationToken);
}
catch
{
}
}
}
public override bool IsConnected() => _uploadVariables?.Count > 0;
protected override void Dispose(bool disposing)
{
try
{
_globalDeviceData?.AllVariables?.ForEach(a => a.VariableValueChange -= VariableValueChange);
_uploadVariables = null;
}
catch (Exception ex)
{
LogMessage.LogError(ex, ToString());
}
base.Dispose(disposing);
}
protected override void Init(UploadDeviceRunTime device)
{
_globalDeviceData = App.GetService<GlobalDeviceData>();
var tags = _globalDeviceData.AllVariables.Where(a => a.VariablePropertys.ContainsKey(device.Id))
.Where(b => GetPropertyValue(b, nameof(variablePropertys.Enable)).ToBoolean())
.ToList();
_uploadVariables = tags;
if (!driverPropertys.IsInterval)
{
_uploadVariables.ForEach(a =>
{
a.VariableValueChange += VariableValueChange;
});
}
if (_uploadVariables.Count == 0)
{
LogMessage.LogWarning("插件变量数量为0");
}
if (driverPropertys.IntervalTime < 1)
driverPropertys.IntervalTime = 10;
exTimerTick = new(driverPropertys.IntervalTime * 1000);
}
/// <summary>
/// Aop设置
/// </summary>
/// <param name="db"></param>
private static void AopSetting(SqlSugarClient db)
{
var config = db.CurrentConnectionConfig;
// 设置超时时间
db.Ado.CommandTimeOut = 30;
// 打印SQL语句
db.Aop.OnLogExecuting = (sql, pars) =>
{
//如果不是开发环境就打印sql
if (App.HostEnvironment.IsDevelopment())
{
if (sql.StartsWith("SELECT"))
{
Console.ForegroundColor = ConsoleColor.Green;
}
if (sql.StartsWith("UPDATE"))
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
if (sql.StartsWith("INSERT"))
{
Console.ForegroundColor = ConsoleColor.Blue;
}
if (sql.StartsWith("DELETE"))
{
Console.ForegroundColor = ConsoleColor.Red;
}
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
}
};
//异常
db.Aop.OnError = (ex) =>
{
//如果不是开发环境就打印日志
if (App.WebHostEnvironment.IsDevelopment())
{
if (ex.Parametres == null) return;
Console.ForegroundColor = ConsoleColor.Red;
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
Console.ForegroundColor = ConsoleColor.White;
}
};
}
private static void WriteSqlLog(string msg)
{
Console.WriteLine("【Sql执行时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
private static void WriteSqlLogError(string msg)
{
Console.WriteLine("【Sql执行错误时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
/// <summary>
/// 获取数据库链接
/// </summary>
/// <returns></returns>
private SqlSugarClient GetHisDbAsync()
{
var configureExternalServices = new ConfigureExternalServices
{
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 = driverPropertys.ConnectStr,//连接字符串
DbType = DbType.QuestDB,//数据库类型
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
}
);
AopSetting(sqlSugarClient);//aop配置
return sqlSugarClient;
}
private async Task InserableAsync(SqlSugarClient db, List<QuestDBHistoryValue> dbInserts, CancellationToken cancellationToken)
{
try
{
var result = await db.Insertable(dbInserts).ExecuteCommandAsync(cancellationToken);
if (result > 0)
LogMessage.Trace(FoundationConst.LogMessageHeader + dbInserts.ToJsonString());
//连接成功时补发缓存数据
var cacheData = await CacheDb.GetCacheData();
foreach (var item in cacheData)
{
try
{
var data = item.CacheStr.FromJsonString<List<QuestDBHistoryValue>>();
var cacheresult = await db.Insertable(data).ExecuteCommandAsync(cancellationToken);
if (cacheresult > 0)
{
await CacheDb.DeleteCacheData(item.Id);
LogMessage.Trace(FoundationConst.LogMessageHeader + $"主题:{item.Topic}{Environment.NewLine}负载:{item.CacheStr}");
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, ToString());
}
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, ToString());
await CacheDb.AddCacheData("", dbInserts.ToJsonString(), driverPropertys.CacheMaxCount);
}
}
private void VariableValueChange(DeviceVariableRunTime collectVariableRunTime)
{
if (!driverPropertys.IsInterval)
DeviceVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<QuestDBHistoryValue>(_config));
}
}

View File

@@ -0,0 +1,62 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
using System.ComponentModel;
namespace ThingsGateway.Gateway.Core;
/// <summary>
/// 历史数据表
/// </summary>
[SugarTable("historyValue", TableDescription = "历史数据表")]
public class QuestDBHistoryValue
{
/// <summary>
/// 采集时间
/// </summary>
[TimeDbSplitField(DateType.Month)]
[Description("采集时间")]
public DateTime CollectTime { get; set; }
/// <summary>
/// 上传时间
/// </summary>
[Description("上传时间")]
public DateTime CreateTime { get; set; }
/// <summary>
/// 设备名称
/// </summary>
[SugarColumn(ColumnDataType = "symbol")]
[Description("设备名称")]
public string DeviceName { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[SugarColumn(ColumnDataType = "symbol")]
[Description("变量名称")]
public string Name { get; set; }
/// <summary>
/// 是否在线
/// </summary>
[Description("是否在线")]
public bool IsOnline { get; set; }
/// <summary>
/// 变量值
/// </summary>
[Description("变量值")]
public double Value { get; set; }
}

View File

@@ -0,0 +1,31 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Plugin.QuestDB;
public class QuestDBProperty : UpDriverPropertyBase
{
[DeviceProperty("链接字符串", "")] public string ConnectStr { get; set; } = "host=localhost;port=8812;username=admin;password=quest;database=qdb;ServerCompatibilityMode=NoTypeLoading;";
[DeviceProperty("是否间隔插入", "False时将每次变化写入")] public bool IsInterval { get; set; } = true;
[DeviceProperty("间隔时间", "秒,实时表时代表更新间隔,历史表时代表插入间隔")] public int IntervalTime { get; set; } = 10;
[DeviceProperty("缓存最大条数", "默认2千条")] public int CacheMaxCount { get; set; } = 2000;
/// <summary>
/// 线程循环间隔
/// </summary>
[DeviceProperty("线程循环间隔", "最小10ms")]
public int CycleInterval { get; set; } = 1000;
}

View File

@@ -0,0 +1,22 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Plugin.QuestDB;
public class QuestDBVariableProperty : VariablePropertyBase
{
[VariableProperty("启用", "")]
public bool Enable { get; set; } = true;
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command=" set dir=&quot;$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist %25dir%25 md %25dir%25 &#xD;&#xA;copy &quot;$(TargetDir)*QuestDB*.dll&quot; %25dir%25&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
</Target>
</Project>

View File

@@ -0,0 +1,15 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using ThingsGateway.Foundation.Core;
global using ThingsGateway.Gateway.Application;
global using ThingsGateway.Gateway.Core;

View File

@@ -0,0 +1,332 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Mapster;
using Microsoft.Extensions.Hosting;
using SqlSugar;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Foundation.Extension.String;
using Yitter.IdGenerator;
namespace ThingsGateway.Plugin.SQLDB;
public class SQLDB : UpLoadBase
{
private readonly ConcurrentQueue<SQLHistoryValue> DeviceVariableRunTimes = new();
private readonly SQLDBProperty driverPropertys = new();
private readonly SQLDBVariableProperty variablePropertys = new();
private TypeAdapterConfig _config;
private GlobalDeviceData _globalDeviceData;
private List<DeviceVariableRunTime> _uploadVariables = new();
private TimerTick exTimerTick;
private TimerTick exRealTimerTick;
public SQLDB()
{
_config = new TypeAdapterConfig();
_config.ForType<DeviceVariableRunTime, SQLHistoryValue>()
.Map(dest => dest.Id, (src) => YitIdHelper.NextId())
.Map(dest => dest.CreateTime, (src) => DateTime.Now);
}
public override Type DriverDebugUIType => null;
public override UpDriverPropertyBase DriverPropertys => driverPropertys;
public override List<DeviceVariableRunTime> UploadVariables => _uploadVariables;
public override VariablePropertyBase VariablePropertys => variablePropertys;
public override Task AfterStopAsync()
{
return Task.CompletedTask;
}
public override async Task BeforStartAsync(CancellationToken cancellationToken)
{
var db = GetHisDbAsync();
db.CodeFirst.InitTables(typeof(SQLHistoryValue));
db.MappingTables.Add("SQLRealValue", driverPropertys.ReadDBTableName); // typeof(类).Name 可以拿到类名
db.CodeFirst.InitTables(typeof(SQLRealValue)); //生成的表名是 newTableName
await Task.CompletedTask;
}
public override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var db = GetHisDbAsync();
if (driverPropertys.IsReadDB)
{
if (exRealTimerTick.IsTickHappen())
{
try
{
var varList = _uploadVariables.ToList().Adapt<List<SQLRealValue>>();
if (varList?.Count != 0)
{
var result = await db.Storageable(varList).As(driverPropertys.ReadDBTableName).ExecuteCommandAsync();
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
}
else
{
if (!driverPropertys.IsInterval)
{
try
{
////变化推送
var varList = DeviceVariableRunTimes.ToListWithDequeue();
if (varList?.Count != 0)
{
await InserableAsync(db, varList, cancellationToken);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
else
{
if (exTimerTick.IsTickHappen())
{
try
{
var varList = _uploadVariables.ToList().Adapt<List<SQLHistoryValue>>(_config);
if (varList?.Count != 0)
{
await InserableAsync(db, varList, cancellationToken);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
}
}
if (driverPropertys.CycleInterval > UploadDeviceThread.CycleInterval + 50)
{
try
{
await Task.Delay(driverPropertys.CycleInterval - UploadDeviceThread.CycleInterval, cancellationToken);
}
catch
{
}
}
}
/// <summary>
/// 获取数据库链接
/// </summary>
/// <returns></returns>
public SqlSugarClient GetHisDbAsync()
{
var configureExternalServices = new ConfigureExternalServices
{
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 = driverPropertys.ConnectStr,//连接字符串
DbType = driverPropertys.DbType,//数据库类型
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
}
);
AopSetting(sqlSugarClient);//aop配置
return sqlSugarClient;
}
public override bool IsConnected() => _uploadVariables?.Count > 0;
protected override void Dispose(bool disposing)
{
try
{
_globalDeviceData?.AllVariables?.ForEach(a => a.VariableValueChange -= VariableValueChange);
_uploadVariables = null;
}
catch (Exception ex)
{
LogMessage.LogError(ex, ToString());
}
base.Dispose(disposing);
}
protected override void Init(UploadDeviceRunTime device)
{
_globalDeviceData = App.GetService<GlobalDeviceData>();
var tags = _globalDeviceData.AllVariables.Where(a => a.VariablePropertys.ContainsKey(device.Id))
.Where(b => GetPropertyValue(b, nameof(variablePropertys.Enable)).ToBoolean())
.ToList();
_uploadVariables = tags;
if (!driverPropertys.IsReadDB)
if (!driverPropertys.IsInterval)
{
_uploadVariables.ForEach(a =>
{
a.VariableValueChange += VariableValueChange;
});
}
if (_uploadVariables.Count == 0)
{
LogMessage.LogWarning("插件变量数量为0");
}
if (driverPropertys.IntervalTime < 1)
driverPropertys.IntervalTime = 10;
exTimerTick = new(driverPropertys.IntervalTime * 1000);
exRealTimerTick = new(driverPropertys.IntervalTime * 1000);
}
/// <summary>
/// Aop设置
/// </summary>
/// <param name="db"></param>
private static void AopSetting(SqlSugarClient db)
{
var config = db.CurrentConnectionConfig;
// 设置超时时间
db.Ado.CommandTimeOut = 30;
// 打印SQL语句
db.Aop.OnLogExecuting = (sql, pars) =>
{
//如果不是开发环境就打印sql
if (App.HostEnvironment.IsDevelopment())
{
if (sql.StartsWith("SELECT"))
{
Console.ForegroundColor = ConsoleColor.Green;
}
if (sql.StartsWith("UPDATE"))
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
if (sql.StartsWith("INSERT"))
{
Console.ForegroundColor = ConsoleColor.Blue;
}
if (sql.StartsWith("DELETE"))
{
Console.ForegroundColor = ConsoleColor.Red;
}
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
}
};
//异常
db.Aop.OnError = (ex) =>
{
//如果不是开发环境就打印日志
if (App.WebHostEnvironment.IsDevelopment())
{
if (ex.Parametres == null) return;
Console.ForegroundColor = ConsoleColor.Red;
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
Console.ForegroundColor = ConsoleColor.White;
}
};
}
private static void WriteSqlLog(string msg)
{
Console.WriteLine("【Sql执行时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
private static void WriteSqlLogError(string msg)
{
Console.WriteLine("【Sql执行错误时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
private async Task InserableAsync(SqlSugarClient db, List<SQLHistoryValue> dbInserts, CancellationToken cancellationToken)
{
try
{
var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync();
if (result > 0)
LogMessage.Trace(FoundationConst.LogMessageHeader + dbInserts.ToJsonString());
//连接成功时补发缓存数据
var cacheData = await CacheDb.GetCacheData();
foreach (var item in cacheData)
{
try
{
var data = item.CacheStr.FromJsonString<List<SQLHistoryValue>>();
var cacheresult = await db.Insertable(data).SplitTable().ExecuteCommandAsync();
if (cacheresult > 0)
{
await CacheDb.DeleteCacheData(item.Id);
LogMessage.Trace(FoundationConst.LogMessageHeader + $"主题:{item.Topic}{Environment.NewLine}负载:{item.CacheStr}");
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, ToString());
}
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, ToString());
await CacheDb.AddCacheData("", dbInserts.ToJsonString(), driverPropertys.CacheMaxCount);
}
}
private void VariableValueChange(DeviceVariableRunTime collectVariableRunTime)
{
if (!driverPropertys.IsReadDB)
if (!driverPropertys.IsInterval)
DeviceVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<SQLHistoryValue>(_config));
}
}

View File

@@ -0,0 +1,37 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
namespace ThingsGateway.Plugin.SQLDB;
public class SQLDBProperty : UpDriverPropertyBase
{
[DeviceProperty("是否实时表", "true=>实时表更新false=>历史存储(按月分表)")] public bool IsReadDB { get; set; } = false;
[DeviceProperty("实时表名称", "")] public string ReadDBTableName { get; set; } = "ReadDBTableName";
[DeviceProperty("数据库类型", "MySql/SqlServer")] public DbType DbType { get; set; } = DbType.MySql;
[DeviceProperty("链接字符串", "")] public string ConnectStr { get; set; } = "server=localhost;Database=test;Uid=root;Pwd=111111;";
[DeviceProperty("是否间隔插入", "False时将每次变化写入")] public bool IsInterval { get; set; } = true;
[DeviceProperty("间隔时间", "秒,实时表时代表更新间隔,历史表时代表插入间隔")] public int IntervalTime { get; set; } = 10;
[DeviceProperty("缓存最大条数", "默认2千条")] public int CacheMaxCount { get; set; } = 2000;
/// <summary>
/// 线程循环间隔
/// </summary>
[DeviceProperty("线程循环间隔", "最小10ms")]
public int CycleInterval { get; set; } = 1000;
}

View File

@@ -0,0 +1,22 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Plugin.SQLDB;
public class SQLDBVariableProperty : VariablePropertyBase
{
[VariableProperty("启用", "")]
public bool Enable { get; set; } = true;
}

View File

@@ -0,0 +1,55 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
namespace ThingsGateway.Plugin.SQLDB;
[SplitTable(SplitType.Month)]//按月分表 (自带分表支持 年、季、月、周、日)
[SugarTable("historyValue_{year}{month}{day}", TableDescription = "设备采集历史表")]//3个变量必须要有
[SugarIndex("index_Name", nameof(SQLHistoryValue.Name), OrderByType.Desc)]
[SugarIndex("index_DeviceName", nameof(SQLHistoryValue.DeviceName), OrderByType.Desc)]
public class SQLHistoryValue
{
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
public long Id { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[SugarColumn(ColumnName = "Name", ColumnDescription = "变量名称")]
public string Name { get; set; }
/// <summary>
/// 设备名称
/// </summary>
[SugarColumn(ColumnName = "DeviceName", ColumnDescription = "设备名称")]
public string DeviceName { get; set; }
///<summary>
///实时值
///</summary>
[SugarColumn(ColumnName = "Value", ColumnDescription = "实时值")]
public string Value { get; set; }
///<summary>
///是否在线
///</summary>
[SugarColumn(ColumnName = "IsOnline", ColumnDescription = "是否在线 True=在线;False=离线")]
public bool IsOnline { get; set; }
public DateTime CollectTime { get; set; }
[SplitField] //分表字段 在插入的时候会根据这个字段插入哪个表,在更新删除的时候用这个字段找出相关表
public DateTime CreateTime { get; set; }
}

View File

@@ -0,0 +1,51 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
namespace ThingsGateway.Plugin.SQLDB;
[SugarTable(TableDescription = "设备采集实时表")]
[SugarIndex("index_Name", nameof(SQLHistoryValue.Name), OrderByType.Desc)]
[SugarIndex("index_DeviceName", nameof(SQLHistoryValue.DeviceName), OrderByType.Desc)]
public class SQLRealValue
{
[SugarColumn(ColumnDescription = "Id", IsPrimaryKey = true)]
public long Id { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[SugarColumn(ColumnName = "Name", ColumnDescription = "变量名称")]
public string Name { get; set; }
/// <summary>
/// 设备名称
/// </summary>
[SugarColumn(ColumnName = "DeviceName", ColumnDescription = "设备名称")]
public string DeviceName { get; set; }
///<summary>
///实时值
///</summary>
[SugarColumn(ColumnName = "Value", ColumnDescription = "实时值")]
public string Value { get; set; }
///<summary>
///是否在线
///</summary>
[SugarColumn(ColumnName = "IsOnline", ColumnDescription = "是否在线 True=在线;False=离线")]
public bool IsOnline { get; set; }
public DateTime CollectTime { get; set; }
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command=" set dir=&quot;$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist %25dir%25 md %25dir%25 &#xD;&#xA;copy &quot;$(TargetDir)*SQLDB*.dll&quot; %25dir%25&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
</Target>
</Project>

View File

@@ -0,0 +1,15 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
global using ThingsGateway.Foundation.Core;
global using ThingsGateway.Gateway.Application;
global using ThingsGateway.Gateway.Core;

View File

@@ -0,0 +1,62 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
using SqlSugar.TDengine;
using System.ComponentModel;
namespace ThingsGateway.Gateway.Core;
/// <summary>
/// 历史数据表
/// </summary>
[SugarTable("historyValue")]
public class TDHistoryValue : STable
{
/// <summary>
/// 上传时间
/// </summary>
[SugarColumn(InsertServerTime = true)]
[Description("上传时间")]
public DateTime CreateTime { get; set; }
/// <summary>
/// 采集时间
/// </summary>
[Description("采集时间")]
public DateTime CollectTime { get; set; }
/// <summary>
/// 设备名称
/// </summary>
[Description("设备名称")]
public string DeviceName { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[Description("变量名称")]
public string Name { get; set; }
/// <summary>
/// 是否在线
/// </summary>
[Description("是否在线")]
public bool IsOnline { get; set; }
/// <summary>
/// 变量值
/// </summary>
[Description("变量值")]
[SugarColumn(Length = 18, DecimalDigits = 2)]
public double Value { get; set; }
}

View File

@@ -0,0 +1,325 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Mapster;
using Microsoft.Extensions.Hosting;
using SqlSugar;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using ThingsGateway.Foundation.Extension;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Foundation.Extension.String;
namespace ThingsGateway.Plugin.SQLDB;
public class TDengineDB : UpLoadBase
{
private readonly ConcurrentQueue<TDHistoryValue> DeviceVariableRunTimes = new();
private readonly TDengineDBProperty driverPropertys = new();
private readonly TDengineDBVariableProperty variablePropertys = new();
private GlobalDeviceData _globalDeviceData;
private TypeAdapterConfig _config;
private List<DeviceVariableRunTime> _uploadVariables = new();
private TimerTick exTimerTick;
public TDengineDB()
{
_config = new TypeAdapterConfig();
_config.ForType<DeviceVariableRunTime, HistoryValue>()
.Map(dest => dest.Value, (src) => ValueReturn(src));
}
private static object ValueReturn(DeviceVariableRunTime src)
{
if (src.Value?.ToString()?.IsBoolValue() == true)
{
if (src.Value.ToBoolean())
{
return 1;
}
else
{
return 0;
}
}
else
{
return src.Value;
}
}
public override Type DriverDebugUIType => null;
public override UpDriverPropertyBase DriverPropertys => driverPropertys;
public override List<DeviceVariableRunTime> UploadVariables => _uploadVariables;
public override VariablePropertyBase VariablePropertys => variablePropertys;
public override Task AfterStopAsync()
{
return Task.CompletedTask;
}
public override async Task BeforStartAsync(CancellationToken cancellationToken)
{
SqlSugarClient db = GetHisDbAsync();
db.DbMaintenance.CreateDatabase();
db.CodeFirst.InitTables(typeof(TDHistoryValue));
await Task.CompletedTask;
}
public override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var db = GetHisDbAsync();
{
if (!driverPropertys.IsInterval)
{
try
{
////变化推送
var varList = DeviceVariableRunTimes.ToListWithDequeue();
if (varList?.Count != 0)
{
await InserableAsync(db, varList, cancellationToken);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
else
{
if (exTimerTick.IsTickHappen())
{
try
{
var varList = _uploadVariables.ToList().Adapt<List<TDHistoryValue>>(_config);
if (varList?.Count != 0)
{
await InserableAsync(db, varList, cancellationToken);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, ToString());
}
}
}
}
if (driverPropertys.CycleInterval > UploadDeviceThread.CycleInterval + 50)
{
try
{
await Task.Delay(driverPropertys.CycleInterval - UploadDeviceThread.CycleInterval, cancellationToken);
}
catch
{
}
}
}
public override bool IsConnected() => _uploadVariables?.Count > 0;
protected override void Dispose(bool disposing)
{
try
{
_globalDeviceData?.AllVariables?.ForEach(a => a.VariableValueChange -= VariableValueChange);
_uploadVariables = null;
}
catch (Exception ex)
{
LogMessage.LogError(ex, ToString());
}
base.Dispose(disposing);
}
protected override void Init(UploadDeviceRunTime device)
{
_globalDeviceData = App.GetService<GlobalDeviceData>();
var tags = _globalDeviceData.AllVariables.Where(a => a.VariablePropertys.ContainsKey(device.Id))
.Where(b => GetPropertyValue(b, nameof(variablePropertys.Enable)).ToBoolean())
.ToList();
_uploadVariables = tags;
if (!driverPropertys.IsInterval)
{
_uploadVariables.ForEach(a =>
{
a.VariableValueChange += VariableValueChange;
});
}
if (_uploadVariables.Count == 0)
{
LogMessage.LogWarning("插件变量数量为0");
}
if (driverPropertys.IntervalTime < 1)
driverPropertys.IntervalTime = 10;
exTimerTick = new(driverPropertys.IntervalTime * 1000);
}
/// <summary>
/// Aop设置
/// </summary>
/// <param name="db"></param>
private static void AopSetting(SqlSugarClient db)
{
var config = db.CurrentConnectionConfig;
// 设置超时时间
db.Ado.CommandTimeOut = 30;
// 打印SQL语句
db.Aop.OnLogExecuting = (sql, pars) =>
{
//如果不是开发环境就打印sql
if (App.HostEnvironment.IsDevelopment())
{
if (sql.StartsWith("SELECT"))
{
Console.ForegroundColor = ConsoleColor.Green;
}
if (sql.StartsWith("UPDATE"))
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
if (sql.StartsWith("INSERT"))
{
Console.ForegroundColor = ConsoleColor.Blue;
}
if (sql.StartsWith("DELETE"))
{
Console.ForegroundColor = ConsoleColor.Red;
}
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
}
};
//异常
db.Aop.OnError = (ex) =>
{
//如果不是开发环境就打印日志
if (App.WebHostEnvironment.IsDevelopment())
{
if (ex.Parametres == null) return;
Console.ForegroundColor = ConsoleColor.Red;
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
Console.ForegroundColor = ConsoleColor.White;
}
};
}
private static void WriteSqlLog(string msg)
{
Console.WriteLine("【Sql执行时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
private static void WriteSqlLogError(string msg)
{
Console.WriteLine("【Sql执行错误时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
/// <summary>
/// 获取数据库链接
/// </summary>
/// <returns></returns>
private SqlSugarClient GetHisDbAsync()
{
var configureExternalServices = new ConfigureExternalServices
{
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 = driverPropertys.ConnectStr,//连接字符串
DbType = driverPropertys.DbType,//数据库类型
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
}
);
AopSetting(sqlSugarClient);//aop配置
return sqlSugarClient;
}
private async Task InserableAsync(SqlSugarClient db, List<TDHistoryValue> dbInserts, CancellationToken cancellationToken)
{
try
{
var result = await db.Insertable(dbInserts).ExecuteCommandAsync(cancellationToken);
if (result > 0)
LogMessage.Trace(FoundationConst.LogMessageHeader + dbInserts.ToJsonString());
//连接成功时补发缓存数据
var cacheData = await CacheDb.GetCacheData();
foreach (var item in cacheData)
{
try
{
var data = item.CacheStr.FromJsonString<List<TDHistoryValue>>();
var cacheresult = await db.Insertable(data).ExecuteCommandAsync(cancellationToken);
if (cacheresult > 0)
{
await CacheDb.DeleteCacheData(item.Id);
LogMessage.Trace(FoundationConst.LogMessageHeader + $"主题:{item.Topic}{Environment.NewLine}负载:{item.CacheStr}");
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, ToString());
}
}
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, ToString());
await CacheDb.AddCacheData("", dbInserts.ToJsonString(), driverPropertys.CacheMaxCount);
}
}
private void VariableValueChange(DeviceVariableRunTime collectVariableRunTime)
{
if (!driverPropertys.IsInterval)
DeviceVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<TDHistoryValue>(_config));
}
}

View File

@@ -0,0 +1,35 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using SqlSugar;
namespace ThingsGateway.Plugin.SQLDB;
public class TDengineDBProperty : UpDriverPropertyBase
{
[DeviceProperty("数据库类型", "QuestDB/TDengine,主要TD需要手动建库并指定超级表")] public DbType DbType { get; set; } = DbType.TDengine;
[DeviceProperty("链接字符串", "")] public string ConnectStr { get; set; } = "Host=localhost;Port=6030;Username=root;Password=taosdata;Database=test";
[DeviceProperty("是否间隔插入", "False时将每次变化写入")] public bool IsInterval { get; set; } = true;
[DeviceProperty("间隔时间", "秒,实时表时代表更新间隔,历史表时代表插入间隔")] public int IntervalTime { get; set; } = 10;
[DeviceProperty("缓存最大条数", "默认2千条")] public int CacheMaxCount { get; set; } = 2000;
/// <summary>
/// 线程循环间隔
/// </summary>
[DeviceProperty("线程循环间隔", "最小10ms")]
public int CycleInterval { get; set; } = 1000;
}

View File

@@ -0,0 +1,22 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
namespace ThingsGateway.Plugin.SQLDB;
public class TDengineDBVariableProperty : VariablePropertyBase
{
[VariableProperty("启用", "")]
public bool Enable { get; set; } = true;
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command=" set dir=&quot;$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist %25dir%25 md %25dir%25 &#xD;&#xA;copy &quot;$(TargetDir)*TDengineDB*.dll&quot; %25dir%25&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
</Target>
<PropertyGroup>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SqlSugar.TDengineCore" Version="2.8.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command=" set dir=&quot;$(SolutionDir)Web\ThingsGateway.Web.Entry\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)&quot;&#xD;&#xA; if not exist %25dir%25 md %25dir%25 &#xD;&#xA;copy &quot;$(TargetDir)*ThingsGateway.Plugin.TDengineDB*.dll&quot; %25dir%25&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;&#xD;&#xA;" />
</Target>
</Project>

View File

@@ -52,10 +52,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Web.Core", "W
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Core", "Web\ThingsGateway.Core\ThingsGateway.Core.csproj", "{51313113-7BB8-494E-9C24-6787BECE39BB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Admin", "Admin", "{79E7042F-F9E3-4D87-BFA9-4B7DD9736735}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "Gateway", "{000C3C62-345E-451C-8CEE-6F2C6A087116}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.ApiController", "Web\ThingsGateway.Gateway.ApiController\ThingsGateway.Gateway.ApiController.csproj", "{5D7BE567-2345-46C8-9F54-DDC1DA96D198}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.Application", "Web\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj", "{5CF1B3EC-84E2-484A-8DFC-2ECD2EE18E2F}"
@@ -64,8 +60,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.Blazo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Gateway.Core", "Web\ThingsGateway.Gateway.Core\ThingsGateway.Gateway.Core.csproj", "{5CD79F91-7182-4A9D-9BEF-4DF410C782D2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugin", "Plugin", "{CC8D0880-B73E-4DFC-9052-86504728708E}"
ProjectSection(SolutionItems) = preProject
Plugin\Directory.Build.props = Plugin\Directory.Build.props
@@ -105,6 +99,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.De
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.Demo.Photino", "Demo\ThingsGateway.Foundation.Demo.Photino\ThingsGateway.Foundation.Demo.Photino.csproj", "{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.SQLDB", "Plugin\ThingsGateway.Plugin.SQLDB\ThingsGateway.Plugin.SQLDB.csproj", "{7EBD5500-0DA0-415A-831D-5DC350917501}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.QuestDB", "Plugin\ThingsGateway.Plugin.QuestDB\ThingsGateway.Plugin.QuestDB.csproj", "{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Plugin.TDengineDB", "Plugin\ThingsGateway.Plugin.TDengineDB\ThingsGateway.Plugin.TDengineDB.csproj", "{2C827B2C-75DF-413B-9AB2-2D1B438AC082}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -231,6 +231,18 @@ Global
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB}.Release|Any CPU.Build.0 = Release|Any CPU
{7EBD5500-0DA0-415A-831D-5DC350917501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7EBD5500-0DA0-415A-831D-5DC350917501}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EBD5500-0DA0-415A-831D-5DC350917501}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EBD5500-0DA0-415A-831D-5DC350917501}.Release|Any CPU.Build.0 = Release|Any CPU
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB}.Release|Any CPU.Build.0 = Release|Any CPU
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C827B2C-75DF-413B-9AB2-2D1B438AC082}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -242,21 +254,18 @@ Global
{566783A4-222B-46F5-AA12-0753997B3254} = {0874CBC5-C583-4FAD-BA93-94571D446898}
{BEFBC44A-E140-4E3E-AFBE-2DD8B98EB9BF} = {0874CBC5-C583-4FAD-BA93-94571D446898}
{9695B353-D773-40DD-B65E-7B10EB0C16EC} = {0874CBC5-C583-4FAD-BA93-94571D446898}
{799C49A4-8E23-475A-A82D-080854718BEE} = {BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15}
{616CA361-B667-42C8-B4DC-097C7CD39830} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
{16C62A28-BACE-4391-91F8-C2D78D063A1E} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
{8FA03089-322F-44CB-8E4B-F2637388E944} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
{14FF7150-6DB7-455B-AD00-6AB4DE37855B} = {79E7042F-F9E3-4D87-BFA9-4B7DD9736735}
{799C49A4-8E23-475A-A82D-080854718BEE} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{616CA361-B667-42C8-B4DC-097C7CD39830} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{16C62A28-BACE-4391-91F8-C2D78D063A1E} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{8FA03089-322F-44CB-8E4B-F2637388E944} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{14FF7150-6DB7-455B-AD00-6AB4DE37855B} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{2861AA39-AAAE-47ED-9ACC-4C165DDF3EF1} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{9FF2A8A6-48D0-4D8A-9EAD-1905174291CC} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{51313113-7BB8-494E-9C24-6787BECE39BB} = {BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15}
{79E7042F-F9E3-4D87-BFA9-4B7DD9736735} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{000C3C62-345E-451C-8CEE-6F2C6A087116} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{5D7BE567-2345-46C8-9F54-DDC1DA96D198} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
{5CF1B3EC-84E2-484A-8DFC-2ECD2EE18E2F} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
{CD0F211A-F65B-4026-9750-68AC3C70D012} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
{5CD79F91-7182-4A9D-9BEF-4DF410C782D2} = {000C3C62-345E-451C-8CEE-6F2C6A087116}
{BB9C2A85-7A8A-4CF9-BF44-34DE9848EC15} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{51313113-7BB8-494E-9C24-6787BECE39BB} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{5D7BE567-2345-46C8-9F54-DDC1DA96D198} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{5CF1B3EC-84E2-484A-8DFC-2ECD2EE18E2F} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{CD0F211A-F65B-4026-9750-68AC3C70D012} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{5CD79F91-7182-4A9D-9BEF-4DF410C782D2} = {9EB46BB6-D4EA-4B06-95EE-6C971E653030}
{2057E5BE-FACA-4D44-A2BA-E1F864A8DAFF} = {CC8D0880-B73E-4DFC-9052-86504728708E}
{A723D4D7-B796-4D97-BA68-95E5696C9559} = {CC8D0880-B73E-4DFC-9052-86504728708E}
{D9944D52-81B4-4DBC-8C3B-2A334CCBD4F6} = {CC8D0880-B73E-4DFC-9052-86504728708E}
@@ -269,6 +278,9 @@ Global
{681F774F-7B0B-450A-917C-1385E1847CA6} = {237C7BC5-7B07-40B5-AF42-CE2F8E0893C3}
{637A662B-7B70-4CE8-8F5F-0A095B9D77EC} = {95008B83-0324-4A7C-80DE-2BBDDD1A9099}
{C5519C51-0A0C-4317-A43D-FFBB6B344ACB} = {95008B83-0324-4A7C-80DE-2BBDDD1A9099}
{7EBD5500-0DA0-415A-831D-5DC350917501} = {CC8D0880-B73E-4DFC-9052-86504728708E}
{A99787D7-A93B-4357-A8B5-B5F1FD2930AB} = {CC8D0880-B73E-4DFC-9052-86504728708E}
{2C827B2C-75DF-413B-9AB2-2D1B438AC082} = {CC8D0880-B73E-4DFC-9052-86504728708E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C49B2D3E-6818-4E28-91B7-6E4E7E264BBB}

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.10</Version>
<Version>3.0.0.13</Version>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Authors>Diego</Authors>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.10</Version>
<Version>3.0.0.13</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DefineConstants>$(DefineConstants);ThingsGateway</DefineConstants>
<DefineConstants>$(DefineConstants);ThingsGateway</DefineConstants>
</PropertyGroup>
<ItemGroup>
@@ -9,13 +9,14 @@
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.8.8.48" />
<PackageReference Include="Furion.Pure" Version="4.8.8.48" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.110" />
<PackageReference Include="SqlSugar.TDengineCore" Version="2.8.0" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
<ProjectReference Include="..\ThingsGateway.Core\ThingsGateway.Core.csproj" />
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
<ProjectReference Include="..\ThingsGateway.Core\ThingsGateway.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -38,6 +38,9 @@ public static class Startup
{ nameof(PEnqueuedSnackbars.Position), SnackPosition.TopCenter }
}
},
{ nameof(MErrorHandler), new Dictionary<string, object>() { { nameof(MErrorHandler.ShowDetail), true } } },
{ nameof(MIcon), new Dictionary<string, object>() { { nameof(MIcon.Dense), true } } },
@@ -49,7 +52,7 @@ public static class Startup
{ nameof(MDescriptions), new Dictionary<string, object>() { { nameof(MDescriptions.Dense), true } } },
{ nameof(MRow), new Dictionary<string, object>() { { nameof(MRow.Dense), true } } },
{ "MAutocomplete", new Dictionary<string, object>() { { "Dense", true } } },
{ "MCascader", new Dictionary<string, object>() { { "Dense", true } } },
{ "MCascader", new Dictionary<string, object>() { { "Dense", true },{ "Outlined", true } } },
{ "MCheckbox", new Dictionary<string, object>() { { "Dense", true } } },
{ "MFileInput", new Dictionary<string, object>() { { "Dense", true } } },
{ "MRadioGroup", new Dictionary<string, object>() { { "Dense", true } } },

View File

@@ -352,7 +352,54 @@
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087330930950,
"FileName": "ThingsGateway.Plugin.SQLDB",
"AssembleName": "SQLDB",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Plugin.SQLDB/ThingsGateway.Plugin.SQLDB.dll",
"CreateTime": "2023/8/6 18:23:02",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087330930951,
"FileName": "ThingsGateway.Plugin.QuestDB",
"AssembleName": "QuestDB",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Plugin.QuestDB/ThingsGateway.Plugin.QuestDB.dll",
"CreateTime": "2023/8/6 18:23:02",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 447087330930952,
"FileName": "ThingsGateway.Plugin.TDengineDB",
"AssembleName": "TDengineDB",
"DriverTypeEnum": "Upload",
"FilePath": "Plugins/ThingsGateway.Plugin.TDengineDB/ThingsGateway.Plugin.TDengineDB.dll",
"CreateTime": "2023/8/6 18:23:02",
"CreateUser": "superAdmin",
"CreateUserId": 212725263002001,
"IsDelete": 0,
"UpdateTime": null,
"UpdateUser": null,
"UpdateUserId": null,
"SortCode": 0,
"ExtJson": null
},
{
"Id": 442505,
"FileName": "ThingsGateway.Plugin.DLT645",

View File

@@ -99,9 +99,80 @@ public class HistoryValueWorker : BackgroundService
IsAutoCloseConnection = true, //不设成true要手动close
ConfigureExternalServices = configureExternalServices,
});
AopSetting(sqlSugarClient);
return OperResult.CreateSuccessResult(sqlSugarClient);
}
#region db
/// <summary>
/// Aop设置
/// </summary>
/// <param name="db"></param>
private static void AopSetting(SqlSugarClient db)
{
var config = db.CurrentConnectionConfig;
// 设置超时时间
db.Ado.CommandTimeOut = 30;
// 打印SQL语句
db.Aop.OnLogExecuting = (sql, pars) =>
{
//如果不是开发环境就打印sql
if (App.HostEnvironment.IsDevelopment())
{
if (sql.StartsWith("SELECT"))
{
Console.ForegroundColor = ConsoleColor.Green;
}
if (sql.StartsWith("UPDATE"))
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
if (sql.StartsWith("INSERT"))
{
Console.ForegroundColor = ConsoleColor.Blue;
}
if (sql.StartsWith("DELETE"))
{
Console.ForegroundColor = ConsoleColor.Red;
}
WriteSqlLog(UtilMethods.GetSqlString(config.DbType, sql, pars));
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
}
};
//异常
db.Aop.OnError = (ex) =>
{
//如果不是开发环境就打印日志
if (App.WebHostEnvironment.IsDevelopment())
{
if (ex.Parametres == null) return;
Console.ForegroundColor = ConsoleColor.Red;
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
WriteSqlLogError(UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres));
Console.ForegroundColor = ConsoleColor.White;
}
};
}
private static void WriteSqlLog(string msg)
{
Console.WriteLine("【Sql执行时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
private static void WriteSqlLogError(string msg)
{
Console.WriteLine("【Sql执行错误时间】" + DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat());
Console.WriteLine("【Sql语句】" + msg + Environment.NewLine);
}
#endregion
#region worker服务
private EasyLock easyLock = new();
@@ -327,6 +398,7 @@ public class HistoryValueWorker : BackgroundService
try
{
_logger.LogWarning("连接历史数据表失败,尝试初始化表");
sqlSugarClient.DbMaintenance.CreateDatabase();
sqlSugarClient.CodeFirst.InitTables(typeof(HistoryValue));
LastIsSuccess = true;
StatuString = OperResult.CreateSuccessResult();
@@ -467,7 +539,8 @@ public class HistoryValueMapper : IRegister
{
config.ForType<DeviceVariableRunTime, HistoryValue>()
.Map(dest => dest.Value, (src) => ValueReturn(src))
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime());//注意sqlsugar插入时无时区直接utc时间
.Map(dest => dest.CollectTime, (src) => src.CollectTime.ToUniversalTime())//注意sqlsugar插入时无时区直接utc时间
.Map(dest => dest.CreateTime, (src) => DateTime.UtcNow);//注意sqlsugar插入时无时区直接utc时间
}
private static object ValueReturn(DeviceVariableRunTime src)

View File

@@ -27,13 +27,28 @@ public class HistoryValue : PrimaryIdEntity
public override long Id { get; set; }
/// <summary>
/// 上传时间
/// 采集时间
/// </summary>
[TimeDbSplitField(DateType.Month)]
[Description("上传时间")]
[Description("采集时间")]
[DataTable(Order = 1, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
public DateTime CollectTime { get; set; }
/// <summary>
/// 上传时间
/// </summary>
[Description("上传时间")]
[DataTable(Order = 1, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
public DateTime CreateTime { get; set; }
/// <summary>
/// 设备名称
/// </summary>
[SugarColumn(ColumnDataType = "symbol")]
[Description("设备名称")]
[DataTable(Order = 2, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
public string DeviceName { get; set; }
/// <summary>
/// 变量名称
/// </summary>
@@ -41,6 +56,7 @@ public class HistoryValue : PrimaryIdEntity
[Description("变量名称")]
[DataTable(Order = 2, IsShow = true, Sortable = true, CellClass = " table-text-truncate ")]
public string Name { get; set; }
/// <summary>
/// 是否在线
/// </summary>

View File

@@ -6,7 +6,7 @@ export const language = ["en","zh"];
export const removeDefaultStopWordFilter = false;
export const removeDefaultStemmer = false;
export { default as Mark } from "E:\\Tg\\ThingsGateway\\ThingsGateway-DEV\\handbook\\node_modules\\mark.js\\dist\\mark.js"
export const searchIndexUrl = "search-index{dir}.json?_=097027f5";
export const searchIndexUrl = "search-index{dir}.json?_=590a19da";
export const searchResultLimits = 8;
export const searchResultContextMaxLength = 50;
export const explicitSearchResultPath = true;

View File

@@ -227,9 +227,9 @@
"179": {
"js": [
{
"file": "assets/js/main.5ea8d7cc.js",
"hash": "8fef14814f80d395",
"publicPath": "/thingsgateway-docs/assets/js/main.5ea8d7cc.js"
"file": "assets/js/main.4e65a7c4.js",
"hash": "7245ddb629dafe73",
"publicPath": "/thingsgateway-docs/assets/js/main.4e65a7c4.js"
}
]
},
@@ -299,9 +299,9 @@
"1303": {
"js": [
{
"file": "assets/js/runtime~main.63f259ea.js",
"hash": "9366fdc84edb655f",
"publicPath": "/thingsgateway-docs/assets/js/runtime~main.63f259ea.js"
"file": "assets/js/runtime~main.7f6e9c09.js",
"hash": "aedff306f0f845ba",
"publicPath": "/thingsgateway-docs/assets/js/runtime~main.7f6e9c09.js"
}
]
},
@@ -326,9 +326,9 @@
"1822": {
"js": [
{
"file": "assets/js/f05a39b7.ba620b20.js",
"hash": "45780fd223d26c99",
"publicPath": "/thingsgateway-docs/assets/js/f05a39b7.ba620b20.js"
"file": "assets/js/f05a39b7.bfb8e652.js",
"hash": "3d8f498000b417cb",
"publicPath": "/thingsgateway-docs/assets/js/f05a39b7.bfb8e652.js"
}
]
},
@@ -484,9 +484,9 @@
"5254": {
"js": [
{
"file": "assets/js/aa2a52e0.3e583530.js",
"hash": "11f046baf4c7f23f",
"publicPath": "/thingsgateway-docs/assets/js/aa2a52e0.3e583530.js"
"file": "assets/js/aa2a52e0.8249d909.js",
"hash": "740d166c624d8086",
"publicPath": "/thingsgateway-docs/assets/js/aa2a52e0.8249d909.js"
}
]
},

View File

@@ -11,6 +11,9 @@
"editUrl": "https://gitee.com/diego2098/ThingsGateway/tree/master/handbook/docs/pluginmc3e.mdx",
"tags": [],
"version": "current",
"lastUpdatedBy": "Kimdiego2098",
"lastUpdatedAt": 1697169111,
"formattedLastUpdatedAt": "Oct 13, 2023",
"frontMatter": {
"id": "pluginmc3e",
"title": "三菱QnA3E采集插件"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -65,7 +65,7 @@ ThingsGatewayPro是ThingsGateway的加强版本**基础功能与ThingsGateway
|6| 深圳市***自动化技术有限公司 | | 2023-7-16|
|7| 西安***信息技术有限责任公司 | | 2023-9-4|
|8| 北京****网络科技发展有限公司 | | 2023-9-26|
|9| 长沙**软件科技有限公司 | | 2023-10-16|
## 六、购买途径