跳到主要内容

通过 SDK 读写 DB 块

本文介绍如何使用 Darra SDK 远程读写 PLC 数据块 (DB),适用于上位机监控、MES 数据采集和第三方系统集成。

建立连接

所有 DB 操作前需通过 DarraLink 协议建立连接。

连接参数

参数说明默认值
IP 地址PLC 运行时 IP
端口DarraLink 端口18821
超时连接超时时间5000ms
using Darra.PLC.SDK;

var connection = new PlcConnection("192.168.1.100", 18821)
{
Timeout = TimeSpan.FromSeconds(5)
};

await connection.ConnectAsync();

读取 DB 块数据

读取基本类型

// 读取 BOOL 类型
bool bValue = await connection.ReadDbAsync<bool>("DB_Status", "bMotorRunning");

// 读取数值类型
float fValue = await connection.ReadDbAsync<float>("DB_ProcessParam", "rSetTemp");
short iValue = await connection.ReadDbAsync<short>("DB_ProcessParam", "iErrorCode");
int diValue = await connection.ReadDbAsync<int>("DB_Counter", "diTotalCount");

// 读取字符串
string sValue = await connection.ReadDbAsync<string>("DB_ProcessParam", "sRecipeName");

读取结构体

// 定义结构体 (与 PLC 端一致)
public class AxisConfig
{
public float MaxSpeed { get; set; }
public float AccelTime { get; set; }
public float DecelTime { get; set; }
public bool EnablePositive { get; set; }
public bool EnableNegative { get; set; }
}

// 读取整个结构体
var axisConfig = await connection.ReadDbAsync<AxisConfig>("DB_Machine", "stAxes[0]");
Console.WriteLine($"轴 0 最大速度: {axisConfig.MaxSpeed}");

// 读取结构体中的单个字段
float maxSpeed = await connection.ReadDbAsync<float>("DB_Machine", "stAxes[0].rMaxSpeed");

读取数组

// 读取整个数组
float[] temperatures = await connection.ReadDbAsync<float[]>("DB_ProcessParam", "arHistory");
Console.WriteLine($"历史数据点: {temperatures.Length}");

// 读取数组中的单个元素
float temp = await connection.ReadDbAsync<float>("DB_ProcessParam", "arHistory[5]");

// 读取数组切片 (部分读取)
float[] slice = await connection.ReadDbAsync<float[]>("DB_ProcessParam", "arHistory[0..9]");

批量读取

// 批量读取多个变量
var results = await connection.ReadBatchAsync(
("DB_ProcessParam.rSetTemp", typeof(float)),
("DB_ProcessParam.rActTemp", typeof(float)),
("DB_ProcessParam.sRecipeName", typeof(string)),
("DB_Status.bMotorRunning", typeof(bool))
);

foreach (var (name, value) in results)
{
Console.WriteLine($"{name} = {value}");
}

写入 DB 块数据

写入基本类型

// 写入 BOOL
await connection.WriteDbAsync("DB_Control", "bStartMotor", true);

// 写入数值
await connection.WriteDbAsync("DB_ProcessParam", "rSetTemp", 150.0f);
await connection.WriteDbAsync("DB_ProcessParam", "iErrorCode", 0);

// 写入字符串
await connection.WriteDbAsync("DB_ProcessParam", "sRecipeName", "标准配方");

写入结构体

// 写入整个结构体
var config = new AxisConfig
{
MaxSpeed = 3000.0f,
AccelTime = 0.5f,
DecelTime = 0.3f,
EnablePositive = true,
EnableNegative = true
};

await connection.WriteDbAsync("DB_Machine", "stAxes[0]", config);

// 写入结构体中的单个字段
await connection.WriteDbAsync("DB_Machine", "stAxes[0].rMaxSpeed", 2500.0f);

批量写入

// 批量写入 (原子操作)
await connection.WriteBatchAsync(
("DB_Control.bStartMotor", true),
("DB_ProcessParam.rSetTemp", 120.0f),
("DB_ProcessParam.sRecipeName", "高速配方")
);

