跳到主要内容

采集插件开发

一、插件开发概述

1.1 什么是采集插件

ThingsGateway 采集插件是用于与各种工业设备进行通讯的模块,负责数据的采集和写入操作。插件系统采用了灵活的架构设计,允许开发者根据不同设备的通讯协议和特性,开发自定义的采集插件。

1.2 插件继承体系

ThingsGateway 提供了多个基类用于插件开发,开发者可以根据需求选择合适的基类:

  • CollectBase:最基础的采集插件基类,提供了基本的框架和方法
  • CollectFoundationBase:继承自 CollectBase,提供了更多的基础功能实现
  • CollectFoundationPackPropertyBase:继承自 CollectFoundationBase,提供了打包读取的功能支持

1.3 插件开发流程

  1. 创建插件项目:创建一个类库项目,引用必要的依赖
  2. 继承基类:选择合适的基类继承
  3. 实现必要方法:根据设备特性实现必要的方法
  4. 配置插件属性:定义插件的配置项
  5. 编译打包:编译项目,生成DLL文件
  6. 部署测试:可以直接引用项目,F5调试

二、插件开发详解

2.1 创建插件项目

2.1.1 项目结构

  1. 创建类库项目:使用 Visual Studio 创建一个 .NET 类库项目
  2. 引用依赖:引用以下必要的依赖:
    • ThingsGateway.Gateway.Application

2.1.2 项目配置

在项目文件中添加以下配置:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>


<ItemGroup>
<ProjectReference Include="..\..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
</ItemGroup>

</Project>

2.2 插件类实现

2.2.1 基本插件结构

using Newtonsoft.Json.Linq;
using ThingsGateway.Foundation;
using ThingsGateway.Gateway.Application;
using ThingsGateway.Razor;

namespace ThingsGateway.Plugin.YourPlugin;

/// <summary>
/// 插件类,继承<see cref="CollectFoundationBase"/> 实现采集插件
/// </summary>
public class YourCollectPlugin : CollectFoundationBase
{
/// <summary>
/// 插件配置项
/// </summary>
public override CollectPropertyBase CollectProperties => _property;
private YourCollectProperty? _property = new();

// 其他方法实现...
}

/// <summary>
/// 插件配置类
/// </summary>
public class YourCollectProperty : CollectFoundationPackPropertyBase
{
// 配置项定义...
}

2.2.2 关键方法实现

2.2.2.1 初始化方法
/// <summary>
/// 在插件初始化时调用,只会执行一次
/// </summary>
/// <param name="channel">插件默认的链路通道类</param>
/// <param name="cancellationToken">取消令牌</param>
protected override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
{
// 初始化通讯参数
// 设置连接属性
// 初始化其他资源
return Task.CompletedTask;
}
2.2.2.2 变量打包方法
/// <summary>
/// 变量打包操作,将设备变量打包成源读取变量
/// </summary>
/// <param name="deviceVariables">设备变量列表</param>
/// <returns>源读取变量列表</returns>
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
var sourceReads = new List<VariableSourceRead>();

// 实现变量打包逻辑
// 例如:将多个连续的寄存器地址打包成一个读取操作

// 示例:打包多个Modbus寄存器
var addressGroups = deviceVariables
.GroupBy(v => GetBaseAddress(v.Variable.Address))
.OrderBy(g => g.Key);

foreach (var group in addressGroups)
{
var firstVar = group.First();
var lastVar = group.Last();
var startAddress = GetRegisterAddress(firstVar.Variable.Address);
var endAddress = GetRegisterAddress(lastVar.Variable.Address);
var length = endAddress - startAddress + 1;

var sourceRead = new VariableSourceRead
{
Address = startAddress.ToString(),
Length = length * 2, // 每个寄存器2字节
VariableRunTimes = group.ToList()
};

sourceReads.Add(sourceRead);
}

return Task.FromResult(sourceReads);
}
2.2.2.3 读取源变量方法
/// <summary>
/// 读取源变量,在VariableSourceReadsEnable为true时执行
/// </summary>
/// <param name="variableSourceRead">源读取变量</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>读取结果</returns>
protected override async ValueTask<OperResult<byte[]>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
{
try
{
// 实现读取逻辑
// 例如:发送读取命令到设备,接收响应数据

// 示例:Modbus读取
var address = int.Parse(variableSourceRead.Address);
var length = variableSourceRead.Length / 2; // 每个寄存器2字节

// 发送读取命令
var result = await _modbusClient.ReadHoldingRegistersAsync(address, length, cancellationToken);

if (result.IsSuccess)
{
// 解析响应数据
var data = new byte[length * 2];
Buffer.BlockCopy(result.Content, 0, data, 0, data.Length);

// 更新变量值
foreach (var variable in variableSourceRead.VariableRunTimes)
{
var varAddress = GetRegisterAddress(variable.Variable.Address);
var offset = (varAddress - address) * 2;
var value = ParseValue(data, offset, variable.Variable.DataType);
variable.SetValue(value, DateTime.Now, true);
}

return new OperResult<byte[]>(data);
}
else
{
return new OperResult<byte[]>(result.ErrorCode, result.ErrorMessage);
}
}
catch (Exception ex)
{
return new OperResult<byte[]>(ex.Message);
}
}
2.2.2.4 写入变量方法
/// <summary>
/// 写入变量,实现设备写入操作
/// </summary>
/// <param name="writeInfoLists">写入信息列表</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>写入结果</returns>
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
{
var results = new Dictionary<string, OperResult>();

using var writeLock = ReadWriteLock.WriterLock();

foreach (var item in writeInfoLists)
{
var variable = item.Key;
var value = item.Value;

try
{
// 实现写入逻辑
// 例如:发送写入命令到设备

// 示例:Modbus写入
var address = GetRegisterAddress(variable.Variable.Address);
var writeValue = ConvertValue(value, variable.Variable.DataType);

// 发送写入命令
var result = await _modbusClient.WriteSingleRegisterAsync(address, writeValue, cancellationToken);

if (result.IsSuccess)
{
// 更新本地变量值
variable.SetValue(value, DateTime.Now, true);
results.Add(variable.Variable.Name, new OperResult());
}
else
{
results.Add(variable.Variable.Name, new OperResult(result.ErrorCode, result.ErrorMessage));
}
}
catch (Exception ex)
{
results.Add(variable.Variable.Name, new OperResult(ex.Message));
}
}

return results;
}
2.2.2.5 设备连接测试方法
/// <summary>
/// 实现离线重连任务
/// </summary>
/// <param name="state">状态对象</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns></returns>
protected override async Task TestOnline(object? state, CancellationToken cancellationToken)
{
try
{
// 实现连接测试逻辑
// 例如:发送一个简单的命令到设备,检查响应

// 示例:Modbus连接测试
var result = await _modbusClient.ReadHoldingRegistersAsync(0, 1, cancellationToken);

if (result.IsSuccess)
{
// 连接成功
CurrentDevice.SetDeviceStatus(DeviceStatus.Online);
}
else
{
// 连接失败
CurrentDevice.SetDeviceStatus(DeviceStatus.Offline);
}
}
catch (Exception)
{
// 连接失败
CurrentDevice.SetDeviceStatus(DeviceStatus.Offline);
}
}

/// <summary>
/// 返回是否成功连接设备
/// </summary>
/// <returns>是否连接成功</returns>
public override bool IsConnected()
{
// 实现连接状态检查逻辑
// 例如:检查底层连接是否有效

return _modbusClient?.IsConnected ?? false;
}

2.3 插件配置

2.3.1 配置类定义

/// <summary>
/// 插件配置类
/// </summary>
public class YourCollectProperty : CollectFoundationPackPropertyBase
{
/// <summary>
/// IP地址
/// </summary>
[DynamicProperty(Description = "设备IP地址", Remark = "例如:192.168.1.100")]
public string IpAddress { get; set; } = "192.168.1.100";

/// <summary>
/// 端口号
/// </summary>
[DynamicProperty(Description = "设备端口号", Remark = "例如:502")]
public int Port { get; set; } = 502;

/// <summary>
/// 从站地址
/// </summary>
[DynamicProperty(Description = "设备从站地址", Remark = "例如:1")]
public byte SlaveId { get; set; } = 1;

/// <summary>
/// 超时时间
/// </summary>
[DynamicProperty(Description = "通讯超时时间(ms)", Remark = "例如:3000")]
public int Timeout { get; set; } = 3000;

/// <summary>
/// 重试次数
/// </summary>
[DynamicProperty(Description = "通讯失败重试次数", Remark = "例如:3")]
public int RetryCount { get; set; } = 3;
}

2.3.2 配置UI

如果需要为插件提供配置UI,可以实现以下接口:

  • IDriverUIBase:设备UI接口
  • IPropertyUIBase:属性UI接口
  • IAddressUIBase:地址UI接口
  • IDriverDebugUIBase:调试UI接口

2.4 特殊方法

插件可以定义特殊方法,通过RPC调用:

/// <summary>
/// 特殊方法,添加<see cref="DynamicMethodAttribute"/>特性
/// </summary>
/// <param name="param1">参数1</param>
/// <param name="param2">参数2</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果</returns>
[DynamicMethod("测试方法")]
public IOperResult<string> TestMethod(string param1, int param2, CancellationToken cancellationToken)
{
try
{
// 实现方法逻辑
var result = $"测试方法执行成功:param1={param1}, param2={param2}";
return new OperResult<string>(result);
}
catch (Exception ex)
{
return new OperResult<string>(ex.Message);
}
}

三、插件调试与测试

3.1 调试方法

  1. 附加进程调试:将 Visual Studio 附加到 ThingsGateway.Server 进程进行调试
  2. 日志调试:在插件中添加日志输出,通过日志查看执行情况
  3. 调试UI:实现调试UI接口,通过界面进行调试

3.2 测试方法

  1. 单元测试:为插件的核心功能编写单元测试
  2. 集成测试:将插件部署到测试环境,进行集成测试

3.3 常见问题排查

  1. 连接失败:检查网络连接、设备地址、端口等参数是否正确
  2. 读取失败:检查寄存器地址、数据类型、设备权限等是否正确
  3. 写入失败:检查设备是否支持写入、写入权限是否正确
  4. 性能问题:检查变量打包逻辑、通讯频率、数据处理逻辑等

六、总结

ThingsGateway 采集插件开发是一个灵活、强大的扩展机制,通过开发自定义插件,可以实现对各种工业设备的采集和控制。

核心优势

  • 灵活性:支持多种设备类型和通讯协议
  • 可扩展性:通过插件机制,轻松扩展支持新设备
  • 高性能:支持变量打包、批量读取等优化技术
  • 可靠性:内置断线重连、异常处理等机制

通过本文档的指导,您应该能够开发出高质量的ThingsGateway采集插件,为工业物联网系统提供更强大的数据采集能力。