订阅与通知

订阅变量变化

// 订阅单个变量
connection.Subscribe("DB_ProcessParam.rActTemp", (name, value) =>
{
float temp = Convert.ToSingle(value);
Console.WriteLine($"温度已更新: {temp:F1}°C");
});

// 订阅多个变量
connection.Subscribe("DB_ProcessParam.rSetTemp", OnSetpointChanged);
connection.Subscribe("DB_ProcessParam.rActTemp", OnFeedbackChanged);
connection.Subscribe("DB_Alarm.bActiveAlarm", OnAlarmChanged);

订阅过滤

// 设置变化阈值 (仅当变化超过阈值时通知)
connection.Subscribe("DB_ProcessParam.rActTemp", OnTempChanged,
changeThreshold: 0.5f); // 温度变化超过 0.5°C 才通知

// 设置最小通知间隔
connection.Subscribe("DB_Status.bMotorRunning", OnStatusChanged,
minIntervalMs: 100); // 100ms 内最多通知一次

订阅 DB 变量变化通知

SDK 支持对 DB 块中的变量进行订阅,当 PLC 侧变量值变化时,SDK 实时推送通知:

// 订阅单个 DB 变量
connection.SubscribeDbVariable(
"DB_ProcessParam", "rActTemp",
(dbName, varName, value) =>
{
float temp = Convert.ToSingle(value);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] DB {dbName}.{varName} = {temp:F1}");
}
);

// 订阅整个 DB 块的所有变量变化
connection.SubscribeDbBlock("DB_ProcessParam", (dbName, changes) =>
{
foreach (var (varName, value) in changes)
{
Console.WriteLine($"{dbName}.{varName}{value}");
}
});

// 订阅带过滤条件的 DB 变量
connection.SubscribeDbVariable(
"DB_ProcessParam", "rActTemp",
OnTemperatureChanged,
new SubscriptionFilter
{
ChangeThreshold = 0.5f, // 变化超过 0.5 才通知
MinIntervalMs = 200, // 200ms 内最多通知一次
DeadbandPercent = 1.0f // 1% 死区
}
);

取消订阅

// 取消单个订阅
connection.Unsubscribe("DB_ProcessParam.rActTemp");

// 取消整个 DB 块的订阅
connection.UnsubscribeDbBlock("DB_ProcessParam");

// 取消所有订阅
connection.UnsubscribeAll();

数据类型映射

SDK 与 PLC 之间的数据类型映射关系:

PLC 类型C# 类型Python 类型Java 类型Rust 类型字节数
BOOLboolboolbooleanbool1
BYTEbyteintbyteu81
WORDushortintintu162
DWORDuintintlongu324
LWORDulongintlongu648
SINTsbyteintbytei81
INTshortintshorti162
DINTintintinti324
LINTlongintlongi648
REALfloatfloatfloatf324
LREALdoublefloatdoublef648
STRING[n]stringstrStringStringn+2
TIMETimeSpanint (ms)DurationDuration4
DATEDateTimeintDateNaiveDate4
TODTimeSpanintLocalTimeDuration4
ARRAYT[]listT[]Vec<T>n × 元素大小
STRUCTclass/structdictclassstruct各字段和

类型转换注意事项

// 1. 读取 INT 赋值给 C# short
short value = await connection.ReadDbAsync<short>("DB_ProcessParam", "iErrorCode");

// 2. 读取 REAL 赋值给 C# float
float temp = await connection.ReadDbAsync<float>("DB_ProcessParam", "rActTemp");

// 3. 读取 DWORD 赋值给 C# uint
uint flags = await connection.ReadDbAsync<uint>("DB_Status", "diFlags");

// 4. 读取 LREAL 赋值给 C# double
double precise = await connection.ReadDbAsync<double>("DB_ProcessParam", "rPreciseValue");

// 5. 类型不匹配时抛出 PlcTypeMismatchException
try
{
// PLC 端是 REAL,但尝试读取为 INT
int wrong = await connection.ReadDbAsync<int>("DB_ProcessParam", "rActTemp");
}
catch (PlcTypeMismatchException ex)
{
Console.WriteLine($"类型不匹配: 期望 {ex.ExpectedType}, 实际 {ex.ActualType}");
}

错误处理

连接错误

try
{
await connection.ConnectAsync();
}
catch (PlcConnectionException ex)
{
Console.WriteLine($"无法连接到 PLC: {ex.Message}");
Console.WriteLine($"目标: {ex.Endpoint}");
// 实施重试策略
await RetryConnectionAsync();
}
catch (PlcAuthenticationException ex)
{
Console.WriteLine($"认证失败: {ex.Message}");
// 检查凭据
}

读写错误

try
{
await connection.WriteDbAsync("DB_ProcessParam", "rSetTemp", value);
}
catch (PlcVariableNotFoundException ex)
{
Console.WriteLine($"变量不存在: {ex.VariableName}");
// 检查变量路径
}
catch (PlcTypeMismatchException ex)
{
Console.WriteLine($"类型不匹配: 期望 {ex.ExpectedType}, 实际 {ex.ActualType}");
}
catch (PlcTimeoutException ex)
{
Console.WriteLine($"操作超时: {ex.Message}");
// 重试
}
catch (PlcWriteProtectedException ex)
{
Console.WriteLine($"变量为只读: {ex.VariableName}");
// 检查 PLC 侧变量权限
}
finally
{
await connection.DisconnectAsync();
}

重试策略

// 带指数退避的重试策略
public async Task<T> ReadWithRetryAsync<T>(string dbName, string varName, int maxRetries = 3)
{
int retryCount = 0;
int baseDelayMs = 1000;

while (true)
{
try
{
return await connection.ReadDbAsync<T>(dbName, varName);
}
catch (PlcTimeoutException) when (retryCount < maxRetries)
{
retryCount++;
int delayMs = baseDelayMs * (int)Math.Pow(2, retryCount - 1);
Console.WriteLine($"读取超时,第 {retryCount} 次重试,等待 {delayMs}ms...");
await Task.Delay(delayMs);
}
catch (PlcConnectionException) when (retryCount < maxRetries)
{
retryCount++;
Console.WriteLine($"连接中断,尝试重新连接...");
await connection.ConnectAsync();
}
}
}

// 使用重试读取
float temp = await ReadWithRetryAsync<float>("DB_ProcessParam", "rActTemp");

完整示例: 配方管理

using Darra.PLC.SDK;

class RecipeManager
{
private readonly PlcConnection _connection;

public RecipeManager(string plcIp)
{
_connection = new PlcConnection(plcIp, 18821);
}

public async Task LoadRecipeAsync(string recipeName)
{
await _connection.ConnectAsync();

try
{
// 1. 停止当前工艺
await _connection.WriteDbAsync("DB_Control", "bStopProcess", true);
await Task.Delay(500);

// 2. 从本地数据库查询配方参数
var recipe = await QueryRecipeFromDatabase(recipeName);

// 3. 批量写入 PLC 配方参数
await _connection.WriteBatchAsync(
("DB_ProcessParam.rSetTemp", recipe.SetTemperature),
("DB_ProcessParam.rSetPressure", recipe.SetPressure),
("DB_ProcessParam.rSpeedLimit", recipe.SpeedLimit),
("DB_ProcessParam.sRecipeName", recipeName),
("DB_ProcessParam.iRecipeVersion", recipe.Version)
);

// 4. 读取写入确认
var verifyTemp = await _connection.ReadDbAsync<float>(
"DB_ProcessParam", "rSetTemp");

Console.WriteLine($"配方已加载: {recipeName}");
Console.WriteLine($"验证温度: {verifyTemp}°C");

// 5. 启动工艺
await _connection.WriteDbAsync("DB_Control", "bStartProcess", true);
}
catch (Exception ex)
{
Console.WriteLine($"配方加载失败: {ex.Message}");
throw;
}
finally
{
await _connection.DisconnectAsync();
}
}

private async Task<RecipeData> QueryRecipeFromDatabase(string name)
{
// 模拟数据库查询
return new RecipeData
{
SetTemperature = 150.0f,
SetPressure = 2.5f,
SpeedLimit = 3000.0f,
Version = 3
};
}
}

class RecipeData
{
public float SetTemperature { get; set; }
public float SetPressure { get; set; }
public float SpeedLimit { get; set; }
public int Version { get; set; }
}

完整 C# 示例:数据采集与监控

using Darra.PLC.SDK;

class DataCollector
{
private readonly PlcConnection _connection;
private readonly string _plcIp;
private readonly int _plcPort;
private CancellationTokenSource? _cts;

public DataCollector(string plcIp, int plcPort = 18821)
{
_plcIp = plcIp;
_plcPort = plcPort;
_connection = new PlcConnection(_plcIp, _plcPort)
{
AutoReconnect = true,
ReconnectIntervalMs = 5000,
MaxReconnectAttempts = 0
};
}

public async Task StartAsync(CancellationToken ct = default)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
await _connection.ConnectAsync();

Console.WriteLine("数据采集已启动");

// 订阅关键变量
SubscribeVariables();

// 启动采集循环
await CollectLoopAsync(_cts.Token);
}

private void SubscribeVariables()
{
// 订阅温度变化
_connection.SubscribeDbVariable(
"DB_ProcessParam", "rActTemp",
(db, varName, value) =>
{
float temp = Convert.ToSingle(value);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 温度: {temp:F1}°C");

if (temp > 150.0f)
{
Console.WriteLine("⚠ 温度超限报警!");
}
},
new SubscriptionFilter { ChangeThreshold = 0.5f }
);

// 订阅报警状态
_connection.SubscribeDbVariable(
"DB_Alarm", "bActiveAlarm",
(db, varName, value) =>
{
bool isActive = Convert.ToBoolean(value);
if (isActive)
{
string msg = _connection.ReadDbAsync<string>(
"DB_Alarm", "sAlarmMessage").Result;
Console.WriteLine($"🚨 报警: {msg}");
}
}
);
}

private async Task CollectLoopAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
// 批量读取
var data = await _connection.ReadBatchAsync(
("DB_ProcessParam.rActTemp", typeof(float)),
("DB_ProcessParam.rActPressure", typeof(float)),
("DB_Status.bMotorRunning", typeof(bool)),
("DB_Counter.diTotalCount", typeof(int))
);

// 写入数据库或文件
await SaveToDatabase(data);

await Task.Delay(1000, ct);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Console.WriteLine($"采集异常: {ex.Message}");
await Task.Delay(5000, ct);
}
}
}

private async Task SaveToDatabase(
IEnumerable<(string name, object value)> data)
{
// 写入时序数据库或本地文件
await Task.CompletedTask;
}

public void Stop()
{
_cts?.Cancel();
_connection?.Dispose();
}
}

性能优化

连接复用

// 长时间运行的应用程序应复用连接
public class PlcService : IDisposable
{
private readonly PlcConnection _connection;

public PlcService(string ip)
{
_connection = new PlcConnection(ip, 18821);
}

public async Task StartAsync()
{
await _connection.ConnectAsync();
// 持续使用同一连接
}

public void Dispose()
{
_connection?.Dispose();
}
}

批量操作

尽量使用批量读写替代单次操作,减少网络往返:

方式100 个变量耗时说明
单次读取 × 100~2s100 次网络往返
批量读取 × 1~20ms1 次网络往返

排错表

问题原因解决
连接超时PLC 不可达或端口错误检查 IP 和端口 (18821)
变量未找到变量路径拼写错误确认 DB 名称和变量名
类型转换异常SDK 类型与 PLC 不匹配确认 PLC 变量数据类型
写入被拒绝变量被强制或只读在 IDE 中取消强制
订阅无响应网络防火墙阻断检查 18821 端口通信
批量写入部分失败其中一个变量异常检查批量写入的错误列表

相关文档